diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6fe7679..186e979 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,10 +33,10 @@ jobs: repository: "Aliucord/Aliucord" path: "repo" - - name: Setup JDK 21 + - name: Setup JDK 11 uses: actions/setup-java@v1 with: - java-version: 21 + java-version: 11 - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 @@ -46,8 +46,8 @@ jobs: cd $GITHUB_WORKSPACE/src chmod +x gradlew ./gradlew make generateUpdaterJson - cp {canary,plugins}/*/build/outputs/*.zip $GITHUB_WORKSPACE/builds - cp build/outputs/updater.json $GITHUB_WORKSPACE/builds + cp {canary,plugins}/*/build/*.zip $GITHUB_WORKSPACE/builds + cp build/updater.json $GITHUB_WORKSPACE/builds - name: Push builds run: | diff --git a/README.md b/README.md index 63011cc..85716d0 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,26 @@ # Awoocord Plugins -## [Bubbles](plugins/Crocosmia) | [Download](https://github.com/cillynder/Awoocord/raw/builds/Bubbles.zip) +## [Bubbles](plugins/Zinnia) | [Download](https://github.com/LavaDesu/Awoocord/raw/builds/RoleBlocks.zip) Wrap messages in bubbles inspired by Material 3 Expressive -## [Clump](plugins/Bocchi) | [Download](https://github.com/cillynder/Awoocord/raw/builds/Clump.zip) - -Group messages more leniently (e.g. mentions, attachments, etc..), reducing clutter and wasted space. - -## [Glance](plugins/Myosotis) | [Download](https://github.com/cillynder/Awoocord/raw/builds/Glance.zip) - -Backports DM previews similar to latest RN. Shows you a line of the last message sent in a DM. - -## [RoleBlocks](plugins/Zinnia) | [Download](https://github.com/cillynder/Awoocord/raw/builds/RoleBlocks.zip) +## [RoleBlocks](plugins/Zinnia) | [Download](https://github.com/LavaDesu/Awoocord/raw/builds/RoleBlocks.zip) Apply the role colour as a background of usernames, improving contrast with some role colours -## [Scout](plugins/Scout) | [Download](https://github.com/cillynder/Awoocord/raw/builds/Scout.zip) +## [Scout](plugins/Scout) | [Download](https://github.com/LavaDesu/Awoocord/raw/builds/Scout.zip) -Vastly improves the search experience on Aliucord. - -Features: -- Sort by oldest messages first -- Sort by oldest first -- Filter by date (before, during, after) -- Exclude certain messages (opposite of `in:`) (not even desktop has this!) -- Search by user ID -- Search in threads - -Fixes: -- Removes the large padding from the top, most noticable if your device has a large status bar -- Removes the unnecessary #0000 discriminator +Reimplemented features from search of other clients: +- Sorting by oldest first +- Filter by date +- Search from user ID # WIP Backports -## [SlashCommandsFix](canary/SlashCommandsFix) | [Download](https://github.com/cillynder/Awoocord/raw/builds/SlashCommandsFixBeta.zip) +## [SlashCommandsFix](canary/SlashCommandsFix) | [Download](https://github.com/LavaDesu/Awoocord/raw/builds/SlashCommandsFixBeta.zip) Fixes slash commands not showing up. -## [ComponentsV2](canary/ComponentsV2) | [Download](https://github.com/cillynder/Awoocord/raw/builds/ComponentsV2Beta.zip) +## [ComponentsV2](canary/ComponentsV2) | [Download](https://github.com/LavaDesu/Awoocord/raw/builds/ComponentsV2Beta.zip) Fix missing/empty bot messages using the new embed system. Such messages will be marked "CV2" as part of its tag. diff --git a/build.gradle.kts b/build.gradle.kts index 6cfa17b..4cc50c4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,8 +46,8 @@ subprojects { } configure { - author("cilly", 368398754077868032L, hyperlink = false) - github("https://github.com/cillynder/Awoocord") + author("Lava", 368398754077868032L, hyperlink = true) + github("https://github.com/LavaDesu/Awoocord") } configure { diff --git a/plugins/Bocchi/build.gradle.kts b/plugins/Bocchi/build.gradle.kts deleted file mode 100644 index 1803a3c..0000000 --- a/plugins/Bocchi/build.gradle.kts +++ /dev/null @@ -1,25 +0,0 @@ -version = "1.0.3" -description = "More lenient message grouping" - -android { - namespace = "moe.lava.awoocord.bocchi" -} - -aliucord { - // Changelog of your plugin - changelog.set(""" - # 1.0.3 - * Clump more than 6 messages together - - # 1.0.2 - * Fix (inverted) webhook clumping - - # 1.0.1 - * Hide blank space w.r.t attachments and embeds - - # 1.0.0 - * Initial release >w< - """.trimIndent()) - - deploy.set(true) -} diff --git a/plugins/Bocchi/src/main/kotlin/moe/lava/awoocord/bocchi/Bocchi.kt b/plugins/Bocchi/src/main/kotlin/moe/lava/awoocord/bocchi/Bocchi.kt deleted file mode 100644 index d4d28da..0000000 --- a/plugins/Bocchi/src/main/kotlin/moe/lava/awoocord/bocchi/Bocchi.kt +++ /dev/null @@ -1,59 +0,0 @@ -package moe.lava.awoocord.bocchi - -import android.content.Context -import android.view.View -import com.aliucord.annotations.AliucordPlugin -import com.aliucord.entities.Plugin -import com.aliucord.patcher.* -import com.aliucord.utils.accessField -import com.discord.api.message.MessageTypes -import com.discord.models.message.Message -import com.discord.utilities.view.text.SimpleDraweeSpanTextView -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage -import com.discord.widgets.chat.list.entries.ChatListEntry -import com.discord.widgets.chat.list.entries.MessageEntry -import com.discord.widgets.chat.list.model.WidgetChatListModelMessages - -private val WidgetChatListAdapterItemMessage.itemText by accessField() - -@AliucordPlugin(requiresRestart = true) -@Suppress("unused") -class Bocchi : Plugin() { - override fun start(context: Context) { - patcher.after( - "onConfigure", - Int::class.java, - ChatListEntry::class.java, - ) { (_, _: Int, entry: MessageEntry) -> - if (entry.type == ChatListEntry.MESSAGE_MINIMAL && entry.message.content.isNullOrEmpty()) { - itemText.visibility = View.GONE - } - } - patcher.instead( - "shouldConcatMessage", - WidgetChatListModelMessages.Items::class.java, - Message::class.java, - Message::class.java, - ) { (_, items: WidgetChatListModelMessages.Items, message: Message, message2: Message?) -> - val timeDiff = (message.timestamp?.g() ?: 0L) - (message2?.timestamp?.g() ?: 0L) - return@instead !( - message2 == null || - message2.isSystemMessage || - message.hasThread() || - message2.hasThread() || - message.type !in arrayOf(MessageTypes.DEFAULT, MessageTypes.LOCAL) || - message.author.id != message2.author.id || - timeDiff >= 420000 || // WidgetChatListModelMessages.MESSAGE_CONCAT_TIMESTAMP_DELTA_THRESHOLD -// items.listItemMostRecentlyAdded.type !in arrayOf(0, 1, 4, 21) || -// message2.hasAttachments() || -// message2.hasEmbeds() || -// message2.mentions?.isNotEmpty() == true || -// message.mentions?.isNotEmpty() == true || -// message.hasAttachments() || -// message.hasEmbeds() || -// items.concatCount >= 5 || - (message.isWebhook && message.author?.username != message2.author.username) - ) - } - } -} diff --git a/plugins/Crocosmia/build.gradle.kts b/plugins/Crocosmia/build.gradle.kts index a3aaca9..0bec3f2 100644 --- a/plugins/Crocosmia/build.gradle.kts +++ b/plugins/Crocosmia/build.gradle.kts @@ -8,5 +8,5 @@ aliucord { * Initial release >w< """.trimIndent()) - deploy.set(true) + deploy.set(false) } diff --git a/plugins/Myosotis/build.gradle.kts b/plugins/Myosotis/build.gradle.kts deleted file mode 100644 index 7bd110c..0000000 --- a/plugins/Myosotis/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -version = "1.0.0" -description = "Backports DM previews" - -aliucord { - // Changelog of your plugin - changelog.set(""" - # 1.0.0 - * Initial release >w< - """.trimIndent()) - - deploy.set(true) -} diff --git a/plugins/Myosotis/src/main/kotlin/moe/lava/awoocord/myosotis/Myosotis.kt b/plugins/Myosotis/src/main/kotlin/moe/lava/awoocord/myosotis/Myosotis.kt deleted file mode 100644 index 60ed811..0000000 --- a/plugins/Myosotis/src/main/kotlin/moe/lava/awoocord/myosotis/Myosotis.kt +++ /dev/null @@ -1,205 +0,0 @@ -package moe.lava.awoocord.myosotis - -import android.annotation.SuppressLint -import android.content.Context -import android.view.View -import androidx.fragment.app.FragmentManager -import androidx.recyclerview.widget.RecyclerView -import com.aliucord.Http -import com.aliucord.Utils -import com.aliucord.annotations.AliucordPlugin -import com.aliucord.api.GatewayAPI -import com.aliucord.entities.Plugin -import com.aliucord.patcher.after -import com.aliucord.patcher.before -import com.aliucord.patcher.component1 -import com.aliucord.patcher.component2 -import com.aliucord.patcher.component3 -import com.aliucord.utils.ChannelUtils -import com.aliucord.utils.GsonUtils -import com.aliucord.utils.SerializedName -import com.aliucord.utils.accessField -import com.aliucord.wrappers.ChannelWrapper.Companion.id -import com.aliucord.wrappers.users.globalName -import com.discord.api.message.Message -import com.discord.databinding.WidgetChannelsListItemChannelPrivateBinding -import com.discord.models.domain.ModelMessageDelete -import com.discord.stores.StoreStream -import com.discord.utilities.color.ColorCompat -import com.discord.utilities.textprocessing.DiscordParser -import com.discord.utilities.textprocessing.MessagePreprocessor -import com.discord.utilities.textprocessing.MessageRenderContext -import com.discord.utilities.view.text.SimpleDraweeSpanTextView -import com.discord.widgets.channels.list.WidgetChannelsListAdapter -import com.discord.widgets.channels.list.items.ChannelListItem -import com.discord.widgets.channels.list.items.ChannelListItemPrivate -import com.discord.widgets.chat.list.adapter.`WidgetChatListAdapterItemMessage$getMessageRenderContext$1` -import com.discord.widgets.chat.list.adapter.`WidgetChatListAdapterItemMessage$getMessageRenderContext$4` -import com.google.gson.reflect.TypeToken -import com.lytefast.flexinput.R -import java.lang.ref.WeakReference - -private val WidgetChannelsListAdapter.ItemChannelPrivate.binding - by accessField() - -private val responseType = TypeToken.getParameterized(List::class.java, Message::class.java).type - -data class ChannelIdsPayload( - @SerializedName("channel_ids") val channelIds: List, -) - -data class MessageItem( - val id: Long, - val content: String?, -) - -fun Message.wrap(): MessageItem { - val author = this.e() - val authorName = if (author.id == StoreStream.getUsers().me.id) { - "You" - } else { - author.globalName ?: author.username - } - val content = this.i() - .takeIf { it.isNotEmpty() } - ?.let { content -> "$authorName: ${content.takeWhile { it != '\n' }}" } - - return MessageItem( - id = this.o(), - content = content, - ) -} - -fun SimpleDraweeSpanTextView.renderText(content: String, other: Pair) { - val me = StoreStream.getUsers().me - val meId = me.id - val meName = me.globalName ?: me.username - val processor = MessagePreprocessor(meId, listOf(), null, false, 50) - val parseChannelMessage = DiscordParser.parseChannelMessage( - context, - content, - MessageRenderContext( - context, - meId, - false, - mapOf(meId to meName, other), - StoreStream.getChannels().channelNames, - mapOf(), - R.b.colorTextLink, - `WidgetChatListAdapterItemMessage$getMessageRenderContext$1`.INSTANCE, - { }, - ColorCompat.getThemedColor(context, R.b.theme_chat_spoiler_bg), - ColorCompat.getThemedColor(context, R.b.theme_chat_spoiler_bg_visible), - { }, - { }, - `WidgetChatListAdapterItemMessage$getMessageRenderContext$4`(context) - ), - processor, - DiscordParser.ParserOptions.DEFAULT, - false - ) - setDraweeSpanStringBuilder(parseChannelMessage); -} - -@AliucordPlugin -class Myosotis : Plugin() { - var cache = mutableMapOf() - var adapterRef: WeakReference? = null - - override fun stop(context: Context) { patcher.unpatchAll() } - - override fun start(context: Context) { - GatewayAPI.onEvent("READY") { refreshAll() } - GatewayAPI.onEvent("RESUMED") { refreshAll() } - - patcher.after( - "onConfigure", - Int::class.java, - ChannelListItem::class.java, - ) { (_, _: Int, item: ChannelListItemPrivate) -> - cache[item.channel.id]?.let { msg -> - val content = msg.content - ?: return@let - - val descView = binding.d - descView.visibility = View.VISIBLE - val user = ChannelUtils.getDMRecipient(item.channel) - descView.renderText(content, user.id to (user.globalName ?: user.username)) - } - } - patcher.after( - RecyclerView::class.java, - FragmentManager::class.java, - ) { - adapterRef = WeakReference(this) - } - - patcher.before( - "handleMessageCreate", - Message::class.java - ) { (_, msg: Message) -> - handleMessageUpdate(msg) - } - - patcher.before( - "handleMessageUpdate", - Message::class.java - ) { (_, msg: Message) -> - handleMessageUpdate(msg) - } - - patcher.before( - "handleMessageDelete", - ModelMessageDelete::class.java - ) { (_, deleteModel: ModelMessageDelete) -> - cache[deleteModel.channelId]?.let { msg -> - if (msg.id in deleteModel.messageIds) { - cache.remove(deleteModel.channelId) - rerender(deleteModel.channelId) - } - } - } - } - - private fun handleMessageUpdate(msg: Message) { - val gid = msg.m() - if (gid == null) { - val channelId = msg.g() - - val oldMsgId = cache[channelId]?.id ?: 0 - if (msg.o() > oldMsgId) { - cache[channelId] = msg.wrap() - rerender(channelId) - } - } - } - - @OptIn(ExperimentalStdlibApi::class) - private fun refreshAll() { - val channels = StoreStream.getChannels().getChannelsForGuild(0) - .filterValues { it.D() == 1 } // type == Type.DM - .keys.take(100) - Utils.threadPool.execute { - val res = Http.Request.newDiscordRNRequest("/channels/preload-messages", "POST") - .executeWithJson(ChannelIdsPayload(channels)) - .json>(GsonUtils.gsonRestApi, responseType) - cache = mutableMapOf(*res.map { it.g() to it.wrap() }.toTypedArray()) - - Utils.mainThread.post { - @SuppressLint("NotifyDataSetChanged") // I DONT CARE HAHAHAAHJAHAAJHDLAHD - adapterRef?.get()?.notifyDataSetChanged() - } - } - } - - private fun rerender(id: Long) { - val adapter = adapterRef?.get() ?: return - val idx = adapter.internalData.indexOfFirst { it.key == "3$id" } - logger.info("found $idx for $id") - if (idx != -1) { - Utils.mainThread.post { - adapter.notifyItemChanged(idx) - } - } - } -} diff --git a/plugins/Scout/build.gradle.kts b/plugins/Scout/build.gradle.kts index 5f7859a..dc6cb7f 100644 --- a/plugins/Scout/build.gradle.kts +++ b/plugins/Scout/build.gradle.kts @@ -1,4 +1,4 @@ -version = "1.4.0" +version = "1.3.0" description = "Backported and improved search functionality" android { @@ -14,12 +14,6 @@ aliucord { Changelog {added marginTop} ====================== - # 1.4.0 - Scout is searching for clues about the elusive MvM update - * Added the authorType filter option to search by user, bot, or webhook - * Moved sort filter to the top of the new ones - * Fixes a Discord bug where typing "mentions" would also suggest "has" - * Some people said the options were getting bloated, so they're all hidden behind a "Show all" button now. They'll still show up in auto suggestions. - # 1.3.0 * Removes empty discriminator when searching with users diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/FilterTypeExtension.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/FilterTypeExtension.kt index 58afef0..6c2ba23 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/FilterTypeExtension.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/FilterTypeExtension.kt @@ -3,15 +3,12 @@ package moe.lava.awoocord.scout import com.discord.utilities.search.query.FilterType object FilterTypeExtension { - lateinit var EXPAND: FilterType - lateinit var SORT: FilterType lateinit var BEFORE: FilterType lateinit var DURING: FilterType lateinit var AFTER: FilterType + lateinit var SORT: FilterType lateinit var EXCLUDE: FilterType - lateinit var AUTHOR_TYPE: FilterType lateinit var dates: Array - lateinit var filters: Array lateinit var values: Array } diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt index c32ddda..10e3e6b 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/Scout.kt @@ -1,54 +1,27 @@ -@file:Suppress("EnumValuesSoftDeprecate", "CanConvertToMultiDollarString") - -/** - * Hi to anyone who might be reading this; I am sorry for the atrocious code in this plugin - * but I promise I'll be fixing it up soon :3 - */ - package moe.lava.awoocord.scout -import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources import android.view.View -import android.view.ViewGroup import android.widget.ImageView import androidx.core.content.res.ResourcesCompat import com.aliucord.Utils import com.aliucord.annotations.AliucordPlugin import com.aliucord.entities.Plugin -import com.aliucord.patcher.PreHook -import com.aliucord.patcher.after -import com.aliucord.patcher.before -import com.aliucord.patcher.component1 -import com.aliucord.patcher.component2 -import com.aliucord.patcher.component3 -import com.aliucord.patcher.component4 -import com.aliucord.patcher.component5 -import com.aliucord.patcher.instead +import com.aliucord.patcher.* import com.aliucord.utils.DimenUtils.dp -import com.aliucord.utils.RxUtils.subscribe import com.aliucord.utils.ViewUtils.findViewById -import com.aliucord.utils.accessField import com.aliucord.wrappers.ChannelWrapper.Companion.id import com.discord.BuildConfig -import com.discord.api.channel.Channel -import com.discord.api.channel.ChannelUtils -import com.discord.api.channel.`ChannelUtils$getSortByNameAndType$1` +import com.discord.api.channel.* import com.discord.api.permission.Permission -import com.discord.databinding.WidgetSearchSuggestionItemHeaderBinding import com.discord.databinding.WidgetSearchSuggestionsItemHasBinding -import com.discord.databinding.WidgetSearchSuggestionsItemSuggestionBinding import com.discord.models.member.GuildMember import com.discord.models.user.User import com.discord.restapi.RequiredHeadersInterceptor import com.discord.restapi.RestAPIBuilder -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.simpleast.core.parser.Parser -import com.discord.simpleast.core.parser.Rule -import com.discord.stores.StoreSearch -import com.discord.stores.StoreSearchInput -import com.discord.stores.StoreStream +import com.discord.simpleast.core.parser.* +import com.discord.stores.* import com.discord.utilities.mg_recycler.MGRecyclerDataPayload import com.discord.utilities.mg_recycler.SingleTypePayload import com.discord.utilities.rest.RestAPI.AppHeadersProvider @@ -56,60 +29,35 @@ import com.discord.utilities.search.network.`SearchFetcher$getRestObservable$3` import com.discord.utilities.search.network.SearchQuery import com.discord.utilities.search.query.FilterType import com.discord.utilities.search.query.node.QueryNode -import com.discord.utilities.search.query.node.answer.ChannelNode -import com.discord.utilities.search.query.node.answer.HasAnswerOption -import com.discord.utilities.search.query.node.answer.HasNode -import com.discord.utilities.search.query.node.answer.UserNode +import com.discord.utilities.search.query.node.answer.* import com.discord.utilities.search.query.node.content.ContentNode import com.discord.utilities.search.query.node.filter.FilterNode import com.discord.utilities.search.query.parsing.QueryParser import com.discord.utilities.search.query.parsing.`QueryParser$Companion$getInAnswerRule$1` -import com.discord.utilities.search.strings.ContextSearchStringProvider import com.discord.utilities.search.strings.SearchStringProvider import com.discord.utilities.search.suggestion.SearchSuggestionEngine -import com.discord.utilities.search.suggestion.entries.ChannelSuggestion -import com.discord.utilities.search.suggestion.entries.FilterSuggestion -import com.discord.utilities.search.suggestion.entries.HasSuggestion -import com.discord.utilities.search.suggestion.entries.SearchSuggestion +import com.discord.utilities.search.suggestion.entries.* import com.discord.utilities.search.validation.SearchData import com.discord.widgets.search.results.WidgetSearchResults import com.discord.widgets.search.suggestions.WidgetSearchSuggestions -import com.discord.widgets.search.suggestions.`WidgetSearchSuggestions$configureUI$1` import com.discord.widgets.search.suggestions.WidgetSearchSuggestionsAdapter import com.franmontiel.persistentcookiejar.PersistentCookieJar import com.franmontiel.persistentcookiejar.cache.SetCookieCache import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor import com.lytefast.flexinput.R import moe.lava.awoocord.scout.api.SearchAPIInterface -import moe.lava.awoocord.scout.entries.AuthorTypeSuggestion -import moe.lava.awoocord.scout.entries.AuthorTypeViewHolder -import moe.lava.awoocord.scout.parsing.AuthorType -import moe.lava.awoocord.scout.parsing.AuthorTypeNode -import moe.lava.awoocord.scout.parsing.DateNode -import moe.lava.awoocord.scout.parsing.SimpleParserRule -import moe.lava.awoocord.scout.parsing.SortNode -import moe.lava.awoocord.scout.parsing.UserIdNode -import moe.lava.awoocord.scout.ui.DatePickerFragment -import moe.lava.awoocord.scout.ui.ScoutResource -import moe.lava.awoocord.scout.ui.ScoutSearchStringProvider +import moe.lava.awoocord.scout.parsing.* +import moe.lava.awoocord.scout.ui.* import java.util.regex.Pattern import b.a.k.b as FormatUtils -private val WidgetSearchSuggestionsAdapter.FilterViewHolder.binding - by accessField() - -private val WidgetSearchSuggestionsAdapter.HeaderViewHolder.binding - by accessField() - -@AliucordPlugin +@AliucordPlugin() @Suppress("unused", "unchecked_cast") class Scout : Plugin() { lateinit var scoutRes: ScoutResource lateinit var ssProvider: ScoutSearchStringProvider lateinit var searchApi: SearchAPIInterface - var optionsExpanded = false - init { @Suppress("DEPRECATION") needsResources = true @@ -124,24 +72,20 @@ class Scout : Plugin() { override fun start(context: Context) { extendFilterType() extendHasAnswerOption() - extendSuggestionCategory() - fixFiltersKeying() - fixHasFilterSuggestion() - fixSearchPadding() patchHasAnswerOption() patchHasNode() patchQuery() patchQueryParser() patchSearchUI(context) + patchSearchPadding() patchThreadSupport() patchUsernameDiscriminator() } override fun stop(context: Context) { - patcher.unpatchAll() resetFilterType() resetHasAnswerOption() - resetSuggestionCategory() + patcher.unpatchAll() } // Creates a new custom search API implementation, for the extra `min_id` param in search queries @@ -167,7 +111,7 @@ class Scout : Plugin() { private var origFilterTypes: Array? = null // Creates new pseudo-values of the `FilterType` enum for date filters - @Suppress("LocalVariableName", "AssignedValueIsNeverRead") + @Suppress("LocalVariableName") private fun extendFilterType() { val cls = FilterType::class.java val constructor = cls.declaredConstructors[0] @@ -179,23 +123,18 @@ class Scout : Plugin() { origFilterTypes = origFilterTypes ?: values var nextIdx = values.size - val EXPAND = constructor.newInstance("EXPAND", nextIdx++) as FilterType - val SORT = constructor.newInstance("SORT", nextIdx++) as FilterType val EXCLUDE = constructor.newInstance("EXCLUDE", nextIdx++) as FilterType - val AUTHOR_TYPE = constructor.newInstance("AUTHOR_TYPE", nextIdx++) as FilterType val BEFORE = constructor.newInstance("BEFORE", nextIdx++) as FilterType val DURING = constructor.newInstance("DURING", nextIdx++) as FilterType val AFTER = constructor.newInstance("AFTER", nextIdx++) as FilterType - FilterTypeExtension.EXPAND = EXPAND - FilterTypeExtension.SORT = SORT + val SORT = constructor.newInstance("SORT", nextIdx) as FilterType FilterTypeExtension.EXCLUDE = EXCLUDE - FilterTypeExtension.AUTHOR_TYPE = AUTHOR_TYPE FilterTypeExtension.BEFORE = BEFORE FilterTypeExtension.DURING = DURING FilterTypeExtension.AFTER = AFTER + FilterTypeExtension.SORT = SORT FilterTypeExtension.dates = arrayOf(BEFORE, DURING, AFTER) - FilterTypeExtension.filters = arrayOf(SORT, AUTHOR_TYPE, EXCLUDE) + FilterTypeExtension.dates - FilterTypeExtension.values = arrayOf(EXPAND) + FilterTypeExtension.filters + FilterTypeExtension.values = arrayOf(EXCLUDE, BEFORE, DURING, AFTER, SORT) val newValues = values.toMutableList() newValues.addAll(FilterTypeExtension.values) @@ -215,7 +154,7 @@ class Scout : Plugin() { private var origHasAnswerOptions: Array? = null // Creates new pseudo-values of the `HasAnswerOption` enum for poll and forwarded filters - @Suppress("LocalVariableName", "AssignedValueIsNeverRead") + @Suppress("LocalVariableName") private fun extendHasAnswerOption() { val cls = HasAnswerOption::class.java val constructor = cls.declaredConstructors[0] @@ -228,7 +167,7 @@ class Scout : Plugin() { var nextIdx = values.size val POLL = constructor.newInstance("POLL", nextIdx++, "poll") as HasAnswerOption - val SNAPSHOT = constructor.newInstance("SNAPSHOT", nextIdx++, "snapshot") as HasAnswerOption + val SNAPSHOT = constructor.newInstance("SNAPSHOT", nextIdx, "snapshot") as HasAnswerOption HasAnswerOptionExtension.POLL = POLL HasAnswerOptionExtension.SNAPSHOT = SNAPSHOT HasAnswerOptionExtension.values = arrayOf(POLL, SNAPSHOT) @@ -249,83 +188,6 @@ class Scout : Plugin() { origHasAnswerOptions = null } - private var origSuggestionCategories: Array? = null - // Creates new pseudo-values of the suggestion categories to add correct headers - @Suppress("LocalVariableName", "AssignedValueIsNeverRead") - private fun extendSuggestionCategory() { - val cls = SearchSuggestion.Category::class.java - val constructor = cls.declaredConstructors[0] - constructor.isAccessible = true - - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - val values = field.get(null) as Array - origSuggestionCategories = origSuggestionCategories ?: values - var nextIdx = values.size - - val AUTHOR_TYPE = constructor.newInstance("AUTHOR_TYPE", nextIdx++) as SearchSuggestion.Category - SuggestionCategoryExtension.AUTHOR_TYPE = AUTHOR_TYPE - SuggestionCategoryExtension.values = arrayOf(AUTHOR_TYPE) - - val newValues = values.toMutableList() - newValues.addAll(SuggestionCategoryExtension.values) - field.set(null, newValues.toTypedArray()) - } - - private fun resetSuggestionCategory() { - if (origSuggestionCategories == null) - return logger.error("No unpatched suggestion categories?", null) - - val cls = SearchSuggestion.Category::class.java - val field = cls.getDeclaredField("\$VALUES") - field.isAccessible = true - field.set(null, origSuggestionCategories) - origSuggestionCategories = null - } - - // Patch to key filters properly for smoother recycling - // Thank u discord for keying every filter type the same thing!! /s - private fun fixFiltersKeying() { - patcher.instead( - "getFilterItem", - FilterSuggestion::class.java, - ) { (_, suggestion: FilterSuggestion) -> - SingleTypePayload(suggestion, suggestion.filterType.name, 2) // 2 = WidgetSearchSuggestionsAdapter.TYPE_FILTER - } - } - - // YES DISCORD TYPO'ED THIS HAHAHAHAHAHAFAUHFAIUFHAIFBHUKFHYRISFSUOIRN - private fun fixHasFilterSuggestion() { - patcher.before( - "getStringRepresentation", - FilterType::class.java, - SearchStringProvider::class.java, - ) { (param, filter: FilterType, provider: SearchStringProvider) -> - if (filter == FilterType.HAS) { - param.result = provider.hasFilterString + ":" - } - } - } - - // Patch out the gigantic padding in search results - private fun fixSearchPadding() { - patcher.after("onViewBound", View::class.java) { - view?.run { - fitsSystemWindows = false - setPadding(paddingLeft, 16.dp, paddingRight, paddingBottom) - } - } - - patcher.after("onViewBound", View::class.java) { - // Being a bit sneaky and reset the expanded flag here - optionsExpanded = false - view?.run { - fitsSystemWindows = false - setPadding(paddingLeft, 16.dp, paddingRight, paddingBottom) - } - } - } - // Patches various methods that use HasAnswerOption to include our new options private fun patchHasAnswerOption() { patcher.before( @@ -396,22 +258,24 @@ class Scout : Plugin() { CharSequence::class.java, FilterType::class.java, SearchStringProvider::class.java, - ) { (_, query: CharSequence, type: FilterType, provider: SearchStringProvider) -> - // Generate entries for author type - if (type == FilterTypeExtension.AUTHOR_TYPE) { - return@instead AuthorType.values() - .filter { it.value.contains(query) } - .map { AuthorTypeSuggestion(it) } + ) { param -> + val query = param.args[0] as CharSequence + val filterType = param.args[1] as FilterType + val ossProvider = param.args[2] as SearchStringProvider + + if (filterType != FilterType.HAS && filterType != FilterTypeExtension.EXCLUDE) + return@instead listOf() + + val res = mutableListOf() + for (opt in HasAnswerOption.values()) { + val filterText = opt.getLocalizedInputText(ossProvider) + + if (filterText.contains(query)) + res.add(HasSuggestion(opt)) } - - // Generate entries for has options, including new ones - if (type == FilterType.HAS || type == FilterTypeExtension.EXCLUDE) - return@instead HasAnswerOption.values() - .filter { it.getLocalizedInputText(provider).contains(query) } - .map { HasSuggestion(it) } - - listOf() + res.toList() } + } // Patching HasNode related methods for our exclude: filter type @@ -437,9 +301,9 @@ class Scout : Plugin() { val opt = field.get(this) as HasAnswerOption if (filterType == FilterType.HAS) - builder.appendParam("has", opt.restParamValue) + builder.appendParam("has", opt.restParamValue); else if (filterType == FilterTypeExtension.EXCLUDE) - builder.appendParam("has", "-" + opt.restParamValue) + builder.appendParam("has", "-" + opt.restParamValue); } // Patching the behaviour when the has suggestion is clicked @@ -469,6 +333,8 @@ class Scout : Plugin() { ) getAnswerReplacementStart.isAccessible = true + logger.info(query.joinToString("|") { it.text }) + val replacementIdx = getAnswerReplacementStart.invoke(this, query) as Int val previousFilterText = query[replacementIdx] val filterNode = if (previousFilterText.text == ssProvider.excludeFilterString) @@ -492,7 +358,6 @@ class Scout : Plugin() { var minID = params["min_id"] var maxID = params["max_id"] val sortOrder = params["sort_order"] - val authorType = params["author_type"] self.`$oldestMessageId`?.let { if (sortOrder?.getOrNull(0) == "asc") minID = listOf(it.toString()) @@ -513,8 +378,7 @@ class Scout : Plugin() { retryAttempts, self.`$searchQuery`.includeNsfw, listOf("timestamp"), - sortOrder, - authorType, + sortOrder ) else searchApi.searchChannelMessages( @@ -528,8 +392,7 @@ class Scout : Plugin() { retryAttempts, self.`$searchQuery`.includeNsfw, listOf("timestamp"), - sortOrder, - authorType, + sortOrder ) } ) @@ -549,8 +412,6 @@ class Scout : Plugin() { DateNode.getDateRule(), SortNode.getFilterRule(ssProvider.sortFilterString), SortNode.getSortRule(ssProvider), - AuthorTypeNode.getFilterRule(ssProvider.authorTypeFilter), - AuthorTypeNode.getAuthorTypesRule(), SimpleParserRule(Pattern.compile("^\\s*?${ssProvider.excludeFilterString}:", 64)) { _, _, obj -> ParseSpec(FilterNode(FilterTypeExtension.EXCLUDE, ssProvider.excludeFilterString), obj) } @@ -559,7 +420,6 @@ class Scout : Plugin() { } // This is probably the worst bit of this plugin - @SuppressLint("SetTextI18n") private fun patchSearchUI(context: Context) { // Run when a filter suggestion is clicked // Most of the code is copied from its implementation @@ -572,7 +432,7 @@ class Scout : Plugin() { ) { param -> val filter = param.args[0] as FilterType if (filter !in FilterTypeExtension.values) - return@before // Exit if not an extended filter type + return@before; // Exit if not an extended filter type val replaceAndPublish = StoreSearchInput::class.java.getDeclaredMethod( "replaceAndPublish", @@ -606,7 +466,7 @@ class Scout : Plugin() { getAnswerReplacementStart.invoke(this, list), listOf(filterNode, DateNode(it)), list - ) + ); } } @@ -615,21 +475,14 @@ class Scout : Plugin() { lastIndex, listOf(filterNode, SortNode(ssProvider.sortOldString)), list - ) + ); if (filter == FilterTypeExtension.EXCLUDE) replaceAndPublish.invoke(this, lastIndex, listOf(filterNode), list - ) - - if (filter == FilterTypeExtension.AUTHOR_TYPE) - replaceAndPublish.invoke(this, - lastIndex, - listOf(filterNode), - list - ) + ); param.result = null } @@ -648,7 +501,6 @@ class Scout : Plugin() { FilterTypeExtension.AFTER -> false to scoutRes.getDrawableId("baseline_update_24") FilterTypeExtension.SORT -> true to R.e.ic_sort_white_24dp FilterTypeExtension.EXCLUDE -> false to scoutRes.getDrawableId("baseline_do_disturb_on_24") - FilterTypeExtension.AUTHOR_TYPE -> true to R.e.ic_members_24dp else -> false to null } @@ -670,8 +522,6 @@ class Scout : Plugin() { param.result = ScoutResource.SORT_ANSWER if (type == FilterTypeExtension.EXCLUDE) param.result = ssProvider.getIdentifier("search_answer_has") - if (type == FilterTypeExtension.AUTHOR_TYPE) - param.result = ScoutResource.AUTHOR_TYPE_ANSWER } // Patch for retrieving filter name @@ -686,7 +536,6 @@ class Scout : Plugin() { FilterTypeExtension.DURING -> ssProvider.getIdentifier("search_filter_during") FilterTypeExtension.AFTER -> ssProvider.getIdentifier("search_filter_after") FilterTypeExtension.SORT -> ScoutResource.SORT_FILTER - FilterTypeExtension.AUTHOR_TYPE -> ScoutResource.AUTHOR_TYPE_FILTER else -> null } res?.let { param.result = it } @@ -695,8 +544,7 @@ class Scout : Plugin() { // Patch formatting utils to use our custom lowercase strings // This is called by FilterViewHolder.onConfigure, using the results from getAnswerText and getFilterText patcher.patch( - FormatUtils::class.java.getDeclaredMethod( - "c", + FormatUtils::class.java.getDeclaredMethod("c", Resources::class.java, Int::class.javaPrimitiveType!!, Array::class.java, @@ -709,8 +557,6 @@ class Scout : Plugin() { ScoutResource.SORT_FILTER -> ssProvider.sortFilterString ScoutResource.SORT_ANSWER -> ssProvider.sortOldString ScoutResource.EXCLUDE_FILTER -> ssProvider.excludeFilterString - ScoutResource.AUTHOR_TYPE_FILTER -> ssProvider.authorTypeFilter - ScoutResource.AUTHOR_TYPE_ANSWER -> ssProvider.authorTypeAnswer else -> null } override?.let { @@ -719,105 +565,38 @@ class Scout : Plugin() { } ) - // Patch to manually configure expander, need to do this to update the suggestions widget - patcher.before( - "onConfigure", - Int::class.javaPrimitiveType!!, - MGRecyclerDataPayload::class.java, - ) { (param, _: Int, payload: SingleTypePayload) -> - val suggestion = payload.data - if (suggestion.filterType != FilterTypeExtension.EXPAND) { - return@before - } - param.result = null - - val sampleText = binding.b - val layout = binding.c - val filterText = binding.d - val icon = binding.e - layout.setOnClickListener { - val onFilter = adapter.onFilterClicked as `WidgetSearchSuggestions$configureUI$1` - val widget = onFilter.`this$0` - optionsExpanded = true - WidgetSearchSuggestions.Model.Companion!!.get(ContextSearchStringProvider(context)).z().subscribe { - WidgetSearchSuggestions.`access$configureUI`(widget, this) - } - } - sampleText.text = null - filterText.text = ssProvider.expandFilterString - val drawable = R.e.ic_chevron_right_primary_300_12dp - icon.setImageDrawable(ResourcesCompat.getDrawable(context.resources, drawable, null)) - } - // Patch to add our new filters into the initial suggestions patcher.after( "getFilterSuggestions", CharSequence::class.java, SearchStringProvider::class.java, Boolean::class.javaPrimitiveType!!, - ) { (param, query: CharSequence) -> + ) { param -> + val query = param.args[0] as CharSequence val res = (param.result as List).toMutableList() + for (type in FilterTypeExtension.values) { + val st = ssProvider.stringFor(type) + ":" - if (optionsExpanded || query != "") { - for (type in FilterTypeExtension.filters) { - val st = ssProvider.stringFor(type) + ":" - - if (st.contains(query)) - res.add(FilterSuggestion(type)) - } - } else { - res.add(FilterSuggestion(FilterTypeExtension.EXPAND)) + if (st.contains(query)) + res.add(FilterSuggestion(type)) } param.result = res.toList() } + } - // Patch to add header for new categories - patcher.before( - "onConfigure", - Int::class.javaPrimitiveType!!, - MGRecyclerDataPayload::class.java, - ) { (param, _: Int, payload: SingleTypePayload) -> - val category = payload.data - if (category == SuggestionCategoryExtension.AUTHOR_TYPE) { - binding.b.text = "Author Type" - param.result = null + // Patch out the gigantic padding in search results + private fun patchSearchPadding() { + patcher.after("onViewBound", View::class.java) { + view?.run { + fitsSystemWindows = false + setPadding(paddingLeft, 16.dp, paddingRight, paddingBottom) } } - // Patch to add entries depending on category - patcher.after( - List::class.java, - List::class.java, - ) { (_, _: List, suggestions: List) -> - var lastCategory: SearchSuggestion.Category? = null - val newItems = mutableListOf() - suggestions.forEach { - if (it is AuthorTypeSuggestion) { - if (lastCategory != it.category) { - newItems.add( - SingleTypePayload(it.category, it.category.name, 0) - ) - lastCategory = it.category - } - newItems.add( - SingleTypePayload(it, it.type.value, SuggestionCategoryExtension.AdapterType.AUTHOR_TYPE) - ) - } - } - suggestionItems.removeAll { it in newItems } - suggestionItems.addAll(0, newItems) - } - - // Patch to add new types of suggestion entries - patcher.before( - "onCreateViewHolder", - ViewGroup::class.java, - Int::class.javaPrimitiveType!!, - ) { (param, _: ViewGroup, id: Int) -> - when (id) { - SuggestionCategoryExtension.AdapterType.AUTHOR_TYPE -> { - param.result = AuthorTypeViewHolder(this, scoutRes) - } + patcher.after("onViewBound", View::class.java) { + view?.run { + fitsSystemWindows = false + setPadding(paddingLeft, 16.dp, paddingRight, paddingBottom) } } } @@ -827,7 +606,7 @@ class Scout : Plugin() { // Patch query parser for in: to support names with spaces, by wrapping them in quotes // This enables searching for threads which can have spaces in their names patcher.instead("getInAnswerRule") { - val compile = Pattern.compile("^\\s*#(\".*?\"|[^ ]+)", 64) + val compile = Pattern.compile("^\\s*#(\".*?\"|[^ ]+)", 64); `QueryParser$Companion$getInAnswerRule$1`(compile, compile) } @@ -922,7 +701,7 @@ class Scout : Plugin() { // Now it matches something like @[#] (bots still have discriminators) // The @ is required unfortunately, to distinguish it from literally any other word patcher.instead("getUserRule") { - val regex = Pattern.compile("^\\s*@(?:([^@#:]+)#([0-9]{4})|([a-z0-9._]{2,32}))", 64) + val regex = Pattern.compile("^\\s*@(?:([^@#:]+)#([0-9]{4})|([a-z0-9._]{2,32}))", 64); // Returns a new rule to support our optional second group (discriminator) return@instead SimpleParserRule(regex) { matcher, _, obj -> diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/SuggestionCategoryExtension.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/SuggestionCategoryExtension.kt deleted file mode 100644 index 2fcc40b..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/SuggestionCategoryExtension.kt +++ /dev/null @@ -1,12 +0,0 @@ -package moe.lava.awoocord.scout - -import com.discord.utilities.search.suggestion.entries.SearchSuggestion - -object SuggestionCategoryExtension { - lateinit var AUTHOR_TYPE: SearchSuggestion.Category - lateinit var values: Array - - object AdapterType { - const val AUTHOR_TYPE = 7 - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/api/SearchAPIInterface.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/api/SearchAPIInterface.kt index c3b45e4..6bbe273 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/api/SearchAPIInterface.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/api/SearchAPIInterface.kt @@ -24,7 +24,6 @@ interface SearchAPIInterface { @t("include_nsfw") includeNsfw: Boolean?, @t("sort_by") sortBy: List?, // "timestamp" is one, not sure about any other sort types @t("sort_order") sortOrder: List?, // "asc" or "desc" - @t("author_type") authorType: List?, ): Observable @f("guilds/{guildId}/messages/search") @@ -41,6 +40,5 @@ interface SearchAPIInterface { @t("include_nsfw") includeNsfw: Boolean?, @t("sort_by") sortBy: List?, @t("sort_order") sortOrder: List?, - @t("author_type") authorType: List?, ): Observable } diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeSuggestion.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeSuggestion.kt deleted file mode 100644 index b989e03..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeSuggestion.kt +++ /dev/null @@ -1,9 +0,0 @@ -package moe.lava.awoocord.scout.entries - -import com.discord.utilities.search.suggestion.entries.SearchSuggestion -import moe.lava.awoocord.scout.SuggestionCategoryExtension -import moe.lava.awoocord.scout.parsing.AuthorType - -data class AuthorTypeSuggestion(val type: AuthorType) : SearchSuggestion { - override fun getCategory() = SuggestionCategoryExtension.AUTHOR_TYPE -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeViewHolder.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeViewHolder.kt deleted file mode 100644 index afb1161..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/entries/AuthorTypeViewHolder.kt +++ /dev/null @@ -1,77 +0,0 @@ -package moe.lava.awoocord.scout.entries - -import android.widget.ImageView -import android.widget.TextView -import com.aliucord.Utils -import com.aliucord.utils.ViewUtils.findViewById -import com.discord.stores.StoreSearchInput -import com.discord.stores.StoreStream -import com.discord.utilities.mg_recycler.MGRecyclerDataPayload -import com.discord.utilities.mg_recycler.MGRecyclerViewHolder -import com.discord.utilities.mg_recycler.SingleTypePayload -import com.discord.utilities.search.query.node.filter.FilterNode -import com.discord.widgets.search.suggestions.`WidgetSearchSuggestions$configureUI$4` -import com.discord.widgets.search.suggestions.WidgetSearchSuggestionsAdapter -import com.lytefast.flexinput.R -import moe.lava.awoocord.scout.FilterTypeExtension -import moe.lava.awoocord.scout.parsing.AuthorType -import moe.lava.awoocord.scout.parsing.AuthorTypeNode -import moe.lava.awoocord.scout.ui.ScoutResource - -private val replaceAndPublish = StoreSearchInput::class.java.getDeclaredMethod( - "replaceAndPublish", - Int::class.javaPrimitiveType!!, - List::class.java, - List::class.java -).apply { isAccessible = true } - -private val getAnswerReplacementStart = StoreSearchInput::class.java.getDeclaredMethod( - "getAnswerReplacementStart", - List::class.java, -).apply { isAccessible = true } - -class AuthorTypeViewHolder( - adapter: WidgetSearchSuggestionsAdapter, - // This should be fine (?) - private val scoutRes: ScoutResource, -) : MGRecyclerViewHolder( - Utils.getResId("widget_search_suggestions_item_has", "layout"), - adapter, -) { - private val imageView = itemView.findViewById("search_suggestions_item_has_icon") - private val textView = itemView.findViewById("search_suggestions_item_has_text") - - override fun onConfigure(i: Int, oPayload: MGRecyclerDataPayload) { - super.onConfigure(i, oPayload) - - @Suppress("UNCHECKED_CAST") - val payload = oPayload as SingleTypePayload - val type = payload.data.type - textView.text = when (type) { - AuthorType.Bot -> "bot" - AuthorType.User -> "user" - AuthorType.Webhook -> "webhook" - } - when (type) { - AuthorType.Bot -> imageView.setImageDrawable(scoutRes.getDrawable("smart_toy_24px")) - AuthorType.User -> imageView.setImageResource(R.e.ic_members_24dp) - AuthorType.Webhook -> imageView.setImageDrawable(scoutRes.getDrawable("webhook_24px")) - } - - itemView.setOnClickListener { - val hasHandler = adapter.onHasClicked as `WidgetSearchSuggestions$configureUI$4` - val query = hasHandler.`$model`.query - - val storeInput = StoreStream.getSearch().storeSearchInput - replaceAndPublish.invoke( - storeInput, - getAnswerReplacementStart.invoke(storeInput, query) as Int, - listOf( - FilterNode(FilterTypeExtension.AUTHOR_TYPE, "authorType"), - AuthorTypeNode(type) - ), - query, - ) - } - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/AuthorTypeNode.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/AuthorTypeNode.kt deleted file mode 100644 index ecee669..0000000 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/AuthorTypeNode.kt +++ /dev/null @@ -1,64 +0,0 @@ -@file:Suppress("EnumValuesSoftDeprecate") - -package moe.lava.awoocord.scout.parsing - -import android.content.Context -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.simpleast.core.parser.Rule -import com.discord.utilities.search.network.SearchQuery -import com.discord.utilities.search.query.FilterType -import com.discord.utilities.search.query.node.QueryNode -import com.discord.utilities.search.query.node.answer.AnswerNode -import com.discord.utilities.search.query.node.filter.FilterNode -import com.discord.utilities.search.validation.SearchData -import moe.lava.awoocord.scout.FilterTypeExtension -import java.util.regex.Pattern - -// TODO: not localised, maybe one day -enum class AuthorType(val value: String) { - User("user"), - Bot("bot"), - Webhook("webhook"), - ; - - companion object { - fun from(value: String) = when (value) { - "user" -> User - "bot" -> Bot - "webhook" -> Webhook - else -> throw IllegalArgumentException("Unknown author type $value") - } - } -} - -class AuthorTypeNode(val type: AuthorType): AnswerNode() { - companion object { - fun getAuthorTypesRule(): Rule { - val joined = AuthorType.values().joinToString("|") { it.value } - val regexStr = "^\\s*(${joined})" - val regex = Pattern.compile(regexStr, Pattern.UNICODE_CASE) - return SimpleParserRule(regex) { matcher, _, obj -> - ParseSpec(AuthorTypeNode(AuthorType.from(matcher.group())), obj) - } - } - - fun getFilterRule(str: String): ParserRule { - val regex = Pattern.compile("^\\s*?(${str}):", 64) - return SimpleParserRule(regex) { _, _, obj -> - ParseSpec(FilterNode(FilterTypeExtension.AUTHOR_TYPE, str), obj) - } - } - } - - override fun getValidFilters() = setOf(FilterTypeExtension.AUTHOR_TYPE) - override fun isValid(searchData: SearchData?) = true - override fun getText() = type.value - - override fun updateQuery( - builder: SearchQuery.Builder, - searchData: SearchData?, - filterType: FilterType? - ) { - builder.appendParam("author_type", type.value) - } -} diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/DateNode.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/DateNode.kt index f72084b..d0ffa02 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/DateNode.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/DateNode.kt @@ -20,7 +20,9 @@ class DateNode(private val date: Long?, private val unparsed: String) : AnswerNo val fmt = SimpleDateFormat("yyyy-MM-dd", Locale.US) val regex: Pattern = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}", Pattern.UNICODE_CASE) fun getDateRule(): ParserRule { - return SimpleParserRule(regex) { matcher, _, obj -> + return SimpleParserRule(regex) { matcher, parser, obj -> + checkNotNull(matcher) { "matcher" } + checkNotNull(parser) { "parser" } val match = matcher.group() val date = fmt.parse(match) val node = DateNode(date?.time, match) @@ -29,7 +31,7 @@ class DateNode(private val date: Long?, private val unparsed: String) : AnswerNo } private fun getFilterRule(str: String, type: FilterType): ParserRule { - val regex = Pattern.compile("^\\s*?(${str}):", 64) + val regex = Pattern.compile("^\\s*?(${str}):", 64); return SimpleParserRule(regex) { _, _, obj -> ParseSpec(FilterNode(type, str), obj) } @@ -42,7 +44,7 @@ class DateNode(private val date: Long?, private val unparsed: String) : AnswerNo override fun getValidFilters(): Set = FilterTypeExtension.dates.toSet() override fun isValid(searchData: SearchData?): Boolean = date != null - override fun getText(): CharSequence = unparsed + override fun getText(): CharSequence? = unparsed private val snowflake: String? get() = date?.let { SnowflakeUtils.fromTimestamp(date).toString() } diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SimpleParserRule.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SimpleParserRule.kt index c78c23b..0a9a76c 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SimpleParserRule.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SimpleParserRule.kt @@ -1,9 +1,7 @@ package moe.lava.awoocord.scout.parsing import android.content.Context -import com.discord.simpleast.core.parser.ParseSpec -import com.discord.simpleast.core.parser.Parser -import com.discord.simpleast.core.parser.Rule +import com.discord.simpleast.core.parser.* import com.discord.utilities.search.query.node.QueryNode import java.util.regex.Matcher import java.util.regex.Pattern @@ -18,10 +16,12 @@ internal class SimpleParserRule( ) -> ParseSpec ) : ParserRule(regex) { override fun parse( - matcher: Matcher, + matcher: Matcher?, parser: Parser, obj: Any? ): ParseSpec { + checkNotNull(matcher) { "matcher" } + checkNotNull(parser) { "parser" } return parseMethod(matcher, parser, obj) } } diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SortNode.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SortNode.kt index e839712..e74f2a9 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SortNode.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/SortNode.kt @@ -24,7 +24,7 @@ class SortNode(private val text: String): AnswerNode() { } fun getFilterRule(str: String): ParserRule { - val regex = Pattern.compile("^\\s*?(${str}):", 64) + val regex = Pattern.compile("^\\s*?(${str}):", 64); return SimpleParserRule(regex) { _, _, obj -> ParseSpec(FilterNode(FilterTypeExtension.SORT, str), obj) } @@ -36,10 +36,11 @@ class SortNode(private val text: String): AnswerNode() { override fun getText() = this.text override fun updateQuery( - builder: SearchQuery.Builder, + builder: SearchQuery.Builder?, searchData: SearchData?, filterType: FilterType? ) { + checkNotNull(builder) { "queryBuilder" } builder.appendParam("sort_order", "asc") } } diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/UserIdNode.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/UserIdNode.kt index 85ea6c1..a3c88b7 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/UserIdNode.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/parsing/UserIdNode.kt @@ -22,7 +22,7 @@ class UserIdNode(private val userID: String) : AnswerNode() { override fun getValidFilters() = setOf(FilterType.FROM, FilterType.MENTIONS) override fun isValid(searchData: SearchData?) = true - override fun getText() = userID + override fun getText() = userID.toString() override fun updateQuery( builder: SearchQuery.Builder?, diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutResource.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutResource.kt index 59b9ed7..38c9e02 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutResource.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutResource.kt @@ -10,8 +10,6 @@ class ScoutResource(private val resources: Resources) { val SORT_FILTER = View.generateViewId() val SORT_ANSWER = View.generateViewId() val EXCLUDE_FILTER = View.generateViewId() - val AUTHOR_TYPE_FILTER = View.generateViewId() - val AUTHOR_TYPE_ANSWER = View.generateViewId() } fun getId(name: String, type: String) = diff --git a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt index d4accb2..184ced8 100644 --- a/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt +++ b/plugins/Scout/src/main/kotlin/moe/lava/awoocord/scout/ui/ScoutSearchStringProvider.kt @@ -21,7 +21,6 @@ class ScoutSearchStringProvider(private val context: Context) { FilterTypeExtension.DURING -> duringFilterString FilterTypeExtension.AFTER -> afterFilterString FilterTypeExtension.SORT -> sortFilterString - FilterTypeExtension.AUTHOR_TYPE -> authorTypeFilter else -> throw IllegalArgumentException("invalid extended filter type") } @@ -42,8 +41,6 @@ class ScoutSearchStringProvider(private val context: Context) { get() = getString("sort").decapitalise(context) val sortOldString: String get() = getString("search_oldest_short").decapitalise(context) - val expandFilterString: String - get() = getString("friends_pending_request_expand") // Not localised val hasPollString: String @@ -52,9 +49,4 @@ class ScoutSearchStringProvider(private val context: Context) { get() = "forward" val excludeFilterString: String get() = "exclude" - val authorTypeFilter: String - get() = "authorType" - val authorTypeAnswer: String - // TODO, could probably be localisable by joining each part together - get() = "user, bot or webhook" } diff --git a/plugins/Scout/src/main/res/drawable/smart_toy_24px.xml b/plugins/Scout/src/main/res/drawable/smart_toy_24px.xml deleted file mode 100644 index 30dd4b2..0000000 --- a/plugins/Scout/src/main/res/drawable/smart_toy_24px.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/plugins/Scout/src/main/res/drawable/webhook_24px.xml b/plugins/Scout/src/main/res/drawable/webhook_24px.xml deleted file mode 100644 index 74a3dc7..0000000 --- a/plugins/Scout/src/main/res/drawable/webhook_24px.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/plugins/Zinnia/build.gradle.kts b/plugins/Zinnia/build.gradle.kts index eec7d36..7d429a2 100644 --- a/plugins/Zinnia/build.gradle.kts +++ b/plugins/Zinnia/build.gradle.kts @@ -1,19 +1,9 @@ -version = "1.2.1" +version = "1.1.1" description = "Coloured usernames to be a bit more pleasing on the eyes" aliucord { // Changelog of your plugin changelog.set(""" - # 1.2.1 - * Use correct default block colour in replies - * Use correct default block colour in "unchanged" mode - - # 1.2.0 - * Finally fixes the annoying padding issue in replies - * Adds nice preview blocks in settings with configurable hsv bars for all your previewing needs - * Tweaked constrast ratio a bit which may improve some colours' legibility - * Added transparency option, alongside "unchanged" colour option which pairs nicely together for a translucent glass effect - # 1.1.1 * Revert incorrect spacing fix, since it just breaks replies. Proper fix soon diff --git a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCAUtil.kt b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCAUtil.kt deleted file mode 100644 index fbf9466..0000000 --- a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/APCAUtil.kt +++ /dev/null @@ -1,117 +0,0 @@ -package moe.lava.awoocord.zinnia - -import android.graphics.Color -import android.graphics.drawable.GradientDrawable -import android.widget.TextView -import androidx.core.graphics.ColorUtils -import com.aliucord.utils.DimenUtils.dp -import com.discord.stores.StoreStream -import kotlin.math.abs - -enum class Threshold { - Large, - Medium, - Small -} - -internal object APCAUtil { - private val settings = ZinniaSettings - - internal fun configureOn(view: TextView, colour: Int?, threshold: Threshold) { - when (settings.mode) { - Mode.Block -> configureBlock(view, colour ?: Color.BLACK, threshold) - Mode.RoleDot -> configureRoleDot(view, colour ?: Color.BLACK) - } - } - - private fun configureRoleDot(view: TextView, colour: Int) { } - - private fun configureBlock(view: TextView, colourP: Int, threshold: Threshold) { - val isLight = StoreStream.getUserSettingsSystem().theme == "light" - var colour = colourP - val bcol = GradientDrawable() - bcol.cornerRadius = 4.dp.toFloat() - view.background = bcol - view.setPadding(4.dp, 0, 4.dp, 0) - - if (colour == Color.BLACK) { - if (settings.blockAlsoDefault) { - colour = if (isLight && (settings.blockInverted || settings.blockMode == BlockMode.Unchanged)) { - Color.BLACK - } else { - Color.WHITE - } - } else { - view.background = null - view.setPadding(0, 0, 0, 0) - return - } - } - - var (preferred, other) = if (isLight) { - Color.WHITE to Color.BLACK - } else { - Color.BLACK to Color.WHITE - } - when (settings.blockMode) { - BlockMode.InvertedThemeOnly -> preferred = other - BlockMode.WhiteOnly -> preferred = Color.WHITE - BlockMode.BlackOnly -> preferred = Color.BLACK - BlockMode.Unchanged -> preferred = colour - else -> {} - } - - val colours = if (!settings.blockInverted) { - Colours( - fgP = preferred, - fgO = other, - bgP = colour, - bgO = colour, - ) - } else { - Colours( - fgP = colour, - fgO = colour, - bgP = preferred, - bgO = other, - ) - } - - val usePreferred = when (settings.blockMode) { - BlockMode.ApcaOnly -> isApca(colours, threshold) - BlockMode.WcagOnly -> isWcag(colours) - BlockMode.ApcaLightWcagDark -> if (isLight) isApca(colours, threshold) else isWcag(colours) - BlockMode.WcagLightApcaDark -> if (isLight) isWcag(colours) else isApca(colours, threshold) - BlockMode.ThemeOnly, - BlockMode.InvertedThemeOnly, - BlockMode.WhiteOnly, - BlockMode.BlackOnly, - BlockMode.Unchanged -> true - } - - if (usePreferred) { - view.setTextColor(colours.fgP) - bcol.setColor(ColorUtils.setAlphaComponent(colours.bgP, settings.alpha)) - } else { - view.setTextColor(colours.fgO) - bcol.setColor(ColorUtils.setAlphaComponent(colours.bgO, settings.alpha)) - } - } - - private fun isApca(c: Colours, threshold: Threshold): Boolean { - val cPref = abs(APCA.contrast(c.fgP, c.bgP)) - val cOth = abs(APCA.contrast(c.fgO, c.bgO)) - val thresholdValue = when (threshold) { - Threshold.Large -> settings.blockApcaThresholdLarge - Threshold.Medium -> settings.blockApcaThresholdMedium - Threshold.Small -> settings.blockApcaThresholdSmall - } - return cPref > thresholdValue || cPref > cOth - } - - private fun isWcag(c: Colours): Boolean { - val cPref = ColorUtils.calculateContrast(c.fgP, c.bgP) - val cOth = ColorUtils.calculateContrast(c.fgO, c.bgO) - return cPref > settings.blockWcagThreshold || cPref > cOth - } -} diff --git a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/Zinnia.kt b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/Zinnia.kt index f70ebde..8aefe72 100644 --- a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/Zinnia.kt +++ b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/Zinnia.kt @@ -2,32 +2,28 @@ package moe.lava.awoocord.zinnia import android.content.Context import android.graphics.Color +import android.graphics.drawable.GradientDrawable import android.view.View import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.graphics.ColorUtils import com.aliucord.annotations.AliucordPlugin import com.aliucord.entities.Plugin -import com.aliucord.patcher.after -import com.aliucord.patcher.component1 -import com.aliucord.patcher.component2 -import com.aliucord.patcher.component3 -import com.aliucord.patcher.instead +import com.aliucord.patcher.* import com.aliucord.utils.DimenUtils.dp +import com.aliucord.utils.ViewUtils.findViewById import com.aliucord.utils.accessField import com.discord.databinding.WidgetChannelMembersListItemUserBinding -import com.discord.models.member.GuildMember +import com.discord.stores.StoreStream import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListAdapter import com.discord.widgets.channels.memberlist.adapter.ChannelMembersListViewHolderMember import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage import com.discord.widgets.chat.list.entries.ChatListEntry import com.discord.widgets.chat.list.entries.MessageEntry +import kotlin.math.abs private val ChannelMembersListViewHolderMember.binding by accessField() -private val WidgetChatListAdapterItemMessage.itemName - by accessField() -private val WidgetChatListAdapterItemMessage.replyName - by accessField() data class Colours( val fgP: Int, @@ -40,6 +36,8 @@ data class Colours( class Zinnia : Plugin() { companion object { const val NAME = "RoleBlocks" } + private val localSettings = ZinniaSettings + init { settingsTab = SettingsTab(ZinniaSettings.Page::class.java, SettingsTab.Type.PAGE) } @@ -51,6 +49,93 @@ class Zinnia : Plugin() { override fun stop(context: Context) { patcher.unpatchAll() } + private fun configureOn(view: TextView, colour: Int?) { + when (localSettings.mode) { + Mode.Block -> configureBlock(view, colour ?: Color.BLACK) + Mode.RoleDot -> configureRoleDot(view, colour ?: Color.BLACK) + } + } + + private fun configureRoleDot(view: TextView, colour: Int) { } + + private fun configureBlock(view: TextView, colourP: Int) { + val isLight = StoreStream.getUserSettingsSystem().theme == "light" + var colour = colourP + val bcol = GradientDrawable() + bcol.cornerRadius = 4.dp.toFloat() + view.background = bcol + + if (colour == Color.BLACK) { + if (localSettings.blockAlsoDefault) { + colour = if (isLight && !localSettings.blockInverted) Color.WHITE else Color.BLACK + } else { + view.background = null + view.setPadding(0, 0, 0, 0) + return + } + } + view.setPadding(4.dp, 0, 4.dp, 0) + + var (preferred, other) = if (isLight) { + Color.WHITE to Color.BLACK + } else { + Color.BLACK to Color.WHITE + } + when (localSettings.blockMode) { + BlockMode.InvertedThemeOnly -> preferred = other + BlockMode.WhiteOnly -> preferred = Color.WHITE + BlockMode.BlackOnly -> preferred = Color.BLACK + else -> {} + } + + val colours = if (!localSettings.blockInverted) { + Colours( + fgP = preferred, + fgO = other, + bgP = colour, + bgO = colour, + ) + } else { + Colours( + fgP = colour, + fgO = colour, + bgP = preferred, + bgO = other, + ) + } + + val usePreferred = when (localSettings.blockMode) { + BlockMode.ApcaOnly -> isApca(colours) + BlockMode.WcagOnly -> isWcag(colours) + BlockMode.ApcaLightWcagDark -> if (isLight) isApca(colours) else isWcag(colours) + BlockMode.WcagLightApcaDark -> if (isLight) isWcag(colours) else isApca(colours) + BlockMode.ThemeOnly, + BlockMode.InvertedThemeOnly, + BlockMode.WhiteOnly, + BlockMode.BlackOnly -> true + } + + if (usePreferred) { + view.setTextColor(colours.fgP) + bcol.setColor(colours.bgP) + } else { + view.setTextColor(colours.fgO) + bcol.setColor(colours.bgO) + } + } + + private fun isApca(c: Colours): Boolean { + val cPref = abs(APCA.contrast(c.fgP, c.bgP)) + val cOth = abs(APCA.contrast(c.fgO, c.bgO)) + return cPref > localSettings.blockApcaThreshold || cPref > cOth + } + + private fun isWcag(c: Colours): Boolean { + val cPref = ColorUtils.calculateContrast(c.fgP, c.bgP) + val cOth = ColorUtils.calculateContrast(c.fgO, c.bgO) + return cPref > localSettings.blockWcagThreshold || cPref > cOth + } + private fun patchMemberList() { // Patches the method that configures the username in members list patcher.after( @@ -68,7 +153,7 @@ class Zinnia : Plugin() { } } - APCAUtil.configureOn(usernameTextView, member.color, Threshold.Medium) + configureOn(usernameTextView, member.color) } } @@ -79,28 +164,20 @@ class Zinnia : Plugin() { Int::class.javaPrimitiveType!!, ChatListEntry::class.java, ) { (_, _: Int, entry: MessageEntry) -> - itemName?.let { - APCAUtil.configureOn(it, entry.author?.color, Threshold.Large) - } - } - - patcher.instead( - "getAuthorTextColor", - GuildMember::class.java, - ) { (_, member: GuildMember?) -> - member?.color ?: Color.BLACK + val username = itemView.findViewById("chat_list_adapter_item_text_name") + ?: return@after + configureOn(username, entry.author?.color) } // Configures for reply preview username patcher.after( - "configureReplyName", - String::class.java, - Int::class.javaPrimitiveType!!, - Boolean::class.javaPrimitiveType!!, - ) { (_, _: String, colour: Int) -> - replyName?.let { - APCAUtil.configureOn(it, colour, Threshold.Small) - } + "configureReplyPreview", + MessageEntry::class.java, + ) { (_, entry: MessageEntry) -> + val referencedAuthor = entry.replyData?.messageEntry?.author + val replyUsername = itemView.findViewById("chat_list_adapter_item_text_decorator_reply_name") + ?: return@after + configureOn(replyUsername, referencedAuthor?.color) } } } diff --git a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/ZinniaSettings.kt b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/ZinniaSettings.kt index 72ca84c..9d9055d 100644 --- a/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/ZinniaSettings.kt +++ b/plugins/Zinnia/src/main/kotlin/moe/lava/awoocord/zinnia/ZinniaSettings.kt @@ -1,26 +1,13 @@ package moe.lava.awoocord.zinnia -import android.graphics.Color -import android.util.TypedValue import android.view.View import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.LinearLayout -import android.widget.SeekBar -import android.widget.TextView -import androidx.core.content.res.ResourcesCompat -import com.aliucord.Constants import com.aliucord.Utils import com.aliucord.api.SettingsAPI import com.aliucord.fragments.SettingsPage import com.aliucord.settings.delegate -import com.aliucord.utils.DimenUtils.dp -import com.aliucord.wrappers.users.globalName -import com.discord.stores.StoreStream -import com.discord.utilities.color.ColorCompat import com.discord.views.CheckedSetting -import com.lytefast.flexinput.R -import kotlin.math.roundToInt +import com.discord.views.RadioManager import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -38,7 +25,6 @@ enum class BlockMode { InvertedThemeOnly, WhiteOnly, BlackOnly, - Unchanged, } class SettingsDelegateEnum>( @@ -63,59 +49,27 @@ private inline fun T.addTo(parent: ViewGroup, block: T.() -> Unit = { parent.addView(this) } -private typealias Delegate = ReadWriteProperty - -fun basicDelegate(initial: T) = object : Delegate { - private var current = initial - override fun getValue(self: Any?, prop: KProperty<*>): T = current - override fun setValue(self: Any?, prop: KProperty<*>, value: T) { current = value } -} - -private class StateDelegate( - private val inner: Delegate, - private val update: (T) -> Unit, -) : Delegate { - override fun getValue(self: Any?, prop: KProperty<*>): T = inner.getValue(self, prop) - - override fun setValue(self: Any?, prop: KProperty<*>, value: T) { - inner.setValue(self, prop, value) - update(value) - } -} - object ZinniaSettings { private val api = SettingsAPI(Zinnia.NAME) - private var onStateUpdate = {} + var mode by api.delegateEnum(Mode.Block) - private inline fun reactive(backing: () -> Delegate): StateDelegate { - return StateDelegate(backing()) { onStateUpdate() } - } + var dotKeepNameColour by api.delegate(false) - var mode by reactive { api.delegateEnum(Mode.Block) } - - var blockAlsoDefault by reactive { api.delegate(true) } - var blockInverted by reactive { api.delegate(false) } - var blockMode by reactive { api.delegateEnum(BlockMode.ApcaLightWcagDark) } - var blockApcaThresholdLarge by reactive { api.delegate(45.0f) } - var blockApcaThresholdMedium by reactive { api.delegate(45.0f) } - var blockApcaThresholdSmall by reactive { api.delegate(45.0f) } - var blockWcagThreshold by reactive { api.delegate(4.5f) } - - private val _alpha = reactive { api.delegate("alpha", 255) } - var alpha by _alpha + var blockAlsoDefault by api.delegate(true) + var blockInverted by api.delegate(false) + var blockMode by api.delegateEnum(BlockMode.ApcaLightWcagDark) + var blockApcaThreshold by api.delegate(75.0) + var blockWcagThreshold by api.delegate(4.5) class Page : SettingsPage() { + private lateinit var manager: RadioManager + private lateinit var mRoleDot: CheckedSetting + private lateinit var mBlock: CheckedSetting + private val checks = mutableListOf() - private val _previewH = reactive { basicDelegate(0) } - private var previewH by _previewH - private val _previewS = reactive { basicDelegate(100) } - private var previewS by _previewS - private val _previewV = reactive { basicDelegate(100) } - private var previewV by _previewV - - private fun addRadio(newMode: BlockMode, text: String, subtext: String? = null): CheckedSetting { + private fun createRadio(newMode: BlockMode, text: String, subtext: String? = null): CheckedSetting { return Utils.createCheckedSetting(requireContext(), CheckedSetting.ViewType.RADIO, text, subtext).addTo(linearLayout) { isChecked = blockMode == newMode setOnCheckedListener { @@ -127,88 +81,6 @@ object ZinniaSettings { } } - private fun createLabel(text: String? = null): TextView { - return TextView(context, null, 0, R.i.UiKit_TextView).apply { - textSize = 16.0f - typeface = ResourcesCompat.getFont(context, Constants.Fonts.whitney_medium) - this.text = text - layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - bottomMargin = 4.dp - } - } - } - - private fun addSlider( - min: Int, - max: Int, - initial: Int = min, - onChange: (value: Int, commit: Boolean) -> String - ): LinearLayout { - var pendingValue = initial - return LinearLayout(requireContext(), null, 0, R.i.UiKit_Settings_Item).addTo(linearLayout) { - orientation = LinearLayout.VERTICAL - val display = createLabel(onChange(initial, false)).addTo(this) - SeekBar(context, null, 0, R.i.UiKit_SeekBar).addTo(this) { - this.max = max - min - progress = initial - setPadding(12.dp, 0, 12.dp, 0) - setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged( - seekBar: SeekBar, - progress: Int, - fromUser: Boolean, - ) { - pendingValue = min + progress - display.text = onChange(pendingValue, false) - } - - override fun onStartTrackingTouch(seekBar: SeekBar) {} - override fun onStopTrackingTouch(seekBar: SeekBar) { - onChange(pendingValue, true) - } - }) - } - } - } - - private fun addSlider(binding: Delegate, min: Int, max: Int, immediate: Boolean = false, label: (Int) -> String): LinearLayout { - var value by binding - return addSlider(min, max, value) { newValue, commit -> - @Suppress("AssignedValueIsNeverRead") // kt so dumb - if (immediate || commit) value = newValue - label(newValue) - } - } - - private fun createPreview( - label: String, - styleRes: Int, - ): TextView { - val ctx = requireContext() - val view = TextView(ctx, null, 0, styleRes).apply { - val me = StoreStream.getUsers().me - text = me.globalName ?: me.username - layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - marginStart = 16.dp - marginEnd = 16.dp - } - } - LinearLayout(ctx, null, 0, R.i.UiKit_Settings_Item).addTo(linearLayout) { - view.addTo(this) - createLabel(label).addTo(this) { - layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - bottomMargin = 0 - } - } - } - return view - } - - override fun onDestroyView() { - onStateUpdate = {} - super.onDestroyView() - } - override fun onViewBound(view: View) { super.onViewBound(view) setActionBarTitle(Zinnia.NAME) @@ -220,20 +92,61 @@ object ZinniaSettings { val roleDotSettings = mutableListOf() addHeader(ctx, "Text colour") - addRadio(BlockMode.ApcaLightWcagDark, "Automatic", "Adjusts text colour based on optimal contrast with role colour") - addRadio(BlockMode.ThemeOnly, "By theme", "Adjusts text colour based on system theme (dark/light)") - addRadio(BlockMode.InvertedThemeOnly, "By theme (inverted)", "Same as above, but inverted") - addRadio(BlockMode.WhiteOnly, "White", "Force text colour to be white") - addRadio(BlockMode.BlackOnly, "Black", "Force text colour to be black") - addRadio(BlockMode.Unchanged, "Unchanged", "Keep text colour; ideal for using with a translucent block") + createRadio(BlockMode.ApcaLightWcagDark, "Automatic", "Adjusts text colour based on role colour") + createRadio(BlockMode.ThemeOnly, "By theme", "Adjusts text colour based on theme") + createRadio(BlockMode.InvertedThemeOnly, "By theme (inverted)", "Same as above, but inverted") + createRadio(BlockMode.WhiteOnly, "White", "Force text colour to be white") + createRadio(BlockMode.BlackOnly, "Black", "Force text colour to be black") + + /* + addHeader(ctx, "Mode") + + mBlock = Utils.createCheckedSetting( + ctx, + CheckedSetting.ViewType.RADIO, + "Block mode", + "Wraps the username in a coloured block", + ).addTo(this) { + isChecked = mode == Mode.Block + setOnCheckedListener { + mode = Mode.Block + mRoleDot.isChecked = false + } + } + + mRoleDot = Utils.createCheckedSetting( + ctx, + CheckedSetting.ViewType.RADIO, + "Role dot mode", + "Adds a coloured role dot next to the username, similar to how Discord does it in their new accessibility settings", + ).addTo(this) { + isChecked = mode == Mode.RoleDot + setOnCheckedListener { + mode = Mode.RoleDot + mBlock.isChecked = false + } + } + */ addHeader(ctx, "Block Settings") + Utils.createCheckedSetting( + ctx, + CheckedSetting.ViewType.SWITCH, + "Also block up default colours", + "Blocks up usernames that have no role colour", + ).addTo(this) { + isChecked = blockAlsoDefault + setOnCheckedListener { + blockAlsoDefault = !blockAlsoDefault + } + blockSettings.add(this) + } - val invertSwitch = Utils.createCheckedSetting( + Utils.createCheckedSetting( ctx, CheckedSetting.ViewType.SWITCH, "Invert block colours", - "By default, the role colour is applied as the block background. Turning this setting on inverts this.\nHas no effect with \"Unchanged\" colour option", + "By default, the role colour is applied as the block background. Turning this setting on instead makes the block black or white, and the text stays coloured.", ).addTo(this) { isChecked = blockInverted setOnCheckedListener { @@ -241,52 +154,7 @@ object ZinniaSettings { } blockSettings.add(this) } - - addSlider(_alpha, 0, 255, true) { "Alpha: ${(it / 2.55f).roundToInt()}%" } - -// createSlider(0, 255, blockApcaThreshold.roundToInt()) { value, commit -> -// blockApcaThreshold = value.toFloat() -// "Apca Threshold: $value" -// } - - addHeader(ctx, "Preview") - val previews = mutableListOf( - Threshold.Large to createPreview("Message header username", R.i.UiKit_TextView_Large_SingleLine), - Threshold.Medium to createPreview("Channels list", R.i.UiKit_TextView).apply { - setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.d.uikit_textsize_medium)) - }, - Threshold.Small to createPreview("Message reply username", R.i.UiKit_TextView).apply { - setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.d.uikit_textsize_small)) - }, - ) - - val hsv = floatArrayOf(0f, 0f, 0f) - Color.colorToHSV(ColorCompat.getThemedColor(this, R.b.color_brand), hsv) - previewH = hsv[0].roundToInt() - previewS = (hsv[1] * 100).roundToInt() - previewV = (hsv[2] * 100).roundToInt() - addSlider(_previewH, 0, 360, true) { "Hue: $it" } - addSlider(_previewS, 0, 100, true) { "Saturation: $it%" } - addSlider(_previewV, 0, 100, true) { "Value: $it%" } - - onStateUpdate = { - previews.forEach { updatePreview(it) } - if (blockMode != BlockMode.Unchanged) { - invertSwitch.l.b().isClickable = true - invertSwitch.alpha = 1f - } else { - invertSwitch.l.b().isClickable = false - invertSwitch.alpha = 0.3f - } - } - onStateUpdate() } } - - fun updatePreview(pair: Pair) { - val (threshold, preview) = pair - val colour = Color.HSVToColor(floatArrayOf(previewH.toFloat(), previewS / 100f, previewV / 100f)) - APCAUtil.configureOn(preview, colour, threshold) - } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index f6d7bdf..3ffd693 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,10 +25,8 @@ val plugins = mapOf( "ComponentsV2Beta" to "canary/ComponentsV2", "SlashCommandsFixBeta" to "canary/SlashCommandsFix", "Bubbles" to "plugins/Crocosmia", - "Clump" to "plugins/Bocchi", "Scout" to "plugins/Scout", "RoleBlocks" to "plugins/Zinnia", - "Glance" to "plugins/Myosotis", ) include(*plugins.keys.toTypedArray())