diff --git a/BetterChannelIcons/build.gradle.kts b/BetterChannelIcons/build.gradle.kts index 67b2528c..efd4e051 100644 --- a/BetterChannelIcons/build.gradle.kts +++ b/BetterChannelIcons/build.gradle.kts @@ -1,11 +1,2 @@ -version = "1.3.7" -description = "Adds an array of new channel icons" - -aliucord.changelog.set( - """ - Improved {improved marginTop} - ====================== - - * Discord v126.18 - """.trimIndent() -) \ No newline at end of file +version = "1.3.8" +description = "Adds an array of new channel icons" \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/BetterChannelIcons.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/BetterChannelIcons.java deleted file mode 100644 index c4c5e486..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/BetterChannelIcons.java +++ /dev/null @@ -1,88 +0,0 @@ -package xyz.wingio.plugins; - -import com.google.android.material.chip.ChipGroup; -import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.*; -import android.view.*; -import android.widget.*; -import android.os.*; - -import androidx.annotation.NonNull; -import androidx.annotation.DrawableRes; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.content.res.ResourcesCompat; -import androidx.core.content.ContextCompat; -import androidx.core.widget.NestedScrollView; -import androidx.core.graphics.ColorUtils; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.*; - -import com.aliucord.utils.*; -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.api.NotificationsAPI; -import com.aliucord.entities.NotificationData; -import com.aliucord.entities.Plugin; -import com.aliucord.patcher.Hook; -import com.aliucord.annotations.AliucordPlugin; -import com.aliucord.wrappers.*; -import com.aliucord.utils.ReflectUtils; - -import com.discord.api.channel.Channel; -import com.discord.databinding.*; -import com.discord.models.guild.Guild; -import com.discord.stores.*; -import com.discord.widgets.channels.list.WidgetChannelsListAdapter; -import com.discord.widgets.channels.list.items.*; -import com.discord.widgets.home.*; -import com.discord.utilities.color.ColorCompat; - -import com.google.gson.reflect.TypeToken; - -import com.lytefast.flexinput.R; - -import java.util.*; -import java.lang.reflect.*; -import java.lang.*; -import kotlin.Unit; - -import xyz.wingio.plugins.betterchannelicons.*; - -@AliucordPlugin -public class BetterChannelIcons extends Plugin { - - public BetterChannelIcons() { - settingsTab = new SettingsTab(PluginSettings.class).withArgs(settings); - needsResources = true; - } - - public Logger logger = new Logger("BetterChannelIcons"); - private Drawable pluginIcon; - - @Override - public void start(Context context) throws Throwable { - pluginIcon = ContextCompat.getDrawable(context, R.e.ic_channel_text_white_a60_24dp); - - boolean hasConverted = settings.getBool("hasConverted", false); - if(!hasConverted){ - PluginManager.disablePlugin("BetterChannelIcons"); - Map oldIcons = settings.getObject("icons", new HashMap<>(), Utils.oldIconStoreType); - settings.setObject("icons", Utils.convertToNewFormat(oldIcons)); - settings.setBool("hasConverted", true); - logger.debug("Converted old icons to new format"); - PluginManager.enablePlugin("BetterChannelIcons"); - return; - } - - Patches patches = new Patches(patcher); - - patches.addChannelAction(); - patches.setTextIcon(resources); - patches.setVoiceIcon(resources); - patches.setToolbarIcon(resources); - } - - @Override - public void stop(Context context) { patcher.unpatchAll(); } -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/BetterChannelIcons.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/BetterChannelIcons.kt new file mode 100644 index 00000000..3df7f492 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/BetterChannelIcons.kt @@ -0,0 +1,49 @@ +package xyz.wingio.plugins + +import android.content.Context +import android.graphics.drawable.Drawable + +import androidx.core.content.ContextCompat +import com.aliucord.Logger + +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.api.SettingsAPI +import com.aliucord.entities.Plugin + +import com.lytefast.flexinput.R + +import xyz.wingio.plugins.betterchannelicons.Patches +import xyz.wingio.plugins.betterchannelicons.PluginSettings + +@AliucordPlugin +class BetterChannelIcons : Plugin() { + + private var pluginIcon: Drawable? = null + + companion object { + val logger = Logger("BetterChannelIcons") + lateinit var pluginSettings: SettingsAPI + } + + init { + settingsTab = SettingsTab(PluginSettings::class.java).withArgs(settings) + needsResources = true + } + + override fun start(context: Context) { + pluginSettings = settings + pluginIcon = ContextCompat.getDrawable(context, R.e.ic_channel_text_white_a60_24dp) + + val patches = Patches(patcher) + + patches.addChannelAction() + patches.setVoiceIcon() + patches.setTextIcon(resources) + patches.setToolbarIcon(resources) + } + + override fun stop(context: Context) { + patcher.unpatchAll() + } + +} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/AddChannelSheet.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/AddChannelSheet.java deleted file mode 100644 index 4fc01909..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/AddChannelSheet.java +++ /dev/null @@ -1,156 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.graphics.*; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RectShape; -import android.graphics.Bitmap.Config; -import android.graphics.PorterDuff.Mode; -import android.content.res.Resources; -import android.content.res.AssetManager; -import android.net.Uri; -import android.text.*; -import android.text.style.ClickableSpan; -import android.util.Base64; -import android.view.*; -import android.widget.*; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.*; -import androidx.core.content.res.ResourcesCompat; - -import xyz.wingio.plugins.BetterChannelIcons; -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.utils.DimenUtils; -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.api.SettingsAPI; -import com.aliucord.fragments.SettingsPage; -import com.aliucord.utils.ReflectUtils; -import com.aliucord.widgets.BottomSheet; -import com.discord.widgets.user.Badge; -import com.aliucord.views.TextInput; -import com.aliucord.views.Button; -import com.discord.app.AppBottomSheet; -import com.discord.app.AppFragment; -import com.discord.views.CheckedSetting; -import com.discord.utilities.color.ColorCompat; -import com.lytefast.flexinput.R; - -import kotlin.Unit; -import java.io.*; -import java.util.*; - -public class AddChannelSheet extends BottomSheet { - private SettingsPage page; - private SettingsAPI settings; - private String currentIcon; - private ImageView icon; - private Long channelId; - - public AddChannelSheet(SettingsPage page, SettingsAPI settings) { - this.page = page; - this.settings = settings; - } - - public AddChannelSheet(Long channelId, SettingsAPI settings) { - this.channelId = channelId; - this.settings = settings; - } - - @Override - public void onViewCreated(View view, Bundle bundle) { - super.onViewCreated(view, bundle); - int p = DimenUtils.dpToPx(16); - Map iconSets = settings.getObject("icons", new HashMap<>(), xyz.wingio.plugins.betterchannelicons.Utils.iconStoreType); - - setPadding(p); - Context ctx = requireContext(); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.setMargins(0, 0, 0, p); - - - LinearLayout iconLayout = new LinearLayout(ctx); - iconLayout.setOrientation(LinearLayout.HORIZONTAL); - iconLayout.setGravity(Gravity.CENTER); - iconLayout.setLayoutParams(params); - - TextInput channelName = new TextInput(ctx); - channelName.setHint("Channel name"); - LinearLayout.LayoutParams iconLParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - channelName.setLayoutParams(iconLParams); - channelName.getEditText().setText(channelId == null || channelId == 0 ? "" : "id:" + channelId); - - var resources = ctx.getResources(); - Drawable badge = ResourcesCompat.getDrawable(resources, R.e.ic_open_in_new_grey_24dp, null).mutate(); - badge.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal)); - - icon = new ImageView(ctx); - icon.setImageDrawable(badge); - var iconParams = new LinearLayout.LayoutParams(DimenUtils.dpToPx(32), DimenUtils.dpToPx(32)); - iconParams.setMargins(p / 2, 0, p, 0); - icon.setLayoutParams(iconParams); - icon.setScaleType(ImageView.ScaleType.FIT_CENTER); - icon.setOnClickListener(v -> { - new IconListSheet(page, settings, this).show(getFragmentManager(), "icon_list"); - }); - - Button add = new Button(ctx); - add.setText("Add"); - - iconLayout.addView(icon); - iconLayout.addView(channelName); - - add.setOnClickListener(v -> { - String name = channelName.getEditText().getText().toString(); - if (name.isEmpty()) { - Toast.makeText(ctx, "Please enter a channel name", Toast.LENGTH_SHORT).show(); - } else { - try { - var ic = ContextCompat.getDrawable(ctx, Utils.getResId(currentIcon, "drawable")); - var chName = name.toLowerCase(); - iconSets.remove(chName); - iconSets.put(chName, currentIcon); - settings.setObject("icons", iconSets); - Toast.makeText(ctx, "Added icon", Toast.LENGTH_SHORT).show(); - - if(page != null) page.reRender(); - dismiss(); - } catch (Throwable e) { - Logger logger = new Logger("BCI"); - logger.error("Error setting icon", e); - Toast.makeText(ctx, "Drawable not found", Toast.LENGTH_SHORT).show(); - } - } - }); - - addView(iconLayout); - addView(add); - } - - public void setCurrentIcon(String icon) { - this.currentIcon = icon; - if(this.icon != null){ - var ic = ContextCompat.getDrawable(requireContext(), Utils.getResId(icon, "drawable")).mutate(); - ic.setTint(ColorCompat.getThemedColor(requireContext(), R.b.colorInteractiveNormal)); - this.icon.setImageDrawable(ic); - } - } - - private CheckedSetting createSwitch(Context context, SettingsAPI sets, String key, String label, CharSequence subtext, boolean defaultValue, boolean reRender) { - CheckedSetting cs = Utils.createCheckedSetting(context, CheckedSetting.ViewType.SWITCH, label, subtext); - cs.setChecked(sets.getBool(key, defaultValue)); - cs.setOnCheckedListener(c -> { - sets.setBool(key, c); - }); - return cs; - } -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/AddChannelSheet.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/AddChannelSheet.kt new file mode 100644 index 00000000..778e26e2 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/AddChannelSheet.kt @@ -0,0 +1,154 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.betterchannelicons + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.Toast + +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat + +import com.aliucord.Utils.getResId +import com.aliucord.Utils.tintToTheme +import com.aliucord.api.SettingsAPI +import com.aliucord.fragments.SettingsPage +import com.aliucord.utils.DimenUtils.dp +import com.aliucord.views.Button +import com.aliucord.views.TextInput +import com.aliucord.widgets.BottomSheet + +import com.lytefast.flexinput.R + +import xyz.wingio.plugins.BetterChannelIcons + +import java.util.Locale + +class AddChannelSheet : BottomSheet { + + private var page: SettingsPage? = null + private var settings: SettingsAPI + private var currentIcon: String? = null + private var icon: ImageView? = null + private var channelId: Long? = null + + constructor(page: SettingsPage?, settings: SettingsAPI) { + this.page = page + this.settings = settings + } + + constructor(channelId: Long?, settings: SettingsAPI) { + this.channelId = channelId + this.settings = settings + } + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, bundle: Bundle?) { + super.onViewCreated(view, bundle) + val ctx = requireContext() + val resources = ctx.resources + val badge = tintToTheme(ResourcesCompat.getDrawable(resources, R.e.ic_open_in_new_grey_24dp, null)!!.mutate()) + val iconSets: MutableMap = + settings.getObject("icons", HashMap(), Utils.iconStoreType) + + setPadding(16.dp) + + val iconLayout = LinearLayout(ctx).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { setMargins(0, 0, 0, 16.dp) } + } + + val channelNameInput = TextInput(ctx).apply { + editText.setText(if (channelId == null || channelId == 0L) "" else "id:$channelId") + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + + setHint("Channel name") + } + + icon = ImageView(ctx).apply { + scaleType = ImageView.ScaleType.FIT_CENTER + layoutParams = LinearLayout.LayoutParams(32.dp, 32.dp).apply { + setMargins(8.dp, 0, 8.dp, 0) + } + + setImageDrawable(badge) + setOnClickListener { + IconListSheet( + settings = settings, + addChannelSheet = this@AddChannelSheet + ).show(parentFragmentManager, "icon_list") + } + } + + val add = Button(ctx).apply { + text = "Add" + + setOnClickListener { + val name = channelNameInput.editText.text.toString() + + if (name.isEmpty()) { + Toast.makeText(ctx, "Please enter a channel name", Toast.LENGTH_SHORT).show() + } else { + try { + val channelName = name.lowercase(Locale.getDefault()) + + // This is just done to verify if the provided icon actually exists + ContextCompat.getDrawable( + /* context = */ ctx, + /* id = */ getResId( + name = currentIcon!!, + type = "drawable" + ) + ) + + iconSets[channelName] = currentIcon + settings.setObject("icons", iconSets) + + Toast.makeText(ctx, "Added icon", Toast.LENGTH_SHORT).show() + + if (page != null) page!!.reRender() + dismiss() + } catch (e: Throwable) { + BetterChannelIcons.logger.error("Error setting icon", e) + Toast.makeText(ctx, "Drawable not found", Toast.LENGTH_SHORT).show() + } + } + } + } + + iconLayout.addView(icon) + iconLayout.addView(channelNameInput) + + addView(iconLayout) + addView(add) + } + + fun setCurrentIcon(icon: String?) { + currentIcon = icon + + if (this.icon != null) { + val ic = ContextCompat.getDrawable( + /* context = */ requireContext(), + /* id = */ getResId( + name = icon!!, + type = "drawable" + ) + )!!.mutate() + + this.icon!!.setImageDrawable(tintToTheme(ic)) + } + } + +} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Constants.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Constants.java deleted file mode 100644 index a93fcf32..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Constants.java +++ /dev/null @@ -1,58 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons; - -import com.aliucord.PluginManager; -import com.aliucord.utils.ReflectUtils; -import com.lytefast.flexinput.R; - -import java.util.*; -import java.lang.reflect.*; - -public class Constants { - public static Field[] fields = R.e.class.getDeclaredFields(); - public static final Map iconMap = new HashMap<>() {{ - put("Chat Bubble", "ic_chat_message_white_24dp"); - put("Help", "ic_help_24dp"); - put("Info", "ic_info_24dp"); - put("Role", "ic_shieldstar_24dp"); - put("Art", "ic_theme_24dp"); - put("Laughing", "ic_emoji_picker_category_people"); - put("Hand Raised/Waving", "ic_raised_hand_action_24dp"); - put("Media", "ic_flex_input_image_24dp_dark"); - put("Changelog", "ic_history_white_24dp"); - put("Logs/Channels", "ic_channels_24dp"); - put("Star", "ic_star_24dp"); - put("Link", "ic_diag_link_24dp"); - put("Microphone", "ic_mic_grey_24dp"); - put("Mic Muted", "ic_mic_muted_grey_24dp"); - put("Headset", "ic_headset_24dp"); - put("Github", "ic_account_github_white_24dp"); - put("D-Pad", "ic_games_24dp"); - put("Controller", "ic_controller_24dp"); - put("Slash Command", "ic_slash_command_24dp"); - }}; - - public static List getIcons() throws Throwable{ - List icons = new ArrayList<>(); - for(Field field : fields) { - icons.add((Integer) field.get(R.e.class)); - } - return icons; - } - - public static Map getIconNameMap() throws Throwable { - Map iconMap = new HashMap<>(); - for(Field field : fields) { - iconMap.put((Integer) field.get(R.e.class), field.getName()); - } - return iconMap; - } - - public static Map getIconMap() throws Throwable { - Map iconMap = new HashMap<>(); - for(Field field : fields) { - iconMap.put(field.getName(), (Integer) field.get(R.e.class)); - } - return iconMap; - } - -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Constants.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Constants.kt new file mode 100644 index 00000000..b4f2ff22 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Constants.kt @@ -0,0 +1,27 @@ +package xyz.wingio.plugins.betterchannelicons + +object Constants { + + val presetIconMap: Map = mapOf( + "Chat Bubble" to "ic_chat_message_white_24dp", + "Help" to "ic_help_24dp", + "Info" to "ic_info_24dp", + "Role" to "ic_shieldstar_24dp", + "Art" to "ic_theme_24dp", + "Laughing" to "ic_emoji_picker_category_people", + "Hand Raised/Waving" to "ic_raised_hand_action_24dp", + "Media" to "ic_flex_input_image_24dp_dark", + "Changelog" to "ic_history_white_24dp", + "Logs/Channels" to "ic_channels_24dp", + "Star" to "ic_star_24dp", + "Link" to "ic_diag_link_24dp", + "Microphone" to "ic_mic_grey_24dp", + "Mic Muted" to "ic_mic_muted_grey_24dp", + "Headset" to "ic_headset_24dp", + "Github" to "ic_account_github_white_24dp", + "D-Pad" to "ic_games_24dp", + "Controller" to "ic_controller_24dp", + "Slash Command" to "ic_slash_command_24dp" + ) + +} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/IconListSheet.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/IconListSheet.java deleted file mode 100644 index eb6498c1..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/IconListSheet.java +++ /dev/null @@ -1,131 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.graphics.*; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RectShape; -import android.graphics.Bitmap.Config; -import android.graphics.PorterDuff.Mode; -import android.content.res.Resources; -import android.content.res.AssetManager; -import android.net.Uri; -import android.text.*; -import android.text.style.ClickableSpan; -import android.util.Base64; -import android.view.*; -import android.widget.*; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.*; -import androidx.core.content.res.ResourcesCompat; - -import xyz.wingio.plugins.BetterChannelIcons; -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.api.SettingsAPI; -import com.aliucord.fragments.SettingsPage; -import com.aliucord.utils.DimenUtils; -import com.aliucord.utils.ReflectUtils; -import com.aliucord.widgets.BottomSheet; -import com.discord.widgets.user.Badge; -import com.aliucord.views.TextInput; -import com.aliucord.views.Button; -import com.discord.app.AppBottomSheet; -import com.discord.app.AppFragment; -import com.discord.views.CheckedSetting; -import com.discord.utilities.color.ColorCompat; -import com.lytefast.flexinput.R; - -import kotlin.Unit; -import java.io.*; -import java.util.*; - -public class IconListSheet extends BottomSheet { - private SettingsPage page; - private SettingsAPI settings; - private AddChannelSheet acs; - - public IconListSheet(SettingsPage page, SettingsAPI settings, AddChannelSheet acs) { - this.page = page; - this.settings = settings; - this.acs = acs; - } - - @Override - public void onViewCreated(View view, Bundle bundle) { - super.onViewCreated(view, bundle); - int p = DimenUtils.dpToPx(16); - boolean isAdvanced = settings.getBool("advanced_mode", false); - setPadding(p); - Context ctx = requireContext(); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.setMargins(0, 0, 0, p); - LinearLayout items = new LinearLayout(ctx); - items.setOrientation(LinearLayout.VERTICAL); - var resources = requireContext().getResources(); - - if(isAdvanced){ - TextInput drName = new TextInput(ctx); - drName.getEditText().setText(""); - Button button = new Button(ctx); - button.setText("Use Icon"); - drName.setHint("Drawable name"); - addView(drName); - addView(button); - button.setOnClickListener(v -> { - String iconName = drName.getEditText().getText().toString(); - if(!iconName.isEmpty()){ - returnAndSetIcon(iconName); - } - }); - } else { - setPadding(0); - var iconMap = Constants.iconMap; - var iconList = new ArrayList(iconMap.keySet()); - for(String i : iconList){ - var iconId = iconMap.get(i); - var icon = ContextCompat.getDrawable(ctx, Utils.getResId(iconId, "drawable")).mutate(); - icon.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal)); - TextView tv = new TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon); - tv.setText(i); - tv.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); - tv.setOnClickListener(v -> { - returnAndSetIcon(iconId); - }); - items.addView(tv); - } - } - - addView(items); - } - - private void returnAndSetIcon(String icon){ - var resources = requireContext().getResources(); - try { - ResourcesCompat.getDrawable(resources, Utils.getResId(icon, "drawable"), null); - } catch(Throwable e) { - Utils.showToast("Icon not found", false); - return; - } - acs.setCurrentIcon(icon); - dismiss(); - } - - private CheckedSetting createSwitch(Context context, SettingsAPI sets, String key, String label, CharSequence subtext, boolean defaultValue, boolean reRender) { - CheckedSetting cs = Utils.createCheckedSetting(context, CheckedSetting.ViewType.SWITCH, label, subtext); - cs.setChecked(sets.getBool(key, defaultValue)); - cs.setOnCheckedListener(c -> { - sets.setBool(key, c); - }); - return cs; - } -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/IconListSheet.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/IconListSheet.kt new file mode 100644 index 00000000..0b212527 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/IconListSheet.kt @@ -0,0 +1,99 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.betterchannelicons + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView + +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat + +import com.aliucord.Utils.getResId +import com.aliucord.Utils.showToast +import com.aliucord.Utils.tintToTheme +import com.aliucord.api.SettingsAPI +import com.aliucord.fragments.SettingsPage +import com.aliucord.utils.DimenUtils.dp +import com.aliucord.views.Button +import com.aliucord.views.TextInput +import com.aliucord.widgets.BottomSheet + +import com.lytefast.flexinput.R + +class IconListSheet( + private val settings: SettingsAPI, + private val addChannelSheet: AddChannelSheet +) : BottomSheet() { + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, bundle: Bundle?) { + super.onViewCreated(view, bundle) + val isAdvanced = settings.getBool("advanced_mode", false) + val ctx = requireContext() + + setPadding(16.dp) + + val items = LinearLayout(ctx).apply { + orientation = LinearLayout.VERTICAL + } + + if (isAdvanced) { + val drawableNameInput = TextInput(ctx).apply { + editText.setText("") + setHint("Drawable name") + setPadding(16.dp) + this@IconListSheet.addView(this) + } + + Button(ctx).apply { + text = "Use Icon" + setOnClickListener { + val iconName = drawableNameInput.editText.text.toString() + if (iconName.isNotEmpty()) { + returnAndSetIcon(iconName) + } + } + this@IconListSheet.addView(this) + } + } else { + setPadding(0) + + for ((iconName, iconId) in Constants.presetIconMap) { + val icon = tintToTheme(ContextCompat.getDrawable( + /* context = */ ctx, + /* id = */ getResId( + name = iconId, + type = "drawable" + ) + )!!.mutate()) + + TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon).apply { + text = iconName + setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) + setOnClickListener { returnAndSetIcon(iconId) } + items.addView(this) + } + } + } + + addView(items) + } + + private fun returnAndSetIcon(icon: String) { + val resources = requireContext().resources + + try { + ResourcesCompat.getDrawable(resources, getResId(icon, "drawable"), null) + } catch (e: Throwable) { + showToast("Icon not found", false) + return + } + + addChannelSheet.setCurrentIcon(icon) + dismiss() + } + +} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Patches.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Patches.java deleted file mode 100644 index bd940dc6..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Patches.java +++ /dev/null @@ -1,111 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.*; -import android.graphics.*; -import android.view.*; -import android.widget.*; - -import androidx.core.content.res.ResourcesCompat; -import androidx.core.content.ContextCompat; - -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.api.*; -import com.aliucord.patcher.*; -import com.aliucord.utils.*; -import com.aliucord.wrappers.*; - -import com.discord.api.channel.Channel; -import com.discord.databinding.*; -import com.discord.models.guild.Guild; -import com.discord.stores.*; -import com.discord.utilities.color.ColorCompat; -import com.discord.utilities.permissions.PermissionUtils; -import com.discord.widgets.channels.list.*; -import com.discord.widgets.channels.list.items.*; -import com.discord.widgets.home.*; -import com.lytefast.flexinput.R; - -import java.util.*; - -public class Patches { - private PatcherAPI patcher; - private static Logger logger = new Logger("BetterChannelIcons"); - private static SettingsAPI settings = PluginManager.plugins.get("BetterChannelIcons").settings; - - public Patches(PatcherAPI patcher) { - this.patcher = patcher; - } - - private boolean isSHCEnabled(){ - return PluginManager.plugins.get("ShowHiddenChannels") != null && PluginManager.isPluginEnabled("ShowHiddenChannels"); - } - - public void addChannelAction(){ - patcher.patch(WidgetChannelsListItemChannelActions.class, "configureUI", new Class[] { WidgetChannelsListItemChannelActions.Model.class }, new Hook(callFrame -> { - WidgetChannelsListItemChannelActions.Model model = (WidgetChannelsListItemChannelActions.Model) callFrame.args[0]; - WidgetChannelsListItemChannelActions _this = (WidgetChannelsListItemChannelActions) callFrame.thisObject; - try { - WidgetChannelsListItemActionsBinding binding = (WidgetChannelsListItemActionsBinding) ReflectUtils.invokeMethod(_this, "getBinding"); - ViewGroup root = (ViewGroup) ((ViewGroup) binding.getRoot()).getChildAt(0); - TextView edit = new TextView(root.getContext(), null, 0, R.i.UiKit_Settings_Item_Icon); - Drawable editIcn = ContextCompat.getDrawable(root.getContext(), R.e.ic_edit_24dp).mutate(); - editIcn.setTint(ColorCompat.getThemedColor(root.getContext(), R.b.colorInteractiveNormal)); - edit.setText("Change Icon"); - edit.setCompoundDrawablesWithIntrinsicBounds(editIcn, null, null, null); - edit.setOnClickListener(v -> { - new AddChannelSheet(ChannelWrapper.getId(model.getChannel()), settings).show(_this.getParentFragmentManager(), "Edit Channel Icon"); - }); - if(isSHCEnabled() && !PermissionUtils.INSTANCE.hasAccess(model.getChannel(), model.getPermissions())) { root.addView(edit); } else { root.addView(edit, 2); } - } catch (Throwable e) {logger.error("Error configuring channel actions", e);} - })); - } - - public void setToolbarIcon(Resources resources) { - patcher.patch(WidgetHomeHeaderManager.class, "configure", new Class[]{ WidgetHome.class, WidgetHomeModel.class, WidgetHomeBinding.class }, new Hook(callFrame -> { - try { - if(!settings.getBool("setToolbarIcon", true)) return; - WidgetHomeBinding binding = (WidgetHomeBinding) callFrame.args[2]; WidgetHomeModel model = (WidgetHomeModel) callFrame.args[1]; WidgetHome widget = (WidgetHome) callFrame.args[0]; ChannelWrapper channel = new ChannelWrapper(model.getChannel()); var root = widget.getActionBarTitleLayout().k.getRoot(); - ImageView channelIcon = (ImageView) root.findViewById(com.aliucord.Utils.getResId("toolbar_icon", "id")); - if(channel.isGuild()) {Guild guild = StoreStream.getGuilds().getGuilds().get(channel.getGuildId());if(guild.getRulesChannelId() != null){if(guild.getRulesChannelId() == channel.getId()) { channelIcon.setImageDrawable(Utils.themeDrawable(widget.getContext(), ResourcesCompat.getDrawable(resources, resources.getIdentifier("ic_rules_24dp", "drawable", "xyz.wingio.plugins"), null)) ); };}} - if(channel.getId() == 811275162715553823L || channel.getId() == 845784407846813696L){ channelIcon.setImageDrawable(Utils.themeDrawable(widget.getContext(), ResourcesCompat.getDrawable(resources, resources.getIdentifier("ic_plugin_24dp", "drawable", "xyz.wingio.plugins"), null))); } - - var icon = Utils.getChannelIcon(channel); - if (icon != null) channelIcon.setImageDrawable(Utils.themeDrawable(widget.getContext(), ContextCompat.getDrawable(widget.getContext(), icon))); - } catch (Throwable e) {logger.error("Error setting channel icon in title bar", e);} - })); - } - - public void setVoiceIcon(Resources resources) { - patcher.patch(WidgetChannelsListAdapter.ItemChannelVoice.class, "onConfigure", new Class[]{int.class, ChannelListItem.class}, new Hook(callFrame -> { - try { - WidgetChannelsListAdapter.ItemChannelVoice _this = (WidgetChannelsListAdapter.ItemChannelVoice) callFrame.thisObject; - ChannelListItem channelListItem = (ChannelListItem) callFrame.args[1]; - ChannelListItemVoiceChannel channelItem = (ChannelListItemVoiceChannel) channelListItem; - Channel apiChannel = channelItem.getChannel(); - ChannelWrapper channel = new ChannelWrapper(apiChannel); - var icon = Utils.getChannelIcon(channel); - if(icon != null) { - WidgetChannelsListItemChannelVoiceBinding binding = (WidgetChannelsListItemChannelVoiceBinding) ReflectUtils.getField(_this, "binding"); - ((ImageView) binding.getRoot().findViewById(com.aliucord.Utils.getResId("channels_item_voice_channel_speaker", "id"))).setImageResource(icon); - } - } catch (Throwable e) {logger.error("Error setting channel icon", e);} - })); - } - - public void setTextIcon(Resources resources) { - patcher.patch(WidgetChannelsListAdapter.ItemChannelText.class, "onConfigure", new Class[]{int.class, ChannelListItem.class}, new Hook(callFrame -> { - try { - WidgetChannelsListAdapter.ItemChannelText _this = (WidgetChannelsListAdapter.ItemChannelText) callFrame.thisObject; ChannelListItem channelListItem = (ChannelListItem) callFrame.args[1]; ChannelListItemTextChannel channelItem = (ChannelListItemTextChannel) channelListItem; Channel apiChannel = channelItem.getChannel(); ChannelWrapper channel = new ChannelWrapper(apiChannel); - WidgetChannelsListItemChannelBinding binding = (WidgetChannelsListItemChannelBinding) ReflectUtils.getField(_this, "binding"); - ImageView channelIcon = (ImageView) binding.getRoot().findViewById(com.aliucord.Utils.getResId("channels_item_channel_hash", "id")); - if(channel.isGuild()) {Guild guild = StoreStream.getGuilds().getGuilds().get(channel.getGuildId());if(guild.getRulesChannelId() != null){if(guild.getRulesChannelId() == channel.getId()) {channelIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, resources.getIdentifier("ic_rules_24dp", "drawable", "xyz.wingio.plugins"), null));};}} - if(channel.getId() == 811275162715553823L || channel.getId() == 845784407846813696L){channelIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, resources.getIdentifier("ic_plugin_24dp", "drawable", "xyz.wingio.plugins"), null));} - - if(Utils.getChannelIcon(channel) != null) channelIcon.setImageResource(Utils.getChannelIcon(channel)); - } catch (Throwable e) {logger.error("Error setting channel icon", e);} - })); - } -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Patches.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Patches.kt new file mode 100644 index 00000000..a596cb35 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Patches.kt @@ -0,0 +1,226 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.betterchannelicons + +import android.annotation.SuppressLint +import android.content.res.Resources +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView + +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat + +import com.aliucord.PluginManager +import com.aliucord.Utils.getResId +import com.aliucord.Utils.tintToTheme +import com.aliucord.api.PatcherAPI +import com.aliucord.patcher.* +import com.aliucord.utils.ReflectUtils +import com.aliucord.wrappers.ChannelWrapper + +import com.discord.databinding.WidgetChannelsListItemActionsBinding +import com.discord.databinding.WidgetChannelsListItemChannelBinding +import com.discord.databinding.WidgetChannelsListItemChannelVoiceBinding +import com.discord.databinding.WidgetHomeBinding +import com.discord.stores.StoreStream +import com.discord.utilities.permissions.PermissionUtils +import com.discord.widgets.channels.list.WidgetChannelsListAdapter.ItemChannelText +import com.discord.widgets.channels.list.WidgetChannelsListAdapter.ItemChannelVoice +import com.discord.widgets.channels.list.WidgetChannelsListItemChannelActions +import com.discord.widgets.channels.list.items.ChannelListItem +import com.discord.widgets.channels.list.items.ChannelListItemTextChannel +import com.discord.widgets.channels.list.items.ChannelListItemVoiceChannel +import com.discord.widgets.home.WidgetHome +import com.discord.widgets.home.WidgetHomeHeaderManager +import com.discord.widgets.home.WidgetHomeModel + +import com.lytefast.flexinput.R +import xyz.wingio.plugins.BetterChannelIcons + +class Patches(private val patcher: PatcherAPI) { + + private val isSHCEnabled: Boolean + get() = PluginManager.plugins["ShowHiddenChannels"] != null && + PluginManager.isPluginEnabled("ShowHiddenChannels") + + @SuppressLint("SetTextI18n") + fun addChannelAction() { + patcher.after("configureUI", WidgetChannelsListItemChannelActions.Model::class.java) { (_, model: WidgetChannelsListItemChannelActions.Model) -> + try { + val binding = ReflectUtils.invokeMethod(this,"getBinding") as WidgetChannelsListItemActionsBinding? + val root = (binding!!.root as ViewGroup).getChildAt(0) as ViewGroup + + val editIcn = tintToTheme(ContextCompat.getDrawable(root.context, R.e.ic_edit_24dp)!!.mutate()) + + val edit = TextView(root.context, null, 0, R.i.UiKit_Settings_Item_Icon).apply { + text = "Change Icon" + setCompoundDrawablesWithIntrinsicBounds(editIcn, null, null, null) + setOnClickListener { + AddChannelSheet( + ChannelWrapper(model.channel).id, + BetterChannelIcons.pluginSettings + ).show(this@after.parentFragmentManager, "Edit Channel Icon") + } + } + + if (isSHCEnabled && !PermissionUtils.INSTANCE.hasAccess(model.channel, model.permissions)) { + root.addView(edit) + } else { + root.addView(edit, 2) + } + } catch (e: Throwable) { + BetterChannelIcons.logger.error("Error configuring channel actions", e) + } + } + } + + fun setToolbarIcon(resources: Resources) { + patcher.after("configure", WidgetHome::class.java, WidgetHomeModel::class.java, WidgetHomeBinding::class.java) { (_, widget: WidgetHome, model: WidgetHomeModel, _: WidgetHomeBinding) -> + try { + if (!BetterChannelIcons.pluginSettings.getBool("setToolbarIcon", true)) return@after + if (model.channel == null) return@after + + val channel = ChannelWrapper(model.channel) + + val root = widget.actionBarTitleLayout.k.root + val channelIcon = root.findViewById(getResId("toolbar_icon", "id")) + + if (channel.isGuild()) { + val guild = StoreStream.getGuilds().guilds[channel.guildId] + + if (guild!!.rulesChannelId != null && guild.rulesChannelId == channel.id) { + channelIcon.setImageDrawable( + tintToTheme( + drawable = ResourcesCompat.getDrawable( + /* res = */ resources, + /* id = */ resources.getIdentifier( + /* name = */ "ic_rules_24dp", + /* defType = */ "drawable", + /* defPackage = */ "xyz.wingio.plugins" + ), + /* theme = */ null + ) + ) + ) + } + } + + if (channel.id == 811275162715553823L /* #plugins-list */ || channel.id == 845784407846813696L /* #new-plugins */) { + channelIcon.setImageDrawable( + tintToTheme( + drawable = ResourcesCompat.getDrawable( + /* res = */ resources, + /* id = */ resources.getIdentifier( + /* name = */ "ic_plugin_24dp", + /* defType = */ "drawable", + /* defPackage = */ "xyz.wingio.plugins" + ), + /* theme = */ null + ) + ) + ) + } + + val icon = Utils.getChannelIcon(channel) + + if (icon != null) channelIcon.setImageDrawable( + tintToTheme( + drawable = ContextCompat.getDrawable( + /* context = */ widget.requireContext(), + /* id = */ icon + ) + ) + ) + } catch (e: Throwable) { + BetterChannelIcons.logger.error("Error setting channel icon in title bar", e) + } + } + } + + fun setVoiceIcon() { + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { (_, _: Int, channelListItem: ChannelListItem) -> + try { + (channelListItem as? ChannelListItemVoiceChannel)?.let { channelItem -> + val channel = ChannelWrapper(channelItem.channel) + val icon = Utils.getChannelIcon(channel) + + if (icon != null) { + val binding = ReflectUtils.getField( + /* instance = */ this, + /* fieldName = */ "binding" + ) as WidgetChannelsListItemChannelVoiceBinding? + + binding!!.root.findViewById( + getResId( + name = "channels_item_voice_channel_speaker", + type = "id" + ) + ).setImageResource(icon) + } + } + } catch (e: Throwable) { + BetterChannelIcons.logger.error("Error setting channel icon", e) + } + } + } + + fun setTextIcon(resources: Resources) { + patcher.after("onConfigure", Int::class.java, ChannelListItem::class.java) { (_, _: Int, channelListItem: ChannelListItem) -> + try { + (channelListItem as? ChannelListItemTextChannel)?.let { channelItem -> + val channel = ChannelWrapper(channelItem.channel) + + val binding = ReflectUtils.getField( + /* instance = */ this, + /* fieldName = */ "binding" + ) as WidgetChannelsListItemChannelBinding? + + val channelIcon = binding!!.root.findViewById( + getResId( + name = "channels_item_channel_hash", + type = "id" + ) + ) + + if (channel.isGuild()) { + val guild = StoreStream.getGuilds().guilds[channel.guildId] + + if (guild!!.rulesChannelId != null && guild.rulesChannelId == channel.id) { + channelIcon.setImageDrawable( + ResourcesCompat.getDrawable( + /* res = */ resources, + /* id = */ resources.getIdentifier( + /* name = */ "ic_rules_24dp", + /* defType = */ "drawable", + /* defPackage = */ "xyz.wingio.plugins" + ), + /* theme = */ null + ) + ) + } + } + + if (channel.id == 811275162715553823L /* #plugins-list */ || channel.id == 845784407846813696L /* #new-plugins */) { + channelIcon.setImageDrawable( + ResourcesCompat.getDrawable( + /* res = */ resources, + /* id = */ resources.getIdentifier( + /* name = */ "ic_plugin_24dp", + /* defType = */ "drawable", + /* defPackage = */ "xyz.wingio.plugins" + ), + /* theme = */ null + ) + ) + } + + Utils.getChannelIcon(channel)?.let { channelIcon.setImageResource(it) } + } + } catch (e: Throwable) { + BetterChannelIcons.logger.error("Error setting channel icon", e) + } + } + } + +} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/PluginSettings.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/PluginSettings.java deleted file mode 100644 index a5f6ec75..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/PluginSettings.java +++ /dev/null @@ -1,77 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons; - -import android.content.Context; -import android.view.*; -import android.widget.*; - -import androidx.recyclerview.widget.*; -import androidx.core.content.ContextCompat; - -import xyz.wingio.plugins.BetterChannelIcons; -import xyz.wingio.plugins.betterchannelicons.recycler.*; -import xyz.wingio.plugins.betterchannelicons.*; - -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.api.SettingsAPI; -import com.aliucord.fragments.SettingsPage; -import com.aliucord.views.Button; -import com.aliucord.views.ToolbarButton; - -import com.lytefast.flexinput.R; - -import java.util.*; - -public class PluginSettings extends SettingsPage { - private SettingsAPI settings; - - public PluginSettings(SettingsAPI settings){ - this.settings = settings; - } - - private final int settingsId = View.generateViewId(); - - @Override - @SuppressWarnings("ResultOfMethodCallIgnored") - public void onViewBound(View view) { - super.onViewBound(view); - Context ctx = view.getContext(); - LinearLayout layout = getLinearLayout(); - setActionBarTitle("BetterChannelIcons"); - - RecyclerView recyclerView = new RecyclerView(ctx); - recyclerView.setLayoutManager(new LinearLayoutManager(ctx)); - Map icons = settings.getObject("icons", new HashMap<>(), xyz.wingio.plugins.betterchannelicons.Utils.iconStoreType); - - - recyclerView.setAdapter(new IconListAdapter(this, icons)); - - Button newIcon = new Button(ctx); - newIcon.setText("Add Icon"); - newIcon.setOnClickListener(v -> { - new AddChannelSheet(this, settings).show(getFragmentManager(), "add_channel_sheet"); - }); - - layout.addView(newIcon); - layout.addView(recyclerView); - - LinearLayout toolbarButtons = new LinearLayout(ctx); - toolbarButtons.setOrientation(LinearLayout.HORIZONTAL); - toolbarButtons.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - LinearLayout.LayoutParams marginEndParams = new LinearLayout.LayoutParams(Toolbar.LayoutParams.WRAP_CONTENT, Toolbar.LayoutParams.WRAP_CONTENT); - toolbarButtons.setHorizontalGravity(Gravity.END); - marginEndParams.setMarginEnd(DimenUtils.getDefaultPadding()); - ToolbarButton settingsBtn = new ToolbarButton(ctx); - settingsBtn.setLayoutParams(marginEndParams); - settingsBtn.setImageDrawable(ContextCompat.getDrawable(ctx, R.e.ic_guild_settings_24dp)); - toolbarButtons.setId(settingsId); - toolbarButtons.addView(settingsBtn); - - settingsBtn.setOnClickListener(e -> { - new SettingsSheet(this, settings).show(getParentFragmentManager(), "Settings"); - }); - ViewGroup toolbar = (ViewGroup) getHeaderBar(); - if(toolbar.findViewById(settingsId) == null) toolbar.addView(toolbarButtons); - } -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/PluginSettings.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/PluginSettings.kt new file mode 100644 index 00000000..4b1f0fc3 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/PluginSettings.kt @@ -0,0 +1,86 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.betterchannelicons + +import android.annotation.SuppressLint +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.Toolbar + +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +import com.aliucord.api.SettingsAPI +import com.aliucord.fragments.SettingsPage +import com.aliucord.utils.DimenUtils.defaultPadding +import com.aliucord.views.Button +import com.aliucord.views.ToolbarButton + +import com.lytefast.flexinput.R + +import xyz.wingio.plugins.betterchannelicons.recycler.IconListAdapter + +class PluginSettings(private val settings: SettingsAPI) : SettingsPage() { + + private val settingsId = View.generateViewId() + + @SuppressLint("SetTextI18n") + override fun onViewBound(view: View) { + super.onViewBound(view) + val ctx = view.context + val layout = linearLayout + val icons: MutableMap = settings.getObject("icons", HashMap(), Utils.iconStoreType) + + setActionBarTitle("BetterChannelIcons") + + Button(ctx).apply { + text = "Add Icon" + + setOnClickListener { + AddChannelSheet(this@PluginSettings, settings).show( + parentFragmentManager, "add_channel_sheet" + ) + } + layout.addView(this) + } + + RecyclerView(ctx).apply { + layoutManager = LinearLayoutManager(ctx) + adapter = IconListAdapter(this@PluginSettings, icons) + + layout.addView(this) + } + + val toolbarButtons = LinearLayout(ctx).apply { + id = settingsId + orientation = LinearLayout.HORIZONTAL + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + + setHorizontalGravity(Gravity.END) + + addView( + ToolbarButton(ctx).apply { + layoutParams = LinearLayout.LayoutParams( + Toolbar.LayoutParams.WRAP_CONTENT, + Toolbar.LayoutParams.WRAP_CONTENT + ).apply { marginEnd = defaultPadding } + + setImageDrawable(ContextCompat.getDrawable(ctx, R.e.ic_guild_settings_24dp)) + setOnClickListener { + SettingsSheet(settings).show(parentFragmentManager, "Settings") + } + } + ) + } + + val toolbar = headerBar as ViewGroup + if (toolbar.findViewById(settingsId) == null) toolbar.addView(toolbarButtons) + } + +} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/SettingsSheet.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/SettingsSheet.java deleted file mode 100644 index 0f0165bc..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/SettingsSheet.java +++ /dev/null @@ -1,78 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.graphics.*; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RectShape; -import android.graphics.Bitmap.Config; -import android.graphics.PorterDuff.Mode; -import android.content.res.Resources; -import android.content.res.AssetManager; -import android.net.Uri; -import android.text.*; -import android.text.style.ClickableSpan; -import android.util.Base64; -import android.view.*; -import android.widget.*; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.*; -import androidx.core.content.res.ResourcesCompat; - -import xyz.wingio.plugins.BetterChannelIcons; -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.api.SettingsAPI; -import com.aliucord.fragments.SettingsPage; -import com.aliucord.utils.ReflectUtils; -import com.aliucord.widgets.BottomSheet; -import com.discord.widgets.user.Badge; -import com.aliucord.views.TextInput; -import com.aliucord.views.Button; -import com.discord.app.AppBottomSheet; -import com.discord.app.AppFragment; -import com.discord.views.CheckedSetting; -import com.discord.utilities.color.ColorCompat; -import com.lytefast.flexinput.R; - -import kotlin.Unit; -import java.io.*; -import java.util.*; - -public class SettingsSheet extends BottomSheet { - private SettingsPage page; - private SettingsAPI settings; - private Integer currentIcon; - - public SettingsSheet(SettingsPage page, SettingsAPI settings) { - this.page = page; - this.settings = settings; - } - - @Override - public void onViewCreated(View view, Bundle bundle) { - super.onViewCreated(view, bundle); - Context ctx = requireContext(); - - addView(createSwitch(ctx, settings, "advanced_mode", "Advanced Mode", "Allows you to set whatever drawable you want for a channel icon", false, false)); - addView(createSwitch(ctx, settings, "setToolbarIcon", "Set Toolbar Icon", "Change the channel icon in the toolbar", true, false)); - } - - private CheckedSetting createSwitch(Context context, SettingsAPI sets, String key, String label, CharSequence subtext, boolean defaultValue, boolean reRender) { - CheckedSetting cs = Utils.createCheckedSetting(context, CheckedSetting.ViewType.SWITCH, label, subtext); - cs.setChecked(sets.getBool(key, defaultValue)); - cs.setOnCheckedListener(c -> { - sets.setBool(key, c); - }); - return cs; - } -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/SettingsSheet.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/SettingsSheet.kt new file mode 100644 index 00000000..b8adb799 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/SettingsSheet.kt @@ -0,0 +1,58 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.betterchannelicons + +import android.content.Context +import android.os.Bundle +import android.view.View + +import com.aliucord.Utils.createCheckedSetting +import com.aliucord.api.SettingsAPI +import com.aliucord.widgets.BottomSheet + +import com.discord.views.CheckedSetting + +class SettingsSheet(private val settings: SettingsAPI) : BottomSheet() { + + override fun onViewCreated(view: View, bundle: Bundle?) { + super.onViewCreated(view, bundle) + val ctx = requireContext() + + addView( + /* view = */ createSwitch( + context = ctx, + sets = settings, + key = "advanced_mode", + label = "Advanced Mode", + subtext = "Allows you to set whatever drawable you want for a channel icon", + defaultValue = false + ) + ) + + addView( + /* view = */ createSwitch( + context = ctx, + sets = settings, + key = "setToolbarIcon", + label = "Set Toolbar Icon", + subtext = "Change the channel icon in the toolbar", + defaultValue = true + ) + ) + } + + private fun createSwitch( + context: Context, + sets: SettingsAPI, + key: String, + label: String, + subtext: CharSequence, + defaultValue: Boolean + ): CheckedSetting { + val cs = createCheckedSetting(context, CheckedSetting.ViewType.SWITCH, label, subtext) + cs.isChecked = sets.getBool(key, defaultValue) + cs.setOnCheckedListener { c: Boolean? -> sets.setBool(key, c!!) } + return cs + } + +} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Utils.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Utils.java deleted file mode 100644 index 3432c955..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Utils.java +++ /dev/null @@ -1,125 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons; - -import android.content.Context; -import android.graphics.drawable.*; -import android.graphics.*; -import android.view.*; -import android.widget.*; - -import androidx.core.graphics.ColorUtils; -import androidx.core.content.ContextCompat; - -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.utils.*; -import com.aliucord.api.*; -import com.aliucord.patcher.*; -import com.aliucord.wrappers.*; - -import com.discord.api.channel.Channel; -import com.discord.databinding.*; -import com.discord.utilities.color.ColorCompat; -import com.discord.utilities.permissions.PermissionUtils; -import com.discord.widgets.channels.list.*; -import com.lytefast.flexinput.R; - -import com.google.gson.reflect.TypeToken; - -import java.lang.reflect.*; -import java.util.*; - -public class Utils { - public static final Type iconStoreType = TypeToken.getParameterized(HashMap.class, String.class, String.class).getType(); - public static final Type oldIconStoreType = TypeToken.getParameterized(HashMap.class, String.class, Integer.class).getType(); - private static SettingsAPI settings = PluginManager.plugins.get("BetterChannelIcons").settings; - - public static Drawable themeDrawable(Context ctx, Drawable drawable){ - drawable = drawable.mutate(); - drawable.setTint(ColorUtils.setAlphaComponent(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal), 153)); - return drawable; - } - - public static Integer getChannelIcon(ChannelWrapper channel) throws Throwable { - if(channel == null || channel.getName() == null) return null; - var name = channel.getName().toLowerCase(); - Map icons = settings.getObject("icons", new HashMap<>(), iconStoreType); - if(channel.getId() != 0 && icons.containsKey("id:" + channel.getId())) return com.aliucord.Utils.getResId(icons.get("id:" + channel.getId()), "drawable"); - if(icons.containsKey(name)) return com.aliucord.Utils.getResId(icons.get(name), "drawable"); - if(name.endsWith("-logs") || name.endsWith("-log")) return R.e.ic_channels_24dp; - if(name.endsWith("-support") || name.endsWith("-help")) return R.e.ic_help_24dp; - if(channel.getId() == 824357609778708580L) return R.e.ic_theme_24dp; - if(channel.getType() == Channel.GUILD_VOICE) { - if(name.startsWith("discord.gg/") || name.startsWith(".gg/") || name.startsWith("gg/") || name.startsWith("dsc.gg/")) return R.e.ic_diag_link_24dp; - if(name.startsWith("member count") || name.startsWith("members") || name.startsWith("member count")) return R.e.ic_people_white_24dp; - return voiceChannelIcons.get(name); - } - return channelIcons.get(name); - } - - public static Map channelIcons = new HashMap() {{ - put("faq", R.e.ic_help_24dp); - put("help", R.e.ic_help_24dp); - put("support", R.e.ic_help_24dp); - put("info", R.e.ic_info_24dp); - put("roles", R.e.ic_shieldstar_24dp); - put("role-info", R.e.ic_shieldstar_24dp); - put("offtopic", R.e.ic_chat_message_white_24dp); - put("off-topic", R.e.ic_chat_message_white_24dp); - put("general", R.e.ic_chat_message_white_24dp); - put("general-chat", R.e.ic_chat_message_white_24dp); - put("general-talk", R.e.ic_chat_message_white_24dp); - put("talk", R.e.ic_chat_message_white_24dp); - put("chat", R.e.ic_chat_message_white_24dp); - put("art", R.e.ic_theme_24dp); - put("fanart", R.e.ic_theme_24dp); - put("fan-art", R.e.ic_theme_24dp); - put("bot", R.e.ic_slash_command_24dp); - put("bots", R.e.ic_slash_command_24dp); - put("bot-spam", R.e.ic_slash_command_24dp); - put("bot-commands", R.e.ic_slash_command_24dp); - put("commands", R.e.ic_slash_command_24dp); - put("memes", R.e.ic_emoji_picker_category_people); - put("meme", R.e.ic_emoji_picker_category_people); - put("meme-chat", R.e.ic_emoji_picker_category_people); - put("shitpost", R.e.ic_emoji_picker_category_people); - put("introductions", R.e.ic_raised_hand_action_24dp); - put("introduce-yourself", R.e.ic_raised_hand_action_24dp); - put("welcome", R.e.ic_raised_hand_action_24dp); - put("welcomes", R.e.ic_raised_hand_action_24dp); - put("intros", R.e.ic_raised_hand_action_24dp); - put("media", R.e.ic_flex_input_image_24dp_dark); - put("changes", R.e.ic_history_white_24dp); - put("changelog", R.e.ic_history_white_24dp); - put("logs", R.e.ic_channels_24dp); - put("modlogs", R.e.ic_channels_24dp); - put("starboard", R.e.ic_star_24dp); - put("resources", R.e.ic_diag_link_24dp); - put("links", R.e.ic_diag_link_24dp); - put("socials", R.e.ic_diag_link_24dp); - put("vc", R.e.ic_mic_grey_24dp); - put("muted", R.e.ic_mic_grey_24dp); - put("vc-chat", R.e.ic_mic_grey_24dp); - put("voice-chat", R.e.ic_mic_grey_24dp); - put("no-mic", R.e.ic_mic_grey_24dp); - put("music", R.e.ic_headset_24dp); - put("github", R.e.ic_account_github_white_24dp); - put("github-commits", R.e.ic_account_github_white_24dp); - put("github-notifications", R.e.ic_account_github_white_24dp); - }}; - - public static Map voiceChannelIcons = new HashMap() {{ - put("music", R.e.ic_headset_24dp); - }}; - - public static Map convertToNewFormat(Map icons) throws Throwable{ - Map newIcons = new HashMap<>(); - Map iconNameMap = Constants.getIconNameMap(); - List keys = new ArrayList<>(icons.keySet()); - for(String key : keys){ - Integer iconIndex = icons.get(key); - Integer icon = Constants.getIcons().get(iconIndex); - newIcons.put(key, iconNameMap.get(icon)); - } - return newIcons; - } -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Utils.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Utils.kt new file mode 100644 index 00000000..fdbb4f3d --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/Utils.kt @@ -0,0 +1,108 @@ +package xyz.wingio.plugins.betterchannelicons + +import com.aliucord.PluginManager +import com.aliucord.Utils.getResId +import com.aliucord.wrappers.ChannelWrapper + +import com.discord.api.channel.Channel + +import com.google.gson.reflect.TypeToken + +import com.lytefast.flexinput.R + +import java.lang.reflect.Type + +object Utils { + + @JvmField + val iconStoreType: Type = TypeToken.getParameterized( + HashMap::class.java, String::class.java, String::class.java // HashMap + ).getType() + + private val settings = PluginManager.plugins["BetterChannelIcons"]!!.settings + + fun getChannelIcon(channel: ChannelWrapper?): Int? { + if (channel?.name == null) return null + + val name = channel.name.lowercase() + val icons: Map = settings.getObject("icons", mapOf(), iconStoreType) + + // Icons chosen by user + + // Get icon specific to individual channel, rather than any with a given name + if (channel.id != 0L && icons.containsKey("id:" + channel.id)) return getResId( + icons["id:" + channel.id]!!, + "drawable" + ) + if (icons.containsKey(name)) return getResId(icons[name]!!, "drawable") // Exact channel name match + + // Default icons + + if (name.endsWith("-logs") || name.endsWith("-log")) return R.e.ic_channels_24dp // Log-esque icon for log channels + if (name.endsWith("-support") || name.endsWith("-help")) return R.e.ic_help_24dp // Question mark in a circle for help and support channels + if (channel.id == 824357609778708580L) return R.e.ic_theme_24dp // Special icon for #themes channel in the Aliucord server + + if (channel.type == Channel.GUILD_VOICE) { + if (listOf("discord.gg/", ".gg/", "gg/", "dsc.gg/").any { name.startsWith(it) }) return R.e.ic_diag_link_24dp // Some servers have a voice channel that they use to display the invite + return if (listOf("member count", "members").any { name.startsWith(it) }) R.e.ic_people_white_24dp else voiceChannelIcons[name] // Use group icon for member count VCs or fallback to a default vc icon + } + + return channelIcons[name] + } + + private val channelIcons: Map = mapOf( + "faq" to R.e.ic_help_24dp, + "help" to R.e.ic_help_24dp, + "support" to R.e.ic_help_24dp, + "info" to R.e.ic_info_24dp, + "roles" to R.e.ic_shieldstar_24dp, + "role-info" to R.e.ic_shieldstar_24dp, + "offtopic" to R.e.ic_chat_message_white_24dp, + "off-topic" to R.e.ic_chat_message_white_24dp, + "general" to R.e.ic_chat_message_white_24dp, + "general-chat" to R.e.ic_chat_message_white_24dp, + "general-talk" to R.e.ic_chat_message_white_24dp, + "talk" to R.e.ic_chat_message_white_24dp, + "chat" to R.e.ic_chat_message_white_24dp, + "art" to R.e.ic_theme_24dp, + "fanart" to R.e.ic_theme_24dp, + "fan-art" to R.e.ic_theme_24dp, + "bot" to R.e.ic_slash_command_24dp, + "bots" to R.e.ic_slash_command_24dp, + "bot-spam" to R.e.ic_slash_command_24dp, + "bot-commands" to R.e.ic_slash_command_24dp, + "commands" to R.e.ic_slash_command_24dp, + "memes" to R.e.ic_emoji_picker_category_people, + "meme" to R.e.ic_emoji_picker_category_people, + "meme-chat" to R.e.ic_emoji_picker_category_people, + "shitpost" to R.e.ic_emoji_picker_category_people, + "introductions" to R.e.ic_raised_hand_action_24dp, + "introduce-yourself" to R.e.ic_raised_hand_action_24dp, + "welcome" to R.e.ic_raised_hand_action_24dp, + "welcomes" to R.e.ic_raised_hand_action_24dp, + "intros" to R.e.ic_raised_hand_action_24dp, + "media" to R.e.ic_flex_input_image_24dp_dark, + "changes" to R.e.ic_history_white_24dp, + "changelog" to R.e.ic_history_white_24dp, + "logs" to R.e.ic_channels_24dp, + "modlogs" to R.e.ic_channels_24dp, + "starboard" to R.e.ic_star_24dp, + "resources" to R.e.ic_diag_link_24dp, + "links" to R.e.ic_diag_link_24dp, + "socials" to R.e.ic_diag_link_24dp, + "vc" to R.e.ic_mic_grey_24dp, + "muted" to R.e.ic_mic_grey_24dp, + "vc-chat" to R.e.ic_mic_grey_24dp, + "voice-chat" to R.e.ic_mic_grey_24dp, + "no-mic" to R.e.ic_mic_grey_24dp, + "music" to R.e.ic_headset_24dp, + "github" to R.e.ic_account_github_white_24dp, + "github-commits" to R.e.ic_account_github_white_24dp, + "github-notifications" to R.e.ic_account_github_white_24dp + ) + + private val voiceChannelIcons: Map = mapOf( + "music" to R.e.ic_headset_24dp + ) + +} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/IconListAdapter.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/IconListAdapter.java deleted file mode 100644 index 984bda1e..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/IconListAdapter.java +++ /dev/null @@ -1,105 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons.recycler; - -import android.content.Context; -import android.view.*; -import android.widget.*; -import android.graphics.*; -import android.graphics.drawable.*; -import android.util.Base64; -import android.app.*; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import androidx.annotation.DimenRes; -import androidx.fragment.app.FragmentActivity; -import androidx.core.content.ContextCompat; - -import com.aliucord.PluginManager; -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.Http; -import com.aliucord.Logger; -import com.aliucord.fragments.SettingsPage; -import com.discord.models.member.GuildMember; -import com.discord.models.user.User; -import com.discord.utilities.color.ColorCompat; -import com.discord.databinding.UserProfileHeaderBadgeBinding; -import com.discord.utilities.extensions.SimpleDraweeViewExtensionsKt; -import com.discord.utilities.icon.IconUtils; -import com.discord.utilities.images.MGImages; -import com.discord.stores.*; -import com.discord.widgets.user.usersheet.WidgetUserSheet; -import com.discord.widgets.user.*; -import com.lytefast.flexinput.R; -import com.facebook.drawee.view.SimpleDraweeView; - -import kotlin.jvm.functions.Function1; -import java.util.*; - -import xyz.wingio.plugins.betterchannelicons.*; - -import java.io.*; - -public class IconListAdapter extends RecyclerView.Adapter { - - public class IconListHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - private final IconListAdapter adapter; - public final RecyclerItem item; - - public IconListHolder(IconListAdapter adapter, RecyclerItem item) { - super(item); - this.adapter = adapter; - this.item = item; - } - - @Override public void onClick(View view) { - adapter.onClick(view.getContext(), getAdapterPosition()); - } - } - - private final Context ctx; - private final Map icons; - private final SettingsPage page; - public Logger logger = new Logger("BCI"); - - public IconListAdapter(SettingsPage page, Map icons) { - this.icons = icons; - this.page = page; - ctx = page.getContext(); - } - - @Override - public int getItemCount() { - return new ArrayList<>(icons.keySet()).size(); - } - - @NonNull - @Override - public IconListHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new IconListHolder(this, new RecyclerItem(ctx)); - } - - @Override - public void onBindViewHolder(@NonNull IconListHolder holder, int position) { - String name = new ArrayList<>(icons.keySet()).get(position); - String iconId = icons.get(name); - - holder.item.name.setText(name); - holder.item.delete.setOnClickListener(v -> { - icons.remove(name); - PluginManager.plugins.get("BetterChannelIcons").settings.setObject("icons", icons); - notifyDataSetChanged(); - }); - try { - Drawable icon = ContextCompat.getDrawable(ctx, Utils.getResId(iconId, "drawable")).mutate(); - icon.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal)); - holder.item.name.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); - } catch (Throwable e) { - logger.error("Failed to load icon", e); - } - } - - public void onClick(Context ctx, int position) { - - } -} diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/IconListAdapter.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/IconListAdapter.kt new file mode 100644 index 00000000..d7efb0d0 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/IconListAdapter.kt @@ -0,0 +1,71 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASs") + +package xyz.wingio.plugins.betterchannelicons.recycler + +import android.annotation.SuppressLint +import android.content.Context +import android.view.View +import android.view.ViewGroup + +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView + +import com.aliucord.Utils.getResId +import com.aliucord.Utils.tintToTheme +import com.aliucord.fragments.SettingsPage + +import xyz.wingio.plugins.BetterChannelIcons + +class IconListAdapter( + page: SettingsPage, + private val icons: MutableMap +) : RecyclerView.Adapter() { + + inner class IconListHolder(private val adapter: IconListAdapter, val item: RecyclerItem) : + RecyclerView.ViewHolder(item), View.OnClickListener { + + override fun onClick(view: View) { + adapter.onClick(view.context, adapterPosition) + } + + } + + private val ctx: Context? = page.context + + override fun getItemCount(): Int { + return ArrayList(icons.keys).size + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IconListHolder { + return IconListHolder(this, RecyclerItem(ctx)) + } + + @SuppressLint("NotifyDataSetChanged") + override fun onBindViewHolder(holder: IconListHolder, position: Int) { + val name = icons.keys.toList()[position] + val iconId = icons[name]!! + + holder.item.name.text = name + holder.item.delete.setOnClickListener { + icons.remove(name) + BetterChannelIcons.pluginSettings.setObject("icons", icons) + notifyDataSetChanged() + } + + try { + val icon = tintToTheme( + ContextCompat.getDrawable( + /* context = */ ctx!!, + /* id = */ getResId(name = iconId, type = "drawable") + )!!.mutate() + ) + + holder.item.name.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) + } catch (e: Throwable) { + BetterChannelIcons.logger.error("Failed to load icon", e) + } + } + + fun onClick(context: Context, adapterPosition: Int) {} + +} diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/RecyclerItem.java b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/RecyclerItem.java deleted file mode 100644 index 7c294c62..00000000 --- a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/RecyclerItem.java +++ /dev/null @@ -1,53 +0,0 @@ -package xyz.wingio.plugins.betterchannelicons.recycler; - -import android.content.Context; -import android.view.*; -import android.widget.*; -import android.graphics.drawable.*; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import androidx.core.content.res.ResourcesCompat; -import androidx.core.content.ContextCompat; - -import com.discord.utilities.color.ColorCompat; - -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.Constants; -import com.aliucord.utils.*; -import com.aliucord.views.Button; -import com.aliucord.views.ToolbarButton; - -import com.facebook.drawee.view.SimpleDraweeView; -import com.google.android.material.card.MaterialCardView; - -import com.lytefast.flexinput.R; - -public class RecyclerItem extends LinearLayout { - public final TextView name; - public final ToolbarButton delete; - - public RecyclerItem(Context ctx) { - super(ctx); - setOrientation(LinearLayout.HORIZONTAL); - setGravity(Gravity.CENTER_VERTICAL); - setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - name = new TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon); - - LinearLayout buttons = new LinearLayout(ctx); - buttons.setOrientation(LinearLayout.HORIZONTAL); - LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - params.setMarginEnd(DimenUtils.dpToPx(16)); - buttons.setLayoutParams(params); - buttons.setHorizontalGravity(Gravity.END); - buttons.setVerticalGravity(Gravity.CENTER_VERTICAL); - - delete = new ToolbarButton(ctx); - delete.setImageDrawable(ContextCompat.getDrawable(ctx, R.e.ic_delete_24dp)); - buttons.addView(delete); - - addView(name); - addView(buttons); - } -} \ No newline at end of file diff --git a/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/RecyclerItem.kt b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/RecyclerItem.kt new file mode 100644 index 00000000..d4386b86 --- /dev/null +++ b/BetterChannelIcons/src/main/java/xyz/wingio/plugins/betterchannelicons/recycler/RecyclerItem.kt @@ -0,0 +1,48 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.betterchannelicons.recycler + +import android.content.Context +import android.view.Gravity +import android.widget.LinearLayout +import android.widget.TextView + +import androidx.core.content.ContextCompat + +import com.aliucord.utils.DimenUtils.dp +import com.aliucord.views.ToolbarButton + +import com.lytefast.flexinput.R + +class RecyclerItem(ctx: Context?) : LinearLayout(ctx) { + + val name: TextView = TextView(ctx, null, 0, R.i.UiKit_Settings_Item_Icon) + + val delete: ToolbarButton = ToolbarButton(ctx).apply { + setImageDrawable(ContextCompat.getDrawable(ctx!!, R.e.ic_delete_24dp)) + } + + private val buttons = LinearLayout(ctx).apply { + setHorizontalGravity(Gravity.END) + setVerticalGravity(Gravity.CENTER_VERTICAL) + orientation = HORIZONTAL + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply { + marginEnd = 16.dp + } + + addView(delete) + } + + init { + orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT + ) + + addView(name) + addView(buttons) + } + +} \ No newline at end of file diff --git a/FriendNicknames/build.gradle.kts b/FriendNicknames/build.gradle.kts index 4c42eb8f..c8b5bde7 100644 --- a/FriendNicknames/build.gradle.kts +++ b/FriendNicknames/build.gradle.kts @@ -1,2 +1,2 @@ -version = "1.3.5" +version = "1.3.6" description = "Set custom nicknames for each of your friends!" \ No newline at end of file diff --git a/FriendNicknames/src/main/java/xyz/wingio/plugins/FriendNicknames.java b/FriendNicknames/src/main/java/xyz/wingio/plugins/FriendNicknames.java deleted file mode 100644 index 94ae6e38..00000000 --- a/FriendNicknames/src/main/java/xyz/wingio/plugins/FriendNicknames.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.aliucord.plugins; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.graphics.drawable.Drawable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.res.ResourcesCompat; -import androidx.core.content.ContextCompat; -import com.aliucord.PluginManager; -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.api.CommandsAPI; -import com.aliucord.api.SettingsAPI; -import com.aliucord.entities.Plugin; -import com.aliucord.patcher.Hook; -import com.aliucord.widgets.LinearLayout; -import com.aliucord.annotations.AliucordPlugin; -import com.discord.api.channel.Channel; -import com.discord.api.commands.ApplicationCommandType; -import com.discord.api.commands.CommandChoice; -import com.discord.app.AppBottomSheet; -import com.discord.models.commands.ApplicationCommandOption; -import com.discord.models.member.GuildMember; -import com.discord.models.user.User; -import com.discord.utilities.color.ColorCompat; -import com.discord.utilities.user.UserUtils; -import com.discord.utilities.icon.IconUtils; -import com.discord.views.CheckedSetting; -import com.discord.views.RadioManager; -import com.lytefast.flexinput.R; -import java.util.*; - -@AliucordPlugin -@SuppressWarnings({ "unchecked", "unused" }) -public class FriendNicknames extends Plugin { - private Drawable pluginIcon; - - public static final class PluginSettings extends AppBottomSheet { - public int getContentViewResId() { return 0; } - private final SettingsAPI settings; - public PluginSettings(SettingsAPI settings) { - this.settings = settings; - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - SettingsAPI sets = PluginManager.plugins.get("FriendNicknames").settings; - Context context = inflater.getContext(); - LinearLayout layout = new LinearLayout(context); - layout.setBackgroundColor(ColorCompat.getThemedColor(context, R.b.colorBackgroundPrimary)); - - layout.addView(createSwitch(context, sets, "showUsername", "Show Username", "Adds the username in parenthesis after the nickname", false)); - return layout; - } - - private CheckedSetting createSwitch(Context context, SettingsAPI sets, String key, String label, String subtext, boolean defaultValue) { - CheckedSetting cs = Utils.createCheckedSetting(context, CheckedSetting.ViewType.SWITCH, label, subtext); - cs.setChecked(sets.getBool(key, defaultValue)); - cs.setOnCheckedListener(c -> sets.setBool(key, c)); - return cs; - } - } - - public FriendNicknames() { - settingsTab = new SettingsTab(PluginSettings.class, SettingsTab.Type.BOTTOM_SHEET).withArgs(settings); - needsResources = true; - } - - @Override - @SuppressWarnings({ "unchecked", "ConstantConditions" }) - public void start(Context context) { - patcher.patch( - GuildMember.Companion.getClass(), - "getNickOrUsername", - new Class[] { - User.class, - GuildMember.class, - Channel.class, - List.class, - }, - new Hook( - callFrame -> { - var user = (User) callFrame.args[0]; - var userId = user.getId(); - var nickname = settings.getString(String.valueOf(userId), null); - if (nickname == null) return; - var showUsername = settings.getBool("showUsername", false); - if(showUsername == true) nickname = nickname + " (" + user.getUsername() + ")"; - callFrame.setResult(nickname); - } - ) - ); - - pluginIcon = ResourcesCompat.getDrawable(resources, resources.getIdentifier("ic_editfriend", "drawable", "com.aliucord.plugins"),null); - - // patcher.patch( - // IconUtils.class, - // "getForUser", - // new Class[] { - // User.class - // }, - // new Hook( - // callFrame -> { - // var user = (User) callFrame.args[0]; - // Utils.log(String.valueOf(user.getId())); - // callFrame.setResult("https://aperii.com/logo_circle.png"); - // } - // ) - // ); - - var userOption = Utils.createCommandOption(ApplicationCommandType.USER, "user", "User you want to set a nickname to", null, true, true, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), false); - var nickOption = Utils.createCommandOption(ApplicationCommandType.STRING, "nickname", "The nickname", null, true, true, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), false); - var setOption = Utils.createCommandOption(ApplicationCommandType.SUBCOMMAND, "set", "Set a nickname", null, true, true, new ArrayList<>(), new ArrayList<>(), Arrays.asList(userOption, nickOption), false); - var clearOption = Utils.createCommandOption(ApplicationCommandType.SUBCOMMAND, "clear", "Clear a nickname", null, true, true, new ArrayList<>(), new ArrayList<>(), Arrays.asList(userOption), false); - - commands.registerCommand( - "nick", - "Modify a nickname for a particular user", - Arrays.asList(setOption, clearOption), - ctx -> { - if (ctx.containsArg("set")) { - var setargs = ctx.getSubCommandArgs("set"); - var user = (String) setargs.get("user"); - var nickname = (String) setargs.get("nickname"); - if ( user == null || user.equals("") || nickname == null || nickname.equals("")) { - return new CommandsAPI.CommandResult( - "Missing arguments", - null, - false - ); - } - - settings.setString(user, String.valueOf(nickname)); - - return new CommandsAPI.CommandResult("Set nickname", null, false); - } - - if (ctx.containsArg("clear")) { - var setargs = ctx.getSubCommandArgs("clear"); - var user = (String) setargs.get("user"); - if (user == null || user.equals("")) { - return new CommandsAPI.CommandResult( - "Missing arguments", - null, - false - ); - } - - settings.setString(user, null); - - return new CommandsAPI.CommandResult("Cleared nickname", null, false); - } - - return new CommandsAPI.CommandResult(); - } - ); - } - - @Override - public void stop(Context context) { - patcher.unpatchAll(); - commands.unregisterAll(); - } -} diff --git a/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/Command.kt b/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/Command.kt new file mode 100644 index 00000000..be5135e2 --- /dev/null +++ b/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/Command.kt @@ -0,0 +1,85 @@ +package xyz.wingio.plugins.friendnicknames + +import com.aliucord.Utils +import com.aliucord.api.CommandsAPI +import com.aliucord.api.CommandsAPI.CommandResult +import com.aliucord.api.SettingsAPI +import com.aliucord.entities.CommandContext +import com.discord.api.commands.ApplicationCommandType +import com.discord.models.commands.ApplicationCommandOption + +fun registerCommand( + commandsAPI: CommandsAPI, + settings: SettingsAPI +) { + commandsAPI.registerCommand( + "nick", + "Modify a nickname for a particular user", + listOf(setOption, clearOption) + ) { ctx: CommandContext -> + when { + ctx.containsArg("set") -> nickSet(ctx, settings) + ctx.containsArg("clear") -> nickClear(ctx, settings) + else -> CommandResult("No subcommand provided", null, false) + } + } +} + +private fun nickSet(ctx: CommandContext, settings: SettingsAPI): CommandResult { + val setArgs = ctx.getSubCommandArgs("set")!! + val user = setArgs["user"] as String? + val nickname = setArgs["nickname"] as String? + + if (user == null || user == "" || nickname == null || nickname == "") { + return CommandResult("Missing Arguments", null, false) + } + + settings.setString(user, nickname) + return CommandResult("Friend nickname updated!", null, false); +} + +private fun nickClear(ctx: CommandContext, settings: SettingsAPI): CommandResult { + val setArgs = ctx.getSubCommandArgs("clear")!! + val user = setArgs["user"] as String? + + if (user == null || user == "") { + return CommandResult("Missing Arguments", null, false) + } + + settings.setString(user, null) + return CommandResult("Friend nickname cleared!", null, false); +} + +val userOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.USER, + name = "user", + description = "User you want to set a nickname to", + required = true, + default = true +) + +val nickOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.STRING, + name = "nickname", + description = "The nickname", + required = true, + default = true, +) + +val setOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.SUBCOMMAND, + name = "set", + description = "Set a nickname", + required = true, + default = true, + subCommandOptions = listOf(userOption, nickOption), +) + +val clearOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.SUBCOMMAND, + name = "clear", + description = "Clear a nickname", + required = true, + default = true, + subCommandOptions = listOf(userOption), +) \ No newline at end of file diff --git a/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/FriendNicknames.kt b/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/FriendNicknames.kt new file mode 100644 index 00000000..474e40f0 --- /dev/null +++ b/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/FriendNicknames.kt @@ -0,0 +1,40 @@ +package xyz.wingio.plugins.friendnicknames + +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.core.content.res.ResourcesCompat +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.discord.api.channel.Channel +import com.discord.models.member.GuildMember +import com.discord.models.user.User + +@AliucordPlugin +class FriendNicknames: Plugin() { + + private lateinit var pluginIcon: Drawable + + init { + settingsTab = SettingsTab(PluginSettings::class.java, SettingsTab.Type.BOTTOM_SHEET) + needsResources = true + } + + override fun start(context: Context?) { + pluginIcon = ResourcesCompat.getDrawable(resources, resources.getIdentifier("ic_editfriend", "drawable", "com.aliucord.plugins"), null)!! + registerCommand(commands, settings) + + patcher.after("getNickOrUsername", User::class.java, GuildMember::class.java, Channel::class.java, List::class.java) { (callFrame, user: User) -> + var nickname = settings.getString(user.id.toString(), null) ?: return@after + val showUsername = settings.getBool("showUsername", false) + + if (showUsername) nickname += " (${user.username})" + callFrame.result = nickname + } + } + + override fun stop(context: Context?) = patcher.unpatchAll() + +} \ No newline at end of file diff --git a/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/PluginSettings.kt b/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/PluginSettings.kt new file mode 100644 index 00000000..9197cafe --- /dev/null +++ b/FriendNicknames/src/main/java/xyz/wingio/plugins/friendnicknames/PluginSettings.kt @@ -0,0 +1,66 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.friendnicknames + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aliucord.PluginManager +import com.aliucord.Utils.createCheckedSetting +import com.aliucord.api.SettingsAPI +import com.aliucord.widgets.LinearLayout +import com.discord.app.AppBottomSheet +import com.discord.utilities.color.ColorCompat +import com.discord.views.CheckedSetting +import com.lytefast.flexinput.R + +class PluginSettings: AppBottomSheet() { + + override fun getContentViewResId() = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val settings = PluginManager.plugins["FriendNicknames"]!!.settings + val context = inflater.context + val layout = LinearLayout(context).apply { + setBackgroundColor(ColorCompat.getThemedColor(context, R.b.colorBackgroundPrimary)) + } + + layout.addView( + createSwitch( + context = context, + sets = settings, + key = "showUsername", + label = "Show Username", + subtext = "Adds the username in parenthesis after the nickname", + defaultValue = false + ) + ) + return layout + } + + @Suppress("SameParameterValue") + private fun createSwitch( + context: Context, + sets: SettingsAPI, + key: String, + label: String, + subtext: String, + defaultValue: Boolean + ): CheckedSetting { + val cs = createCheckedSetting(context, CheckedSetting.ViewType.SWITCH, label, subtext) + cs.isChecked = sets.getBool(key, defaultValue) + cs.setOnCheckedListener { c: Boolean? -> + sets.setBool( + key, + c!! + ) + } + return cs + } +} \ No newline at end of file diff --git a/MoreHighlight/build.gradle.kts b/MoreHighlight/build.gradle.kts index 2212c141..7f38765a 100644 --- a/MoreHighlight/build.gradle.kts +++ b/MoreHighlight/build.gradle.kts @@ -1,11 +1,15 @@ -version = "1.3.0" +version = "1.4.3" description = "Adds more syntax highlighting options" aliucord.changelog.set( """ - New {added marginTop} + New {added} ====================== - - * Backported slash command highlighting + * Added headers, subtext and bulletpoints -serinova (credit to AAurus for their version with their regex, god send) + * Added slider in settings to change the headers font size + + Fixed (v1.4.2) {fixed marginTop} + ====================== + * Bullets and subtext no longer prevent other markdown from working """.trimIndent() -) \ No newline at end of file +) diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/MoreHighlight.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/MoreHighlight.java deleted file mode 100644 index 3981d0f7..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/MoreHighlight.java +++ /dev/null @@ -1,107 +0,0 @@ -package xyz.wingio.plugins; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.*; -import android.view.*; -import android.widget.*; -import android.os.*; - -import androidx.constraintlayout.widget.ConstraintLayout; - -import xyz.wingio.plugins.morehighlight.*; - -import com.aliucord.Constants; -import com.aliucord.Utils; -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.api.CommandsAPI; -import com.aliucord.entities.Plugin; -import com.aliucord.patcher.*; -import com.aliucord.annotations.AliucordPlugin; -import com.aliucord.wrappers.*; -import com.aliucord.utils.*; -import com.aliucord.views.Button; - -import com.discord.api.commands.ApplicationCommandType; -import com.discord.api.commands.CommandChoice; -import com.discord.models.commands.ApplicationCommandOption; -import com.discord.stores.*; -import com.discord.widgets.guilds.profile.WidgetGuildProfileSheet; -import com.discord.widgets.user.usersheet.WidgetUserSheet; -import com.discord.widgets.guilds.list.*; -import com.discord.widgets.home.*; -import com.discord.panels.*; -import com.discord.simpleast.core.parser.*; -import com.discord.utilities.textprocessing.*; -import com.discord.simpleast.core.node.Node; -import com.discord.widgets.chat.list.adapter.WidgetChatListAdapterItemMessage; - -import com.discord.utilities.textprocessing.node.EditedMessageNode; -import com.discord.utilities.textprocessing.node.ZeroSpaceWidthNode; - - -import com.lytefast.flexinput.R; - -import java.util.regex.*; -import java.util.*; -import java.lang.reflect.*; -import java.lang.*; -import kotlin.NoWhenBranchMatchedException; - -@AliucordPlugin -public class MoreHighlight extends Plugin { - - public MoreHighlight() { - settingsTab = new SettingsTab(PluginSettings.class).withArgs(this); - needsResources = true; - } - - public Logger logger = new Logger("MoreHighlight"); - public static Pattern REDDIT_REGEX = Pattern.compile("^<([ur])\\/([a-zA-Z0-9_]{3,20})>"); - public static Pattern ISSUE_REGEX = Pattern.compile("^<([A-Za-z0-9-]{1,39})\\/([A-Za-z0-9-]{1,39})#([0-9]{1,})>"); - public static Pattern REPO_REGEX = Pattern.compile("^"); - public static Pattern ALIU_REGEX = Pattern.compile("^ac://([A-Za-z0-9$]+)"); - - public Field rulesField; - - @Override - public void start(Context context) throws Throwable { - - try { - rulesField = Parser.class.getDeclaredField("rules"); - rulesField.setAccessible(true); - } catch (NoSuchFieldException e) { - logger.error("Failed to get rules field", e); - } - - patcher.patch(DiscordParser.class, "parseChannelMessage", new Class[] {Context.class, String.class, MessageRenderContext.class, MessagePreprocessor.class, DiscordParser.ParserOptions.class, boolean.class}, new PreHook(callFrame -> { - try{ - Context ctx = (Context) callFrame.args[0]; - Parser, MessageParseState> parser = DiscordParser.createParser$default(true, true, true, false, false, 4, null); - String str = (String) callFrame.args[1]; - ArrayList,MessageParseState>> rules = (ArrayList,MessageParseState>>) rulesField.get(parser); - rules.add(0, new RedditRule(REDDIT_REGEX, ctx)); - rules.add(0, new IssueRule(ISSUE_REGEX, ctx)); - rules.add(0, new RepoRule(REPO_REGEX, ctx)); - rules.add(0, new AliuRule(ALIU_REGEX, ctx)); - rules.add(0, new SlashCommandRule()); - rules.add(0, new ColorRule()); - rulesField.set(parser, rules); - if (str == null) { - str = ""; - } - var parsed = Parser.parse$default(parser, str, MessageParseState.Companion.getInitialState(), null, 4, null); - ((MessagePreprocessor) callFrame.args[3]).process(parsed); - if((boolean) callFrame.args[5]){ - parsed.add(new EditedMessageNode((Context) callFrame.args[0])); - } - parsed.add(new ZeroSpaceWidthNode()); - callFrame.setResult(AstRenderer.render(parsed, (MessageRenderContext) callFrame.args[2])); - } catch(Throwable e) {logger.error("Error patching parser", e);} - })); - } - - @Override - public void stop(Context context) { patcher.unpatchAll(); } -} diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/AliuRule.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/AliuRule.java deleted file mode 100644 index 5460c1d0..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/AliuRule.java +++ /dev/null @@ -1,56 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import android.content.Context; - -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.Fragment; - -import com.aliucord.PluginManager; -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.entities.Plugin; - -import com.discord.app.*; -import com.discord.simpleast.core.parser.ParseSpec; -import com.discord.simpleast.core.parser.Parser; -import com.discord.simpleast.core.node.Node; -import com.discord.simpleast.core.parser.Rule; -import com.discord.utilities.textprocessing.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import kotlin.Unit; - -public final class AliuRule extends Rule,MessageParseState> { - private Context context; - private static FragmentManager cachedFragment = Utils.appActivity.getSupportFragmentManager(); - - public AliuRule(Pattern pattern, Context context) { - super(pattern); - this.context = context; - } - - @Override - public ParseSpec parse(Matcher matcher, Parser, MessageParseState> parser, MessageParseState s) { - final Plugin p = PluginManager.plugins.get(matcher.group(1)); - Context ctx = context; - var textNode = (p != null && p.settingsTab != null) ? new ClickableNode(matcher.group(1), "", context) : new TextNode(matcher.group(1)); - if(textNode instanceof ClickableNode) ((ClickableNode) textNode).setOnClickListener(v -> { - try{ - if (p.settingsTab.type == Plugin.SettingsTab.Type.PAGE && p.settingsTab.page != null) { - Fragment page = p.settingsTab.args != null - ? ReflectUtils.invokeConstructorWithArgs(p.settingsTab.page, p.settingsTab.args) - : p.settingsTab.page.newInstance(); - Utils.openPageWithProxy(ctx, page); - } else if (p.settingsTab.type == Plugin.SettingsTab.Type.BOTTOM_SHEET && p.settingsTab.bottomSheet != null) { - AppBottomSheet sheet = p.settingsTab.args != null - ? ReflectUtils.invokeConstructorWithArgs(p.settingsTab.bottomSheet, p.settingsTab.args) - : p.settingsTab.bottomSheet.newInstance(); - - sheet.show(cachedFragment, p.getName() + "Settings"); - } - } catch (Throwable e) {} - return Unit.a; - }); - return new ParseSpec<>(textNode, s); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ClickableNode.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ClickableNode.java deleted file mode 100644 index 0b236f9f..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ClickableNode.java +++ /dev/null @@ -1,60 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import com.aliucord.Utils; - -import android.content.Context; -import android.view.View; - -import com.discord.simpleast.core.node.Node; -import com.discord.utilities.textprocessing.*; -import com.discord.utilities.color.ColorCompat; - -import com.discord.utilities.spans.ClickableSpan; -import android.text.SpannableStringBuilder; -import android.text.Spanned; - -import com.lytefast.flexinput.R; - -import kotlin.Unit; -import kotlin.jvm.functions.Function1; - -public class ClickableNode extends Node { - String content; - String url; - Context context; - Function1 onClick; - Function1 onLongClick; - - public ClickableNode(String content, String url, Context context){ - this(content, url, context, v -> {return Unit.a;}, v -> {return Unit.a;}); - } - - public ClickableNode(String content, String url, Context context, Function1 onClick){ - this(content, url, context, onClick, v -> {return Unit.a;}); - } - - public ClickableNode(String content, String url, Context context, Function1 onClick, Function1 onLongClick){ - super(); - this.content = content; - this.url = url; - this.context = context; - this.onClick = onClick; - this.onLongClick = onLongClick; - } - - public void setOnClickListener(Function1 onClick){ - this.onClick = onClick; - } - - public void setOnLongClickListener(Function1 onLongClick){ - this.onLongClick = onLongClick; - } - - @Override - public void render(SpannableStringBuilder builder, MessageRenderContext renderContext) { - Object clickableSpan = new ClickableSpan(Integer.valueOf(ColorCompat.getThemedColor(context, R.b.colorTextLink)), false, onLongClick, onClick); - int i = builder.length(); - builder.append(content); - builder.setSpan(clickableSpan, i, builder.length(), 33); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ColorNode.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ColorNode.java deleted file mode 100644 index 1bdc3496..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ColorNode.java +++ /dev/null @@ -1,29 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import androidx.core.graphics.ColorUtils; - -import com.discord.simpleast.core.node.Node; -import com.discord.utilities.textprocessing.*; - -import android.text.SpannableStringBuilder; -import android.text.style.*; - -public class ColorNode extends Node { - String content; - int color; - - public ColorNode(String content, int color){ - super(); - this.content = content; - this.color = color; - } - - @Override - public void render(SpannableStringBuilder builder, MessageRenderContext renderContext) { - int length = builder.length(); - builder.append(content); - builder.setSpan(new ForegroundColorSpan(color), length, builder.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - builder.setSpan(new BackgroundColorSpan(ColorUtils.setAlphaComponent(color, 25)), length, builder.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - builder.setSpan(new StyleSpan(1), length, builder.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ColorRule.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ColorRule.java deleted file mode 100644 index bbcaff38..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/ColorRule.java +++ /dev/null @@ -1,29 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import android.content.Context; -import android.graphics.Color; - -import com.aliucord.PluginManager; -import com.discord.simpleast.core.parser.ParseSpec; -import com.discord.simpleast.core.parser.Parser; -import com.discord.simpleast.core.node.Node; -import com.discord.simpleast.core.parser.Rule; -import com.discord.utilities.textprocessing.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class ColorRule extends Rule,MessageParseState> { - public ColorRule() { - super(Pattern.compile("^#[0-9a-fA-F]{6,8}")); - } - - @Override - public ParseSpec parse(Matcher matcher, Parser, MessageParseState> parser, MessageParseState s) { - try { - ColorNode textNode = new ColorNode(matcher.group(0), Color.parseColor(matcher.group(0))); - return new ParseSpec<>(textNode, s); - } catch (Exception e) { - return new ParseSpec<>(new TextNode(matcher.group(0)), s); - } - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/IssueRule.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/IssueRule.java deleted file mode 100644 index 828052e5..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/IssueRule.java +++ /dev/null @@ -1,26 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import android.content.Context; - -import com.aliucord.PluginManager; -import com.discord.simpleast.core.parser.ParseSpec; -import com.discord.simpleast.core.parser.Parser; -import com.discord.simpleast.core.node.Node; -import com.discord.simpleast.core.parser.Rule; -import com.discord.utilities.textprocessing.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public final class IssueRule extends Rule,MessageParseState> { - private Context context; - public IssueRule(Pattern pattern, Context context) { - super(pattern); - this.context = context; - } - - @Override - public ParseSpec parse(Matcher matcher, Parser, MessageParseState> parser, MessageParseState s) { - boolean showRepo = PluginManager.plugins.get("MoreHighlight").settings.getBool("show_repo_name", false); - LinkNode textNode = new LinkNode((showRepo ? matcher.group(2) + "#" : "#") + matcher.group(3), String.format("https://github.com/%s/%s/issues/%s", matcher.group(1), matcher.group(2), matcher.group(3)), context); - return new ParseSpec<>(textNode, s); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/LinkNode.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/LinkNode.java deleted file mode 100644 index 0b57f3a6..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/LinkNode.java +++ /dev/null @@ -1,38 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import com.aliucord.Utils; - -import android.content.Context; - -import com.discord.simpleast.core.node.Node; -import com.discord.utilities.textprocessing.*; -import com.discord.utilities.color.ColorCompat; - -import com.discord.utilities.spans.ClickableSpan; -import android.text.SpannableStringBuilder; -import android.text.Spanned; - -import com.lytefast.flexinput.R; - -import kotlin.Unit; - -public class LinkNode extends Node { - String content; - String url; - Context context; - - public LinkNode(String content, String url, Context context){ - super(); - this.content = content; - this.url = url; - this.context = context; - } - - @Override - public void render(SpannableStringBuilder builder, MessageRenderContext renderContext) { - Object clickableSpan = new ClickableSpan(Integer.valueOf(ColorCompat.getThemedColor(context, R.b.colorTextLink)), false, v -> {return Unit.a;}, v-> {Utils.launchUrl(url); return Unit.a;}); - int i = builder.length(); - builder.append(content); - builder.setSpan(clickableSpan, i, builder.length(), 33); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/MoreHighlight.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/MoreHighlight.kt new file mode 100644 index 00000000..5ba49c00 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/MoreHighlight.kt @@ -0,0 +1,85 @@ +package xyz.wingio.plugins.morehighlight + +import android.content.Context + +import com.aliucord.Logger +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import com.aliucord.patcher.* +import com.aliucord.utils.lazyField + +import com.discord.simpleast.core.node.Node +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule +import com.discord.utilities.textprocessing.AstRenderer +import com.discord.utilities.textprocessing.DiscordParser +import com.discord.utilities.textprocessing.DiscordParser.ParserOptions +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessagePreprocessor +import com.discord.utilities.textprocessing.MessageRenderContext +import com.discord.utilities.textprocessing.node.BasicRenderContext +import com.discord.utilities.textprocessing.node.EditedMessageNode +import com.discord.utilities.textprocessing.node.ZeroSpaceWidthNode +import com.facebook.drawee.span.DraweeSpanStringBuilder + +import xyz.wingio.plugins.morehighlight.rule.AliuRule +import xyz.wingio.plugins.morehighlight.rule.BulletPointRule +import xyz.wingio.plugins.morehighlight.rule.ColorRule +import xyz.wingio.plugins.morehighlight.rule.HeaderRule +import xyz.wingio.plugins.morehighlight.rule.IssueRule +import xyz.wingio.plugins.morehighlight.rule.RedditRule +import xyz.wingio.plugins.morehighlight.rule.RepoRule +import xyz.wingio.plugins.morehighlight.rule.SlashCommandRule +import xyz.wingio.plugins.morehighlight.rule.SubtextRule + +import java.util.regex.Pattern + +@AliucordPlugin +class MoreHighlight: Plugin() { + + private val rulesField by lazyField>("rules") + + init { + settingsTab = SettingsTab(PluginSettings::class.java).withArgs(this) + } + + @Suppress("UNCHECKED_CAST") + override fun start(context: Context) { + patcher.instead("parseChannelMessage", Context::class.java, String::class.java, MessageRenderContext::class.java, MessagePreprocessor::class.java, ParserOptions::class.java, Boolean::class.javaPrimitiveType!!) { (_, ctx: Context, content: String?, renderContext: MessageRenderContext, messagePreprocessor: MessagePreprocessor, _: ParserOptions, isEdited: Boolean) -> + val parser= + DiscordParser.createParser(true, true, true, false, false) as Parser, MessageParseState> + val rules = + rulesField[parser] as ArrayList, MessageParseState>> + + rules.addAll(0, listOf( + SlashCommandRule(), + ColorRule(), + AliuRule(ctx), + IssueRule(ctx), + RepoRule(ctx), + RedditRule(ctx), + HeaderRule(), + SubtextRule(ctx), + BulletPointRule(ctx) + )) + + rulesField[parser] = rules + + val parsed = Parser.`parse$default`(parser, content ?: "", MessageParseState.`access$getInitialState$cp`(), null, 4, null) as MutableList> + messagePreprocessor.process(parsed) + + if (isEdited) parsed.add(EditedMessageNode(ctx)) + parsed.add(ZeroSpaceWidthNode()) + + AstRenderer.render(parsed, renderContext) + } + } + + override fun stop(context: Context) = patcher.unpatchAll() + + companion object { + const val PREF_SHOW_REPO_NAME = "show_repo_name" + const val PREF_HEADER_SIZE = "header_size_scale" + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/PluginSettings.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/PluginSettings.java deleted file mode 100644 index a0085265..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/PluginSettings.java +++ /dev/null @@ -1,75 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import android.annotation.SuppressLint; -import android.view.*; -import android.widget.*; -import android.content.Context; -import android.util.AttributeSet; - -import androidx.core.content.res.ResourcesCompat; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.recyclerview.widget.*; - -import xyz.wingio.plugins.MoreHighlight; - -import com.aliucord.Constants; -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.Http; -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.api.SettingsAPI; -import com.aliucord.api.NotificationsAPI; -import com.aliucord.fragments.SettingsPage; -import com.aliucord.views.Divider; -import com.aliucord.entities.NotificationData; - -import com.discord.views.CheckedSetting; -import com.discord.views.RadioManager; -import com.discord.widgets.user.profile.UserProfileHeaderView; -import com.discord.stores.*; -import com.discord.models.user.User; -import com.discord.panels.*; -import com.discord.utilities.rest.RestAPI; -import com.discord.utilities.analytics.AnalyticSuperProperties; -import com.lytefast.flexinput.R; - -import kotlin.Unit; -import java.util.*; - -@SuppressLint("SetTextI18n") -public final class PluginSettings extends SettingsPage { - private SettingsAPI settings; - private MoreHighlight plugin; - private int p = DimenUtils.dpToPx(16); - - public PluginSettings(MoreHighlight plugin) { - this.plugin = plugin; - this.settings = plugin.settings; - } - - @Override - @SuppressWarnings("ResultOfMethodCallIgnored") - public void onViewBound(View view) { - super.onViewBound(view); - setActionBarTitle("Test Plugin"); - setPadding(p); - - var ctx = view.getContext(); - var layout = getLinearLayout(); - - layout.addView(createSwitch(ctx, settings, "show_repo_name", "Show repo name in issue/pr link", null, false)); - TextView info = new TextView(ctx, null, 0, R.i.UiKit_TextView); - info.setText(MDUtils.render("**Currently supports:**\n\n - **Reddit** (, , *ex. *)\n - **Github** ( , *ex. *)\n - **Plugin Settings** (ac://[Plugin Name], *ex. ac://MoreHighlight*)\n -**Colors** *ex #1f8b4c*\n -**Slash Commands** (, *ex. *)")); - info.setPadding(0, p, 0, 0); - layout.addView(new Divider(ctx)); - layout.addView(info); - } - - private CheckedSetting createSwitch(Context context, SettingsAPI sets, String key, String label, String subtext, boolean defaultValue) { - CheckedSetting cs = Utils.createCheckedSetting(context, CheckedSetting.ViewType.SWITCH, label, subtext); - cs.setChecked(sets.getBool(key, defaultValue)); - cs.setOnCheckedListener(c -> sets.setBool(key, c)); - return cs; - } -} diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/PluginSettings.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/PluginSettings.kt new file mode 100644 index 00000000..9f6d6636 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/PluginSettings.kt @@ -0,0 +1,159 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.morehighlight + +import android.annotation.SuppressLint +import android.content.Context +import android.view.View +import android.widget.LinearLayout +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import com.aliucord.Utils.createCheckedSetting +import com.aliucord.api.SettingsAPI +import com.aliucord.fragments.SettingsPage +import com.aliucord.utils.DimenUtils.dp +import com.aliucord.utils.MDUtils +import com.aliucord.views.Divider +import com.discord.views.CheckedSetting +import com.lytefast.flexinput.R + +private val INFO_TEXT = """ + **Currently supports:** + + **Ported** + - **Headers** (# Header 1, ## Header 2, ### Header 3) + - **Subtext** (-# tiny greyed out text) + - **Bullets** (* bulletpoint or - bulletpoint) + - **Slash Commands** (, *ex. *) + + **Custom** + - **Reddit** (, , *ex. *) + - **Github** ( , *ex. *) + - **Plugin Settings** (ac://[Plugin Name], *ex. ac://MoreHighlight*) + - **Colors** (*ex. #1f8b4c*) +""".trimIndent() + +@SuppressLint("SetTextI18n") +class PluginSettings( + plugin: MoreHighlight +): SettingsPage() { + + private val settings: SettingsAPI = plugin.settings + private val padding = 16.dp + + override fun onViewBound(view: View) { + super.onViewBound(view) + setActionBarTitle("MoreHighlight") + setActionBarSubtitle("Settings") + setPadding(0) + + val ctx = view.context + val layout = linearLayout.apply { setPadding(0, 0, 0, 0) } + val currentScale = (settings.getFloat(MoreHighlight.PREF_HEADER_SIZE, 1.0f) * 100).toInt() + + layout.addView( + createSlider( + context = ctx, + label = "Header Size Scale", + subtext = "Adjust the size of headers (100% is default)", + maxValue = 200, + defaultValue = currentScale, + valuePostfix = "%", + onSeekChanged = { + settings.setFloat(MoreHighlight.PREF_HEADER_SIZE, it / 100f) + } + ) + ) + + layout.addView( + createSwitch( + context = ctx, + sets = settings, + key = MoreHighlight.PREF_SHOW_REPO_NAME, + label = "Show repo name in issue/pr link", + subtext = null, + defaultValue = false + ) + ) + + layout.addView(Divider(ctx)) + + TextView(ctx, null, 0, R.i.UiKit_TextView).apply { + text = MDUtils.render(INFO_TEXT) + setPadding(padding, padding, padding, 0) + layout.addView(this) + } + } + + @Suppress("SameParameterValue") + private fun createSwitch( + context: Context, + sets: SettingsAPI, + key: String, + label: String, + subtext: String?, + defaultValue: Boolean + ): CheckedSetting { + return createCheckedSetting(context, CheckedSetting.ViewType.SWITCH, label, subtext).apply { + l.b().run { setPadding(0, padding, padding, padding) } + isChecked = sets.getBool(key, defaultValue) + setPadding(0, 0, 0, 0) + setOnCheckedListener { checked: Boolean? -> + sets.setBool(key, checked!!) + } + } + } + + @Suppress("SameParameterValue") + private fun createSlider( + context: Context, + label: String, + subtext: String?, + maxValue: Int, + defaultValue: Int, + onSeekChanged: (Int) -> Unit, + valuePostfix: String = "" + ): LinearLayout { + return LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + setPadding(padding, padding, padding, padding) + + TextView(context, null, 0, R.i.UiKit_Settings_Item_Label).apply { + text = label + setPadding(0, 0, 0, 8.dp) + addView(this) + } + + TextView(context, null, 0, R.i.UiKit_Settings_Item_SubText).apply { + text = subtext + setPadding(0, 0, 0, 8.dp) + addView(this) + } + + val headerSizeSlider = SeekBar(context).apply { + max = maxValue + progress = defaultValue + setPadding(0, 8.dp, 0, 0) + addView(this) + } + + val headerSizeValue = TextView(context, null, 0, R.i.UiKit_TextView).apply { + text = "$defaultValue$valuePostfix" + setPadding(0, 4.dp, 0, 0) + addView(this) + } + + headerSizeSlider.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + onSeekChanged(progress) + headerSizeValue.text = "$progress$valuePostfix" + } + + override fun onStartTrackingTouch(seekBar: SeekBar) {} + override fun onStopTrackingTouch(seekBar: SeekBar) {} + }) + } + } + +} diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/RedditRule.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/RedditRule.java deleted file mode 100644 index f8a9a79a..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/RedditRule.java +++ /dev/null @@ -1,24 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import android.content.Context; - -import com.discord.simpleast.core.parser.ParseSpec; -import com.discord.simpleast.core.parser.Parser; -import com.discord.simpleast.core.node.Node; -import com.discord.simpleast.core.parser.Rule; -import com.discord.utilities.textprocessing.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public final class RedditRule extends Rule,MessageParseState> { - private Context context; - public RedditRule(Pattern pattern, Context context) { - super(pattern); - this.context = context; - } - - @Override - public ParseSpec parse(Matcher matcher, Parser, MessageParseState> parser, MessageParseState s) { - LinkNode textNode = new LinkNode(matcher.group(1) + "/" + matcher.group(2), String.format("https://reddit.com/%s/%s", matcher.group(1), matcher.group(2)), context); - return new ParseSpec<>(textNode, s); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/RepoRule.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/RepoRule.java deleted file mode 100644 index fbddba4e..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/RepoRule.java +++ /dev/null @@ -1,25 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import android.content.Context; - -import com.aliucord.PluginManager; -import com.discord.simpleast.core.parser.ParseSpec; -import com.discord.simpleast.core.parser.Parser; -import com.discord.simpleast.core.node.Node; -import com.discord.simpleast.core.parser.Rule; -import com.discord.utilities.textprocessing.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public final class RepoRule extends Rule,MessageParseState> { - private Context context; - public RepoRule(Pattern pattern, Context context) { - super(pattern); - this.context = context; - } - - @Override - public ParseSpec parse(Matcher matcher, Parser, MessageParseState> parser, MessageParseState s) { - LinkNode textNode = new LinkNode(matcher.group(1) + "/" + matcher.group(2), String.format("https://github.com/%s/%s", matcher.group(1), matcher.group(2)), context); - return new ParseSpec<>(textNode, s); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/SlashCommandNode.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/SlashCommandNode.java deleted file mode 100644 index f80ed45d..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/SlashCommandNode.java +++ /dev/null @@ -1,38 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import android.content.Context; -import android.text.SpannableStringBuilder; -import android.text.style.*; - -import androidx.core.graphics.ColorUtils; - -import com.discord.simpleast.core.node.Node; -import com.discord.stores.*; -import com.discord.utilities.textprocessing.*; -import com.discord.utilities.spans.ClickableSpan; -import com.discord.utilities.color.ColorCompat; - -import com.lytefast.flexinput.R; -import kotlin.Unit; - -public final class SlashCommandNode extends Node { - private String content; - - public SlashCommandNode(String content) { - super(); - this.content = content; - } - - @Override - public void render(SpannableStringBuilder builder, MessageRenderContext renderContext) { - var color = Integer.valueOf(ColorCompat.getThemedColor(renderContext.getContext(), R.b.colorTextLink)); - int length = builder.length(); - builder.append("/" + content); - builder.setSpan(new ClickableSpan(color, false, v -> {return Unit.a;}, v-> { - StoreStream.getChat().replaceChatText("/" + content); - return Unit.a; - }), length, builder.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - builder.setSpan(new BackgroundColorSpan(ColorUtils.setAlphaComponent(color, 25)), length, builder.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - builder.setSpan(new StyleSpan(1), length, builder.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/SlashCommandRule.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/SlashCommandRule.java deleted file mode 100644 index f12dd6a6..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/SlashCommandRule.java +++ /dev/null @@ -1,21 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import com.discord.simpleast.core.parser.ParseSpec; -import com.discord.simpleast.core.parser.Parser; -import com.discord.simpleast.core.parser.Rule; -import com.discord.utilities.textprocessing.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class SlashCommandRule extends Rule,MessageParseState> { - - public SlashCommandRule() { - super(Pattern.compile("^")); - } - - @Override - public ParseSpec parse(Matcher matcher, Parser, MessageParseState> parser, MessageParseState s) { - SlashCommandNode scNode = new SlashCommandNode(matcher.group(1)); - return new ParseSpec<>(scNode, s); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/TextNode.java b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/TextNode.java deleted file mode 100644 index 2177a7fd..00000000 --- a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/TextNode.java +++ /dev/null @@ -1,20 +0,0 @@ -package xyz.wingio.plugins.morehighlight; - -import com.discord.simpleast.core.node.Node; -import com.discord.utilities.textprocessing.*; - -import android.text.SpannableStringBuilder; - -public class TextNode extends Node { - String content; - - public TextNode(String content){ - super(); - this.content = content; - } - - @Override - public void render(SpannableStringBuilder builder, MessageRenderContext renderContext) { - builder.append(content); - } -} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/BulletPointNode.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/BulletPointNode.kt new file mode 100644 index 00000000..f11033cd --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/BulletPointNode.kt @@ -0,0 +1,28 @@ +package xyz.wingio.plugins.morehighlight.node + +import android.content.Context +import android.graphics.Typeface +import android.os.Build +import android.text.SpannableStringBuilder +import android.text.style.BulletSpan +import android.text.style.StyleSpan +import com.aliucord.utils.DimenUtils.dp +import com.discord.simpleast.core.node.Node +import com.discord.utilities.color.ColorCompat +import com.lytefast.flexinput.R + +class BulletPointNode(private val context: Context): Node.a() { + + override fun render(builder: SpannableStringBuilder, renderContext: MessageRenderContext) { + val length = builder.length + super.render(builder, renderContext) + + val greyColor = ColorCompat.getThemedColor(context, R.b.colorTextMuted) + val gapWidth = 8.dp + val span = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) BulletSpan(gapWidth, greyColor, /* bulletRadius = */ 6) else BulletSpan(gapWidth, greyColor) + + builder.setSpan(span, length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(StyleSpan(Typeface.NORMAL), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/ClickableNode.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/ClickableNode.kt new file mode 100644 index 00000000..71f77602 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/ClickableNode.kt @@ -0,0 +1,26 @@ +package xyz.wingio.plugins.morehighlight.node + +import android.content.Context +import android.text.SpannableStringBuilder +import android.view.View +import com.discord.simpleast.core.node.Node +import com.discord.utilities.color.ColorCompat +import com.discord.utilities.spans.ClickableSpan +import com.lytefast.flexinput.R + +class ClickableNode( + private val content: String, + private val context: Context, + private val onClick: (View) -> Unit = { }, + private val onLongClick: (View) -> Unit = { } +): Node() { + + override fun render(builder: SpannableStringBuilder, renderContext: MessageRenderContext) { + val i = builder.length + builder.append(content) + + val clickableSpan = ClickableSpan(ColorCompat.getThemedColor(context, R.b.colorTextLink), false, onLongClick, onClick) + builder.setSpan(clickableSpan, i, builder.length, 33) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/ColorNode.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/ColorNode.kt new file mode 100644 index 00000000..7fd51504 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/ColorNode.kt @@ -0,0 +1,25 @@ +package xyz.wingio.plugins.morehighlight.node + +import android.graphics.Typeface +import android.text.SpannableStringBuilder +import android.text.style.BackgroundColorSpan +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import androidx.core.graphics.ColorUtils +import com.discord.simpleast.core.node.Node + +class ColorNode( + private var content: String, + private var color: Int +): Node() { + + override fun render(builder: SpannableStringBuilder, renderContext: MessageRenderContext) { + val length = builder.length + builder.append(content) + + builder.setSpan(ForegroundColorSpan(color), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(BackgroundColorSpan(ColorUtils.setAlphaComponent(color, 25)), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(StyleSpan(Typeface.BOLD), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/HeaderNode.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/HeaderNode.kt new file mode 100644 index 00000000..e7a5115b --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/HeaderNode.kt @@ -0,0 +1,31 @@ +package xyz.wingio.plugins.morehighlight.node + +import android.graphics.Typeface +import android.text.SpannableStringBuilder +import android.text.style.RelativeSizeSpan +import android.text.style.StyleSpan +import com.aliucord.PluginManager +import com.discord.simpleast.core.node.Node +import xyz.wingio.plugins.morehighlight.MoreHighlight + +class HeaderNode( + private val headerSize: Int +): Node.a() { + + override fun render(builder: SpannableStringBuilder, renderContext: MessageRenderContext) { + val length = builder.length + super.render(builder, renderContext) + + val scaleFactor = PluginManager.plugins["MoreHighlight"]!!.settings.getFloat(MoreHighlight.PREF_HEADER_SIZE, 1.0f) + val proportion = when (headerSize) { + 1 -> 2.0f * scaleFactor + 2 -> 1.5f * scaleFactor + 3 -> 1.25f * scaleFactor + else -> 1f + } + + builder.setSpan(RelativeSizeSpan(proportion), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(StyleSpan(Typeface.BOLD), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + } + +} diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/SlashCommandNode.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/SlashCommandNode.kt new file mode 100644 index 00000000..37175e50 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/SlashCommandNode.kt @@ -0,0 +1,37 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.morehighlight.node + +import android.graphics.Typeface +import android.text.SpannableStringBuilder +import android.text.style.BackgroundColorSpan +import android.text.style.StyleSpan +import android.view.View +import androidx.core.graphics.ColorUtils +import com.discord.simpleast.core.node.Node +import com.discord.stores.StoreStream +import com.discord.utilities.color.ColorCompat +import com.discord.utilities.spans.ClickableSpan +import com.discord.utilities.textprocessing.MessageRenderContext +import com.lytefast.flexinput.R + +class SlashCommandNode( + private val content: String +): Node() { + + override fun render(builder: SpannableStringBuilder, renderContext: MessageRenderContext) { + val color = ColorCompat.getThemedColor(renderContext.context, R.b.colorTextLink) + val length = builder.length + + builder.append("/$content") + + val clickableSpan = ClickableSpan(color, false, {}) { + StoreStream.getChat().replaceChatText("/$content") + } + + builder.setSpan(clickableSpan, length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(BackgroundColorSpan(ColorUtils.setAlphaComponent(color, 25)), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(StyleSpan(Typeface.BOLD), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/SubtextNode.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/SubtextNode.kt new file mode 100644 index 00000000..a115c594 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/SubtextNode.kt @@ -0,0 +1,24 @@ +package xyz.wingio.plugins.morehighlight.node + +import android.content.Context +import android.text.SpannableStringBuilder +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import com.discord.simpleast.core.node.Node +import com.discord.utilities.color.ColorCompat +import com.lytefast.flexinput.R + +class SubtextNode( + private val context: Context +): Node.a() { + + override fun render(builder: SpannableStringBuilder, renderContext: MessageRenderContext) { + val length = builder.length + super.render(builder, renderContext) + + val mutedColor = ColorCompat.getThemedColor(context, R.b.colorTextMuted) + builder.setSpan(RelativeSizeSpan(0.85f), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + builder.setSpan(ForegroundColorSpan(mutedColor), length, builder.length, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE) + } + +} diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/TextNode.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/TextNode.kt new file mode 100644 index 00000000..2ad7a940 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/node/TextNode.kt @@ -0,0 +1,14 @@ +package xyz.wingio.plugins.morehighlight.node + +import android.text.SpannableStringBuilder +import com.discord.simpleast.core.node.Node + +class TextNode( + private val content: String +): Node() { + + override fun render(builder: SpannableStringBuilder, renderContext: MessageRenderContext) { + builder.append(content) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/AliuRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/AliuRule.kt new file mode 100644 index 00000000..d8f7388f --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/AliuRule.kt @@ -0,0 +1,60 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.morehighlight.rule + +import android.content.Context +import androidx.fragment.app.FragmentManager +import com.aliucord.PluginManager +import com.aliucord.Utils +import com.aliucord.Utils.openPageWithProxy +import com.aliucord.entities.Plugin +import com.aliucord.utils.ReflectUtils +import com.discord.simpleast.core.node.Node +import com.discord.simpleast.core.parser.ParseSpec +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.node.ClickableNode +import xyz.wingio.plugins.morehighlight.node.TextNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class AliuRule( + private val context: Context +): Rule, MessageParseState>( + Pattern.compile("^ac://([A-Za-z0-9$]+)") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState?>, + s: MessageParseState? + ): ParseSpec { + val pluginSettings = PluginManager.plugins[matcher.group(1)]?.settingsTab + ?: return ParseSpec(TextNode(matcher.group(0)!!), s) + + val node: Node = ClickableNode(matcher.group(1)!!, context, { + when (pluginSettings.type) { + Plugin.SettingsTab.Type.PAGE -> { + val page = if (pluginSettings.args != null) ReflectUtils.invokeConstructorWithArgs(pluginSettings.page, *pluginSettings.args) else pluginSettings.page.newInstance() + openPageWithProxy(context, page) + } + + Plugin.SettingsTab.Type.BOTTOM_SHEET -> { + val sheet = if (pluginSettings.args != null) ReflectUtils.invokeConstructorWithArgs(pluginSettings.bottomSheet, *pluginSettings.args) else pluginSettings.bottomSheet.newInstance() + sheet.show(fragmentManager, pluginSettings::class.simpleName) + } + + else -> {} + } + }, {}) + + return ParseSpec(node, s) + } + + companion object { + private val fragmentManager: FragmentManager = Utils.appActivity.supportFragmentManager + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/BulletPointRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/BulletPointRule.kt new file mode 100644 index 00000000..9178b173 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/BulletPointRule.kt @@ -0,0 +1,27 @@ +package xyz.wingio.plugins.morehighlight.rule + +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.BlockRule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.node.BulletPointNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class BulletPointRule( + private val context: Context +): BlockRule, MessageParseState>( + Pattern.compile("^\\s*([*-])\\s+(.+)(?=\\n|$)") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState?>, + s: MessageParseState? + ): ParseSpec { + return ParseSpec(BulletPointNode(context), s, matcher.start(2), matcher.end(2)) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/ColorRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/ColorRule.kt new file mode 100644 index 00000000..0c8e773b --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/ColorRule.kt @@ -0,0 +1,30 @@ +package xyz.wingio.plugins.morehighlight.rule + +import android.graphics.Color +import com.discord.simpleast.core.parser.ParseSpec +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.node.ColorNode +import xyz.wingio.plugins.morehighlight.node.TextNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class ColorRule: Rule, MessageParseState>( + Pattern.compile("^#[0-9a-fA-F]{6,8}") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState>, + s: MessageParseState + ): ParseSpec { + return try { + ParseSpec(ColorNode(matcher.group(0)!!, Color.parseColor(matcher.group(0))), s) + } catch (_: Exception) { + ParseSpec(TextNode(matcher.group(0)!!), s) + } + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/HeaderRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/HeaderRule.kt new file mode 100644 index 00000000..1c1c90de --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/HeaderRule.kt @@ -0,0 +1,24 @@ +package xyz.wingio.plugins.morehighlight.rule + +import com.discord.simpleast.core.parser.ParseSpec +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule.BlockRule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.node.HeaderNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class HeaderRule: BlockRule, MessageParseState>( + Pattern.compile("^\\s*(##?#?)\\s+(.+)(?=\\n|$)") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState?>, + s: MessageParseState? + ): ParseSpec { + return ParseSpec(HeaderNode(matcher.group(1)!!.length), s, matcher.start(2), matcher.end(2)) + } + +} diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/IssueRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/IssueRule.kt new file mode 100644 index 00000000..575aec66 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/IssueRule.kt @@ -0,0 +1,35 @@ +package xyz.wingio.plugins.morehighlight.rule + +import android.content.Context +import com.aliucord.PluginManager +import com.aliucord.Utils +import com.discord.simpleast.core.parser.ParseSpec +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.MoreHighlight +import xyz.wingio.plugins.morehighlight.node.ClickableNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class IssueRule( + private val context: Context +): Rule, MessageParseState>( + Pattern.compile("^<([A-Za-z0-9-]{1,39})/([A-Za-z0-9-]{1,39})#([0-9]+)>") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState>, + s: MessageParseState + ): ParseSpec { + + val showRepo = PluginManager.plugins["MoreHighlight"]!!.settings.getBool(MoreHighlight.PREF_SHOW_REPO_NAME, false) + val url = "https://github.com/%s/%s/issues/%s".format(matcher.group(1), matcher.group(2), matcher.group(3)) + + val clickableNode = ClickableNode((if (showRepo) matcher.group(2)!! + "#" else "#") + matcher.group(3), context, { Utils.launchUrl(url) }, {}) + return ParseSpec(clickableNode, s) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/RedditRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/RedditRule.kt new file mode 100644 index 00000000..3874055d --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/RedditRule.kt @@ -0,0 +1,31 @@ +package xyz.wingio.plugins.morehighlight.rule + +import android.content.Context +import com.aliucord.Utils +import com.discord.simpleast.core.parser.ParseSpec +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.node.ClickableNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class RedditRule( + private val context: Context +): Rule, MessageParseState>( + Pattern.compile("^<([ur])/([a-zA-Z0-9_]{3,20})>") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState>, + s: MessageParseState + ): ParseSpec { + val url = "https://reddit.com/%s/%s".format(matcher.group(1), matcher.group(2)) + + val clickableNode = ClickableNode(matcher.group(1)!! + "/" + matcher.group(2), context, { Utils.launchUrl(url) }, {}) + return ParseSpec(clickableNode, s) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/RepoRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/RepoRule.kt new file mode 100644 index 00000000..b89c1742 --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/RepoRule.kt @@ -0,0 +1,31 @@ +package xyz.wingio.plugins.morehighlight.rule + +import android.content.Context +import com.aliucord.Utils +import com.discord.simpleast.core.parser.ParseSpec +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.node.ClickableNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class RepoRule( + private val context: Context +): Rule, MessageParseState>( + Pattern.compile("^") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState>, + s: MessageParseState + ): ParseSpec { + val url = "https://github.com/%s/%s".format(matcher.group(1), matcher.group(2)) + + val clickableNode = ClickableNode(matcher.group(1)!! + "/" + matcher.group(2), context, { Utils.launchUrl(url) }, {}) + return ParseSpec(clickableNode, s) + } + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/SlashCommandRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/SlashCommandRule.kt new file mode 100644 index 00000000..46a3938a --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/SlashCommandRule.kt @@ -0,0 +1,24 @@ +package xyz.wingio.plugins.morehighlight.rule + +import com.discord.simpleast.core.parser.ParseSpec +import com.discord.simpleast.core.parser.Parser +import com.discord.simpleast.core.parser.Rule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.node.ClickableNode +import xyz.wingio.plugins.morehighlight.node.SlashCommandNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class SlashCommandRule: Rule, MessageParseState>( + Pattern.compile("^") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState>, + s: MessageParseState + ): ParseSpec + = ParseSpec(SlashCommandNode(matcher.group(1)!!), s) + +} \ No newline at end of file diff --git a/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/SubtextRule.kt b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/SubtextRule.kt new file mode 100644 index 00000000..6d0e1a2e --- /dev/null +++ b/MoreHighlight/src/main/java/xyz/wingio/plugins/morehighlight/rule/SubtextRule.kt @@ -0,0 +1,28 @@ +package xyz.wingio.plugins.morehighlight.rule + +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.BlockRule +import com.discord.utilities.textprocessing.MessageParseState +import com.discord.utilities.textprocessing.MessageRenderContext +import xyz.wingio.plugins.morehighlight.node.BulletPointNode +import xyz.wingio.plugins.morehighlight.node.SubtextNode +import java.util.regex.Matcher +import java.util.regex.Pattern + +class SubtextRule( + private val context: Context +): BlockRule, MessageParseState>( + Pattern.compile("^\\s*(-#)\\s+(.+)(?=\\n|$)") +) { + + override fun parse( + matcher: Matcher, + parser: Parser?, MessageParseState?>, + s: MessageParseState + ): ParseSpec { + return ParseSpec(SubtextNode(context), s, matcher.start(2), matcher.end(2)) + } + +} diff --git a/MoreHighlight/src/res/drawable/ic_rules_24dp.xml b/MoreHighlight/src/res/drawable/ic_rules_24dp.xml deleted file mode 100644 index d9e780a1..00000000 --- a/MoreHighlight/src/res/drawable/ic_rules_24dp.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file diff --git a/ShowPermsV2/.gitignore b/ShowPermsV2/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/ShowPermsV2/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/ShowPermsV2/build.gradle.kts b/ShowPermsV2/build.gradle.kts new file mode 100644 index 00000000..2afc6444 --- /dev/null +++ b/ShowPermsV2/build.gradle.kts @@ -0,0 +1,2 @@ +version = "2.0.0" +description = "Shows user permissions in the profile sheet" \ No newline at end of file diff --git a/ShowPermsV2/src/main/AndroidManifest.xml b/ShowPermsV2/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ec117435 --- /dev/null +++ b/ShowPermsV2/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/ShowPerms.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/ShowPerms.kt new file mode 100644 index 00000000..195d4f28 --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/ShowPerms.kt @@ -0,0 +1,26 @@ +package xyz.wingio.plugins.showperms + +import android.content.Context +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin +import xyz.wingio.plugins.showperms.patch.addPermissionSection +import xyz.wingio.plugins.showperms.screen.SettingsScreen +import xyz.wingio.plugins.showperms.util.Settings + +@AliucordPlugin +class ShowPerms: Plugin() { + + init { + settingsTab = SettingsTab(SettingsScreen::class.java) + } + + override fun start(context: Context) { + Settings.migrate() + patcher.addPermissionSection() + } + + override fun stop(context: Context) { + patcher.unpatchAll() + } + +} \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/patch/AddPermissionSection.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/patch/AddPermissionSection.kt new file mode 100644 index 00000000..54faac0f --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/patch/AddPermissionSection.kt @@ -0,0 +1,151 @@ +@file:Suppress( + "MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS", + "MISSING_DEPENDENCY_SUPERCLASS_WARNING", "ERROR_SUPPRESSION" +) + +package xyz.wingio.plugins.showperms.patch + +import android.annotation.SuppressLint +import android.view.View +import android.widget.LinearLayout +import android.widget.LinearLayout.LayoutParams +import android.widget.LinearLayout.LayoutParams.MATCH_PARENT +import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT +import android.widget.TextView +import com.google.android.material.chip.ChipGroup + +import com.aliucord.Utils +import com.aliucord.api.PatcherAPI +import com.aliucord.patcher.after +import com.aliucord.patcher.component1 +import com.aliucord.patcher.component2 +import com.aliucord.utils.DimenUtils.dp +import com.aliucord.utils.ReflectUtils +import com.aliucord.wrappers.ChannelWrapper.Companion.name +import com.aliucord.wrappers.ChannelWrapper.Companion.permissionOverwrites +import com.aliucord.wrappers.GuildRoleWrapper.Companion.color +import com.aliucord.wrappers.GuildRoleWrapper.Companion.name +import com.aliucord.wrappers.GuildRoleWrapper.Companion.position + +import com.discord.databinding.WidgetUserSheetBinding +import com.discord.stores.StoreStream +import com.discord.widgets.user.usersheet.WidgetUserSheet +import com.discord.widgets.user.usersheet.WidgetUserSheetViewModel + +import xyz.wingio.plugins.showperms.util.Format +import xyz.wingio.plugins.showperms.view.PermChip +import xyz.wingio.plugins.showperms.util.Permission +import xyz.wingio.plugins.showperms.util.PermUtil +import xyz.wingio.plugins.showperms.util.PermUtil.applyOverwrites +import xyz.wingio.plugins.showperms.util.Settings +import xyz.wingio.plugins.showperms.util.sortedBy + +import java.util.ArrayList +import com.lytefast.flexinput.R + +private val sectionId = View.generateViewId() +private val contentId = Utils.getResId("user_sheet_content", "id") +private val guildHeaderId = Utils.getResId("user_sheet_guild_header", "id") +private val connectionsHeaderId = Utils.getResId("user_sheet_connections_header", "id") + +@SuppressLint("SetTextI18n") +fun PatcherAPI.addPermissionSection() { + after( + "configureGuildSection", + WidgetUserSheetViewModel.ViewState.Loaded::class.java + ) { (_, viewState: WidgetUserSheetViewModel.ViewState.Loaded) -> + if (viewState.guildMember == null) return@after + + // Views + val binding = ReflectUtils.invokeMethod(this, "getBinding") as WidgetUserSheetBinding + val content = binding.root.findViewById(contentId) + val guildHeader = content.findViewById(guildHeaderId) + val connectionsHeader = content.findViewById(connectionsHeaderId) + + val context = content.context + + // Relevant information + val guild = StoreStream.getGuilds().getGuild(viewState.guildId)!! + val guildRoles = StoreStream.getGuilds().roles[guild.id] ?: return@after + val userRoles = ArrayList(viewState.roleItems!!) + val applyOverwrites = Settings.applyOverwrites && viewState.channel != null + if (guildRoles.containsKey(guild.id)) userRoles.add(guildRoles[guild.id]) // Adds the implicit @everyone role + + if (Settings.showRoleCount && userRoles.isNotEmpty()) { + guildHeader.text = "${guild.name} • ${userRoles.size} roles" + } + + // Creating the permissions section + // ================================ + + val section = LinearLayout(context).apply { + id = sectionId + orientation = LinearLayout.VERTICAL + layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { + setMargins(16.dp, 0, 16.dp, 0) + } + } + if (content.findViewById(sectionId) == null) content.addView( + section, + content.indexOfChild(connectionsHeader) + ) + + TextView(context, null, 0, R.i.UserProfile_Section_Header).apply { + text = "Permissions${if (applyOverwrites) " - #${viewState.channel.name}" else ""}" + layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { + setMargins(0, 0, 0, 8.dp) + } + section.addView(this) + } + + val permList = ChipGroup(context).apply { + chipSpacingVertical = 4.dp + chipSpacingHorizontal = 4.dp + addView(PermChip.detailsButton(context)) + section.addView(this) + } + + if (guild.isOwner(viewState.user.id)) { + PermChip(context).apply { + setPermName("Server Owner") + permList.addView(this) + } + return@after // Permissions are irrelevant for server owners + } + + // Obtain, sort, and filter the user's permissions + // =============================================== + + PermUtil.getRolePermissions(userRoles) + .let { perms -> + if (applyOverwrites) perms.applyOverwrites( + overwrites = viewState.channel.permissionOverwrites, + roles = userRoles, + userId = viewState.user.id + ) else perms + } + .let { perms -> // Filter based on user settings + when (Settings.format) { + Format.Default -> perms + Format.MinAdmin -> perms.filter { (perm) -> perm == Permission.ADMINISTRATOR }.ifEmpty { perms } + Format.FullAdmin -> { + perms + .firstOrNull { (perm) -> perm == Permission.ADMINISTRATOR } + ?.let { (_, role) -> Permission.values().map { it to role } } + ?: perms + } + } + } + .sortedBy(Settings.permissionNameSort) { (perm) -> perm.displayName } // Alphabetically + .sortedBy(Settings.rolePosSort) { (_, role) -> role.position } // Highest to lowest role + .forEach { (perm, role) -> + permList.addView( + PermChip(context).apply { + setPermName(perm.displayName) + setDotColor(role.color) + setOnClickListener { Utils.showToast(role.name) } + } + ) + } + } +} \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/screen/SettingsScreen.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/screen/SettingsScreen.kt new file mode 100644 index 00000000..c7c86c1e --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/screen/SettingsScreen.kt @@ -0,0 +1,174 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS", "MISSING_DEPENDENCY_SUPERCLASS_WARNING") + +package xyz.wingio.plugins.showperms.screen + +import android.content.Context +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import com.aliucord.Utils +import com.aliucord.fragments.SettingsPage +import com.aliucord.utils.DimenUtils.dp +import com.aliucord.views.Divider +import com.discord.views.CheckedSetting +import com.discord.views.RadioManager +import com.lytefast.flexinput.R +import xyz.wingio.plugins.showperms.util.Settings + +@Suppress("SetTextI18n") +class SettingsScreen: SettingsPage() { + + override fun onViewBound(view: View) { + super.onViewBound(view) + + setActionBarTitle("ShowPerms") + setActionBarSubtitle("Settings") + setPadding(0) + + val ctx = view.context + val layout = linearLayout.apply { setPadding(0, 0, 0, 0) } + + layout.addView( + createSwitch( + context = ctx, + label = "Show Dot", + sublabel = "Display a colored dot in the permission chip to indicate the role that grants this permission", + default = Settings.showDot + ) { Settings.showDot = it } + ) + + layout.addView( + createSwitch( + context = ctx, + label = "Show Role Count", + sublabel = "Display the number of roles a user has next to the server name", + default = Settings.showRoleCount + ) { Settings.showRoleCount = it } + ) + + layout.addView( + createSwitch( + context = ctx, + label = "Apply Channel Overwrites", + sublabel = "Apply channel-specific permission overwrites. Keep off to show global permissions", + default = Settings.applyOverwrites + ) { Settings.applyOverwrites = it } + ) + + layout.addView(Divider(ctx)) + + TextView(context, null, 0, R.i.UiKit_Settings_Item_Header).apply { + text = "Format" + setPadding(16.dp, 24.dp, 16.dp, 16.dp) + layout.addView(this) + } + + layout.createRadioGroup( + labels = mapOf( + "Default" to null, + "Show All Permissions for Admin" to "Show every permission if at least one of the roles has admin permissions.", + "Show Only Admin" to "If the user is admin only show the Administrator permission" + ), + default = Settings.format + ) { + Settings.format = it + } + + layout.addView(Divider(ctx)) + + TextView(context, null, 0, R.i.UiKit_Settings_Item_Header).apply { + text = "Sort by Name" + setPadding(16.dp, 24.dp, 16.dp, 16.dp) + layout.addView(this) + } + + layout.createRadioGroup( + labels = mapOf( + "A to Z" to null, + "Z to A" to null, + ), + default = Settings.permissionNameSort + ) { + Settings.permissionNameSort = it + } + + layout.addView(Divider(ctx)) + + TextView(context, null, 0, R.i.UiKit_Settings_Item_Header).apply { + text = "Sort by Role Position" + setPadding(16.dp, 24.dp, 16.dp, 16.dp) + layout.addView(this) + } + + layout.createRadioGroup( + labels = mapOf( + "Lowest to Highest" to null, + "Highest to Lowest" to null, + ), + default = Settings.rolePosSort + ) { + Settings.rolePosSort = it + } + } + + private fun createSwitch( + context: Context, + label: String, + sublabel: String, + default: Boolean, + onChecked: (Boolean) -> Unit + ): CheckedSetting { + return Utils.createCheckedSetting( + context = context, + type = CheckedSetting.ViewType.SWITCH, + text = label, + subtext = sublabel + ).apply { + l.b().run { setPadding(0, 16.dp, 16.dp, 16.dp) } + isChecked = default + setPadding(0, 0, 0, 0) + setOnCheckedListener(onChecked) + } + } + + private fun createRadio( + context: Context, + label: String, + sublabel: String?, + default: Boolean + ): CheckedSetting { + return Utils.createCheckedSetting( + context = context, + type = CheckedSetting.ViewType.RADIO, + text = label, + subtext = sublabel + ).apply { + l.b().run { setPadding(0, 16.dp, 16.dp, 16.dp) } + isChecked = default + setPadding(0, 0, 0, 0) + } + } + + private inline fun > LinearLayout.createRadioGroup( + labels: Map, + default: E, + crossinline onSelected: (E) -> Unit + ) { + val radios = enumValues().map { labels.entries.toList()[it.ordinal] }.mapIndexed { i, (label, sublabel) -> + createRadio(context, label, sublabel, i == default.ordinal) + } + + val radioManager = RadioManager(radios) + + for (i in radios.indices) { + val radio = radios[i] + radio.e { + onSelected(enumValues()[i]) + radioManager.a(radio) + } + addView(radio) + if (i == default.ordinal) radioManager.a(radio) + } + } + +} \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/ListUtil.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/ListUtil.kt new file mode 100644 index 00000000..3b230094 --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/ListUtil.kt @@ -0,0 +1,19 @@ +package xyz.wingio.plugins.showperms.util + +/** + * Returns a list of elements sorted according to either their + * natural ascending or descending sort order + * + * @param sortOrder The order in which to sort items, either ascending or descending + * @param selector Used to determine what the items should be sorted by + * @return A sorted list + */ +inline fun > Collection.sortedBy( + sortOrder: SortOrder, + crossinline selector: (T) -> R? +): List { + return when (sortOrder) { + SortOrder.Ascending -> sortedBy(selector) + SortOrder.Descending -> sortedByDescending(selector) + } +} \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/PermUtil.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/PermUtil.kt new file mode 100644 index 00000000..b872f263 --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/PermUtil.kt @@ -0,0 +1,125 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS", "MISSING_DEPENDENCY_SUPERCLASS_WARNING") + +package xyz.wingio.plugins.showperms.util + +import com.aliucord.utils.ReflectUtils +import com.aliucord.utils.lazyField +import com.aliucord.wrappers.GuildRoleWrapper.Companion.position +import com.aliucord.wrappers.PermissionOverwriteWrapper.Companion.allowed +import com.aliucord.wrappers.PermissionOverwriteWrapper.Companion.denied +import com.aliucord.wrappers.PermissionOverwriteWrapper.Companion.id +import com.aliucord.wrappers.PermissionOverwriteWrapper.Companion.type +import com.discord.api.permission.PermissionOverwrite +import com.discord.api.role.GuildRole +import com.discord.models.user.User +import com.discord.stores.StoreStream + +object PermUtil { + + private val permsField by lazyField("permissions") + + /** + * Creates a map containing every permission and the role that grants it. + * + * Each permission will only be attributed to one role, specifically the lowest role that grants it. + * This means that the color associated with the permission will be for the role that actually adds the permission. + * + * @param roles Roles belonging to a particular guild member + * @return A map of permissions to roles, represented as a list for easier filtering and altering + */ + fun getRolePermissions(roles: List): List> { + val rolePerms = mutableListOf>() + for (role in roles.reversed()) { + val permBitfield = permsField[role] as? Long ?: continue + val perms = getPermissions(permBitfield) + + for (permission in perms) { + if (rolePerms.any { (perm) -> perm == permission }) continue + rolePerms.add(permission to role) + } + } + + return rolePerms + } + + /** + * Applies channel-specific permission overwrites to the base permissions obtained using [getRolePermissions] + * + * @param overwrites The permission overwrites for a given channel + * @param roles The roles belonging to the guild member + * @param userId The user id of the guild member + * + * @return Updated map of permissions to roles, represented as a list for easier filtering and altering + */ + fun List>.applyOverwrites( + overwrites: List, + roles: List, + userId: Long + ): List> { + val rolePerms = this.toMutableList() + val roleOverwrites = overwrites + .filter { it.type == PermissionOverwrite.Type.ROLE && it.id in roles.map { r -> r.id } } // Only get relevant role overwrites + .sortedBy { // Overwrites must be applied in ascending order of role position, starting with @everyone + roles.first { r -> r.id == it.id }.position + } + + roleOverwrites.forEach { overwrite -> + rolePerms.applyOverwrite(overwrite, roles.first { it.id == overwrite.id }) + } + + overwrites.find { it.id == userId }?.let { // After role overwrites are applied, we apply the member-specific overwrites + val user: User? = StoreStream.getUsers().users.getOrDefault(userId, null) + rolePerms.applyOverwrite(it, createFakeRole(if (user == null) "Member Overwrite" else "@${user.username}")) + } + + return rolePerms + } + + /** + * Parses a 64 bit integer bitfield into a list of permissions + * + * @param permissionBits A 64 bit integer representing a set of permissions + * @return List of [Permissions][Permission] + */ + private fun getPermissions(permissionBits: Long): List { + val list = mutableListOf() + Permission.values().forEach { + val bit = 1L shl it.ordinal // The ordinal is used as the shift amount for convenience + if (permissionBits and bit == bit) list.add(it) + } + return list + } + + /** + * Applies an individual overwrite to the previously obtained base permissions + * + * @param overwrite The overwrite to apply + * @param role The role to attribute any new permissions to + */ + private fun MutableList>.applyOverwrite(overwrite: PermissionOverwrite, role: GuildRole) { + val denied = getPermissions(overwrite.denied) + val allowed = getPermissions(overwrite.allowed) + + removeIf { (perm) -> perm in denied } + for (allowedPerm in allowed) { + if (any { (perm) -> perm == allowedPerm }) continue + add(allowedPerm to role) + } + } + + /** + * Creates a [GuildRole] class using reflection. This fake role has no color, + * and is always set to the highest possible position so that it appears above + * all actual roles. + * + * @param name The name to give this fake role + */ + private fun createFakeRole(name: String): GuildRole { + return ReflectUtils.allocateInstance(GuildRole::class.java).apply { + ReflectUtils.setField(this, "name", name) + ReflectUtils.setField(this, "color", 0) + ReflectUtils.setField(this, "position", Int.MAX_VALUE) + } + } + +} \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/Permission.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/Permission.kt new file mode 100644 index 00000000..a94b0009 --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/Permission.kt @@ -0,0 +1,66 @@ +package xyz.wingio.plugins.showperms.util + +/** + * A permission that can be assigned to a role, or overwritten in a channel. + * + * The order here matters as the ordinal is used as the amount of bits to shift when + * calculating permissions from the bitfield. + * + * @param displayName Human-readable name for the permission + */ +enum class Permission( + val displayName: String +) { + CREATE_INSTANT_INVITE("Create Invite"), + KICK_MEMBERS("Kick, Approve, and Reject Members"), + BAN_MEMBERS("Ban Members"), + ADMINISTRATOR("Administrator"), + MANAGE_CHANNELS("Manage Channels"), + MANAGE_GUILD("Manage Server"), + ADD_REACTIONS("Add Reactions"), + VIEW_AUDIT_LOG("View Audit Log"), + PRIORITY_SPEAKER("Priority Speaker"), + STREAM("Video"), + VIEW_CHANNEL("View Channels"), + SEND_MESSAGES("Send Messages"), + SEND_TTS_MESSAGES("Send Text-to-Speech Messages"), + MANAGE_MESSAGES("Manage Messages"), + EMBED_LINKS("Embed Links"), + ATTACH_FILES("Attach Files"), + READ_MESSAGE_HISTORY("Read Message History"), + MENTION_EVERYONE("Mention @everyone, @here, and All Roles"), + USE_EXTERNAL_EMOJIS("Use External Emoji"), + VIEW_GUILD_INSIGHTS("View Server Insights"), + CONNECT("Connect"), + SPEAK("Speak"), + MUTE_MEMBERS("Mute Members"), + DEAFEN_MEMBERS("Deafen Members"), + MOVE_MEMBERS("Move Members"), + USE_VAD("Use Voice Activity"), + CHANGE_NICKNAME("Change Nickname"), + MANAGE_NICKNAMES("Manage Nicknames"), + MANAGE_ROLES("Manage Roles"), + MANAGE_WEBHOOKS("Manage Webhooks"), + MANAGE_EXPRESSIONS("Manage Expressions"), + USE_APPLICATION_COMMANDS("Use Application Commands"), + REQUEST_TO_SPEAK("Request to Speak"), + MANAGE_EVENTS("Manage Events"), + MANAGE_THREADS("Manage Threads"), + CREATE_PUBLIC_THREADS("Create Public Threads"), + CREATE_PRIVATE_THREADS("Create Private Threads"), + USE_EXTERNAL_STICKERS("Use External Stickers"), + SEND_MESSAGES_IN_THREADS("Send Messages in Threads"), + USE_EMBEDDED_ACTIVITIES("Use Activities"), + MODERATE_MEMBERS("Timeout Members"), + VIEW_CREATOR_MONETIZATION_ANALYTICS("View Monetization Analytics"), + USE_SOUNDBOARD("Use Soundboard"), + CREATE_EXPRESSIONS("Create Expressions"), + CREATE_EVENTS("Create Events"), + USE_EXTERNAL_SOUNDS("Use External Sounds"), + SEND_VOICE_MESSAGES("Send Voice Messages"), + USE_CLYDE_AI("Use Clyde"), // Deprecated but here so that I can use the ordinal for the bit shift + SET_VOICE_CHANNEL_STATUS("Set Voice Channel Status"), + SEND_POLLS("Send Polls"), + USE_EXTERNAL_APPS("Use External Apps"), + PIN_MESSAGES("Pin Messages") +} \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/SettingDelegates.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/SettingDelegates.kt new file mode 100644 index 00000000..722baae8 --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/SettingDelegates.kt @@ -0,0 +1,32 @@ +package xyz.wingio.plugins.showperms.util + +import com.aliucord.api.SettingsAPI +import kotlin.reflect.KProperty + +class Setting( + private val getter: (String) -> T, + private val setter: (String, T) -> Unit, + private val key: String? = null +) { + + operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: T) { + setter(key ?: prop.name, value) + } + + operator fun getValue(thisRef: Any?, prop: KProperty<*>): T { + return getter(key ?: prop.name) + } + +} + +fun SettingsAPI.boolean(default: Boolean, key: String? = null) = Setting( + getter = { k -> getBool(k, default) }, + setter = { k, v -> setBool(k, v) }, + key = key +) + +inline fun > SettingsAPI.enum(default: E, key: String? = null) = Setting( + getter = { k -> enumValues()[getInt(k, default.ordinal)] }, + setter = { k, v -> setInt(k, v.ordinal) }, + key = key +) \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/Settings.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/Settings.kt new file mode 100644 index 00000000..822d315d --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/util/Settings.kt @@ -0,0 +1,38 @@ +package xyz.wingio.plugins.showperms.util + +import com.aliucord.PluginManager + +object Settings { + + private val settings by lazy { PluginManager.plugins["ShowPermsV2"]!!.settings } + + var format by settings.enum(Format.Default) + + // Sort + var permissionNameSort by settings.enum(SortOrder.Ascending) + var rolePosSort by settings.enum(SortOrder.Descending) + + // Toggles + var showDot by settings.boolean(true) + var showRoleCount by settings.boolean(true) + var applyOverwrites by settings.boolean(false) + + fun migrate() { + if (settings.exists("invertOrder")) { + rolePosSort = if (settings.getBool("invertOrder", false)) SortOrder.Ascending else SortOrder.Descending + settings.remove("invertOrder") + } + } + +} + +enum class Format { + Default, + FullAdmin, + MinAdmin +} + +enum class SortOrder { + Ascending, + Descending +} \ No newline at end of file diff --git a/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/view/PermChip.kt b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/view/PermChip.kt new file mode 100644 index 00000000..247537f6 --- /dev/null +++ b/ShowPermsV2/src/main/kotlin/xyz/wingio/plugins/showperms/view/PermChip.kt @@ -0,0 +1,89 @@ +package xyz.wingio.plugins.showperms.view + +import android.content.Context +import android.graphics.Color +import android.view.Gravity +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.aliucord.utils.DimenUtils.dp +import com.discord.utilities.color.ColorCompat +import com.google.android.material.card.MaterialCardView +import com.lytefast.flexinput.R +import xyz.wingio.plugins.showperms.util.Settings + +/** + * A chip that is designed to emulate the appearance of the role chip + */ +class PermChip( + private val ctx: Context +): MaterialCardView(ctx) { + + private val label: TextView + private val roleDot: ImageView + + init { + layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + radius = 4.dp.toFloat() + setCardBackgroundColor(ColorCompat.getThemedColor(ctx, R.b.colorBackgroundTertiary)) + + val root = LinearLayout(ctx).apply { + setVerticalGravity(Gravity.CENTER_VERTICAL) + setPadding(8.dp, 6.dp, 8.dp, 6.dp) + orientation = LinearLayout.HORIZONTAL + layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + } + + label = TextView(ctx, null, 0, R.i.UiKit_TextAppearance_Semibold).apply { + textSize = 12f + maxLines = 1 + } + + roleDot = ImageView(ctx).apply { + layoutParams = LinearLayout.LayoutParams(12.dp, 12.dp).apply { + marginEnd = 8.dp + } + } + setDotColor(0) + + if (Settings.showDot) root.addView(roleDot) + root.addView(label) + addView(root) + } + + fun setPermName(name: String) { + label.text = name + } + + fun setDotColor(color: Int) { + val clr = if (color == 0) ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal) else Color.parseColor("#%06x".format(color)) + val dotIcon = ContextCompat.getDrawable(ctx, R.e.drawable_circle_white_12dp)!!.mutate() + dotIcon.setTint(clr) + roleDot.setImageDrawable(dotIcon) + } + + companion object { + + fun detailsButton(ctx: Context) = PermChip(ctx).apply { + val shieldIcon = ContextCompat.getDrawable(ctx, R.e.ic_shieldstar_24dp)!!.mutate() + shieldIcon.setTint(ColorCompat.getThemedColor(ctx, R.b.colorInteractiveNormal)) + + label.visibility = GONE + roleDot.apply { + setImageDrawable(shieldIcon) + (layoutParams as LinearLayout.LayoutParams).apply { + height = 16.dp + width = 16.dp + marginEnd = 0 + } + } + + setOnClickListener { + + } + } + + } + +} \ No newline at end of file diff --git a/UserTags/build.gradle.kts b/UserTags/build.gradle.kts index de77001c..0c82c18f 100644 --- a/UserTags/build.gradle.kts +++ b/UserTags/build.gradle.kts @@ -1,2 +1,2 @@ -version = "1.1.6" +version = "1.1.7" description = "Gives any user custom bot tags" \ No newline at end of file diff --git a/UserTags/src/main/java/xyz/wingio/plugins/UserTags.java b/UserTags/src/main/java/xyz/wingio/plugins/UserTags.java deleted file mode 100644 index 6aa6b5ab..00000000 --- a/UserTags/src/main/java/xyz/wingio/plugins/UserTags.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.aliucord.plugins; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.view.*; -import android.widget.*; -import android.os.*; - -import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.content.res.ResourcesCompat; -import androidx.core.widget.NestedScrollView; -import androidx.fragment.app.Fragment; - -import com.aliucord.Constants; -import com.aliucord.api.CommandsAPI; -import com.aliucord.Utils; -import com.aliucord.utils.*; -import com.aliucord.Logger; -import com.aliucord.PluginManager; -import com.aliucord.entities.Plugin; -import com.aliucord.patcher.Hook; -import com.aliucord.annotations.AliucordPlugin; -import com.discord.utilities.color.ColorCompat; -import com.discord.api.premium.PremiumTier; -import com.discord.api.user.User; -import com.discord.databinding.WidgetChatOverlayBinding; -import com.discord.databinding.WidgetGuildProfileSheetBinding; -import com.discord.databinding.WidgetChannelMembersListItemUserBinding; -import com.discord.databinding.UserProfileHeaderViewBinding; -import com.discord.utilities.viewbinding.FragmentViewBindingDelegate; -import com.discord.utilities.SnowflakeUtils; -import com.discord.utilities.time.ClockFactory; -import com.discord.utilities.time.TimeUtils; -import com.discord.utilities.user.UserUtils; -import com.discord.stores.StoreStream; -import com.discord.widgets.chat.*; -import com.discord.widgets.chat.input.*; -import com.discord.widgets.chat.overlay.WidgetChatOverlay$binding$2; -import com.discord.widgets.chat.list.adapter.*; -import com.discord.widgets.changelog.WidgetChangeLog; -import com.discord.widgets.guilds.profile.*; -import com.discord.widgets.channels.memberlist.adapter.*; -import com.discord.widgets.user.profile.UserProfileHeaderView; -import com.discord.widgets.user.profile.UserProfileHeaderViewModel; -import com.discord.utilities.icon.*; -import com.discord.utilities.guilds.*; -import com.discord.models.member.GuildMember; -import com.discord.models.guild.Guild; -import com.discord.models.user.CoreUser; -import com.discord.models.message.Message; -import com.discord.models.commands.ApplicationCommandOption; -import com.discord.api.commands.ApplicationCommandType; -import com.discord.api.commands.CommandChoice; -import com.lytefast.flexinput.R; - -import java.util.*; -import java.lang.reflect.*; - -import kotlin.jvm.functions.Function0; - -@AliucordPlugin -public class UserTags extends Plugin { - - public RelativeLayout overlay; - public Logger logger = new Logger("UserTags"); - - @Override - public void start(Context context) throws Throwable { - var id = View.generateViewId(); - var bindingField = ChannelMembersListViewHolderMember.class.getDeclaredField("binding"); - bindingField.setAccessible(true); - - patcher.patch(WidgetChatListAdapterItemMessage.class, "configureItemTag", new Class[] { Message.class, boolean.class }, new Hook(callFrame -> { - Message msg = (Message) callFrame.args[0]; - User author = msg.getAuthor(); - CoreUser coreUser = new CoreUser(author); - WidgetChatListAdapterItemMessage _this = (WidgetChatListAdapterItemMessage) callFrame.thisObject; - - try{ - boolean showTag = false; - TextView textView = (TextView) _this.itemView.findViewById(Utils.getResId("chat_list_adapter_item_text_tag", "id")); - String tag = settings.getString(String.valueOf(coreUser.getId()), null); - boolean verified = settings.getBool(coreUser.getId() + "_verified", false); - boolean isServer = (msg.getType() == 0 && msg.getMessageReference() != null); - boolean isOP = (boolean) callFrame.args[1]; - if (coreUser.getId() == 298295889720770563L || coreUser.isBot() || isOP || tag != null) { - showTag = true; - } - if(textView != null){ - textView.setVisibility(showTag ? View.VISIBLE : View.GONE); - textView.setText(isServer ? "SERVER" : coreUser.isBot() ? "BOT" : tag == null && isOP ? "OP" : String.valueOf(tag)); - if(coreUser.getId() == 298295889720770563L) { - textView.setText("DEV"); - } - if(UserUtils.INSTANCE.isVerifiedBot(coreUser) || coreUser.getId() == 298295889720770563L || verified == true) { - textView.setCompoundDrawablesWithIntrinsicBounds(R.e.ic_verified_10dp, 0, 0, 0); - } - } - } catch(Throwable e) { - logger.error("Error adding tag to message", e); - } - })); - - patcher.patch(ChannelMembersListViewHolderMember.class, "bind", new Class[]{ ChannelMembersListAdapter.Item.Member.class, Function0.class}, new Hook(callFrame -> { - try { - WidgetChannelMembersListItemUserBinding binding = (WidgetChannelMembersListItemUserBinding) bindingField.get(callFrame.thisObject); - ConstraintLayout layout = (ConstraintLayout) binding.getRoot(); - ChannelMembersListAdapter.Item.Member user = (ChannelMembersListAdapter.Item.Member) callFrame.args[0]; - String tag = settings.getString(String.valueOf(user.getUserId()), null); - boolean verified = settings.getBool(user.getUserId() + "_verified", false); - if(user.getUserId() == 298295889720770563L) { - tag = "DEV"; - } - if(tag != null && user.isBot() == false) { - TextView tagText = (TextView) layout.findViewById(Utils.getResId("username_tag", "id")); - tagText.setText(String.valueOf(tag)); - if(user.getUserId() == 298295889720770563L || verified == true) { - tagText.setCompoundDrawablesWithIntrinsicBounds(R.e.ic_verified_10dp, 0, 0, 0); - } - tagText.setVisibility(View.VISIBLE); - } - } catch(Throwable e) {logger.error("Error setting bot text in member list", e);} - })); - - var profileBinding = UserProfileHeaderView.class.getDeclaredField("binding"); - profileBinding.setAccessible(true); - - patcher.patch(UserProfileHeaderView.class, "updateViewState", new Class[]{ UserProfileHeaderViewModel.ViewState.Loaded.class }, new Hook(callFrame -> { - try { - UserProfileHeaderViewBinding binding = (UserProfileHeaderViewBinding) profileBinding.get(callFrame.thisObject); - - var user = ((UserProfileHeaderViewModel.ViewState.Loaded) callFrame.args[0]).getUser(); - var tag = settings.getString(String.valueOf(user.getId()), null); - boolean verified = settings.getBool(user.getId() + "_verified", false); - if(user.getId() == 298295889720770563L) { - tag = "UserTags Developer"; - } - if(tag != null && user.isBot() == false) { - TextView tagText = (TextView) binding.a.findViewById(Utils.getResId("username_tag", "id")); - tagText.setText(String.valueOf(tag)); - if(user.getId() == 298295889720770563L || verified == true) { - tagText.setCompoundDrawablesWithIntrinsicBounds(R.e.ic_verified_10dp, 0, 0, 0); - } - tagText.setVisibility(View.VISIBLE); - } - } catch(Throwable e) {logger.error("Error setting bot text in profile sheet", e);} - })); - - var userOption = Utils.createCommandOption(ApplicationCommandType.USER, "user", "User you want to give a tag to", null, true, true, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), false); - var labelOption = Utils.createCommandOption(ApplicationCommandType.STRING, "label", "The label for the tag", null, true, true, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), false); - var verifiedOption = Utils.createCommandOption(ApplicationCommandType.BOOLEAN, "verified", "Whether the tag should show as verified", null, false, true, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), false); - var setOption = Utils.createCommandOption(ApplicationCommandType.SUBCOMMAND, "set", "Set a tag", null, true, true, new ArrayList<>(), new ArrayList<>(), Arrays.asList(userOption, labelOption, verifiedOption), false); - var clearOption = Utils.createCommandOption(ApplicationCommandType.SUBCOMMAND, "clear", "Clear a tag", null, true, true, new ArrayList<>(), new ArrayList<>(), Arrays.asList(userOption), false); - - commands.registerCommand( - "usertags", - "Modify a tag for a particular user", - Arrays.asList(setOption, clearOption), - ctx -> { - if (ctx.containsArg("set")) { - var setargs = ctx.getSubCommandArgs("set"); - var user = (String) setargs.get("user"); - var label = (String) setargs.get("label"); - var verified = (Boolean) setargs.get("verified"); - verified = verified == null ? false : verified; - if ( user == null || user.equals("") || label == null || label.equals("")) { - return new CommandsAPI.CommandResult("Missing arguments",null,false); - } - - settings.setString(user, String.valueOf(label)); - settings.setBool(user + "_verified", verified); - - return new CommandsAPI.CommandResult("Set tag", null, false); - } - - if (ctx.containsArg("clear")) { - var setargs = ctx.getSubCommandArgs("clear"); - var user = (String) setargs.get("user"); - if (user == null || user.equals("")) { - return new CommandsAPI.CommandResult("Missing arguments",null,false); - } - - settings.setString(user, null); - - return new CommandsAPI.CommandResult("Cleared tag", null, false); - } - - return new CommandsAPI.CommandResult(); - } - ); - } - - @Override - public void stop(Context context) { - patcher.unpatchAll(); - commands.unregisterAll(); - } -} diff --git a/UserTags/src/main/java/xyz/wingio/plugins/usertags/Command.kt b/UserTags/src/main/java/xyz/wingio/plugins/usertags/Command.kt new file mode 100644 index 00000000..002f5466 --- /dev/null +++ b/UserTags/src/main/java/xyz/wingio/plugins/usertags/Command.kt @@ -0,0 +1,95 @@ +package xyz.wingio.plugins.usertags + +import com.aliucord.PluginManager +import com.aliucord.Utils +import com.aliucord.api.CommandsAPI +import com.aliucord.api.CommandsAPI.CommandResult +import com.aliucord.entities.CommandContext +import com.discord.api.commands.ApplicationCommandType +import com.discord.models.commands.ApplicationCommandOption + +fun CommandsAPI.registerCommand() { + registerCommand( + "usertags", + "Modify a tag for a particular user", + listOf(setOption, clearOption) + ) { ctx: CommandContext -> + when { + ctx.containsArg("set") -> tagSet(ctx) + ctx.containsArg("clear") -> tagClear(ctx) + else -> CommandResult() + } + } +} + +private fun tagSet(ctx: CommandContext): CommandResult { + val settings = PluginManager.plugins["UserTags"]!!.settings + val setArgs = ctx.getSubCommandArgs("set") + + val user = setArgs!!["user"] as String? + val label = setArgs["label"] as String? + val verified = setArgs["verified"] as Boolean? ?: false + + if (user == null || user == "" || label == null || label == "") { + return CommandResult("Missing arguments", null, false) + } + + settings.setString(user, label.toString()) + settings.setBool(user + "_verified", verified) + return CommandResult("Set tag", null, false) +} + +private fun tagClear(ctx: CommandContext): CommandResult { + val settings = PluginManager.plugins["UserTags"]!!.settings + val clearArgs = ctx.getSubCommandArgs("clear") + val user = clearArgs!!["user"] as String? + + if (user == null || user == "") { + return CommandResult("Missing arguments", null, false) + } + + settings.setString(user, null) + return CommandResult("Cleared tag", null, false) +} + +private val userOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.USER, + name = "user", + description = "User you want to give a tag to", + required = true, + default = true, +) + +private val labelOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.STRING, + name = "label", + description = "The label for the tag", + required = true, + default = true, +) + +private val verifiedOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.BOOLEAN, + name = "verified", + description = "Whether the tag should show as verified", + required = false, + default = true, +) + +private val setOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.SUBCOMMAND, + name = "set", + description = "Set a tag", + required = true, + default = true, + subCommandOptions = listOf(userOption, labelOption, verifiedOption), +) + +private val clearOption: ApplicationCommandOption = Utils.createCommandOption( + type = ApplicationCommandType.SUBCOMMAND, + name = "clear", + description = "Clear a tag", + required = true, + default = true, + subCommandOptions = listOf(userOption), +) \ No newline at end of file diff --git a/UserTags/src/main/java/xyz/wingio/plugins/usertags/Patches.kt b/UserTags/src/main/java/xyz/wingio/plugins/usertags/Patches.kt new file mode 100644 index 00000000..9db14138 --- /dev/null +++ b/UserTags/src/main/java/xyz/wingio/plugins/usertags/Patches.kt @@ -0,0 +1,92 @@ +@file:Suppress("MISSING_DEPENDENCY_CLASS", "MISSING_DEPENDENCY_SUPERCLASS") + +package xyz.wingio.plugins.usertags + +import android.view.View +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import com.aliucord.PluginManager +import com.aliucord.Utils.getResId +import com.aliucord.api.PatcherAPI +import com.aliucord.patcher.* +import com.discord.databinding.UserProfileHeaderViewBinding +import com.discord.databinding.WidgetChannelMembersListItemUserBinding +import com.discord.models.message.Message +import com.discord.models.user.CoreUser +import com.discord.utilities.user.UserUtils +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.user.profile.UserProfileHeaderView +import com.discord.widgets.user.profile.UserProfileHeaderViewModel +import com.lytefast.flexinput.R + +fun PatcherAPI.patchTagInMessage() { + val settings = PluginManager.plugins["UserTags"]!!.settings + val tagViewId = getResId("chat_list_adapter_item_text_tag", "id") + + after("configureItemTag", Message::class.java, Boolean::class.javaPrimitiveType!!) { (_, message: Message, isForumPostOP: Boolean) -> + val author = message.author ?: return@after + val coreUser = CoreUser(author) + + val (tag, verified) = settings.getTag(coreUser.id) + + val isDev = coreUser.id == UserTags.DEV_ID + val isServer = (message.type == 0 && message.messageReference != null) + val isWebhook = message.webhookId != null + + val showTag = isDev || coreUser.isBot || isForumPostOP || coreUser.isSystemUser || isWebhook || tag != null + val textView = itemView.findViewById(tagViewId) + + if (textView != null) { + textView.visibility = if (showTag) View.VISIBLE else View.GONE + + // TODO: Use the localized strings for these + textView.text = when { + isDev -> "DEV" + coreUser.isSystemUser -> "SYSTEM" + isServer -> "SERVER" + isWebhook -> "WEBHOOK" + coreUser.isBot -> "BOT" + tag == null && isForumPostOP -> "OP" + else -> tag + } + + if (UserUtils.INSTANCE.isVerifiedBot(coreUser) || coreUser.id == UserTags.DEV_ID || verified) { + textView.setCompoundDrawablesWithIntrinsicBounds(R.e.ic_verified_10dp, 0, 0, 0) + } + } + } +} + +fun PatcherAPI.patchTagInMemberList() { + val settings = PluginManager.plugins["UserTags"]!!.settings + val bindingField = ChannelMembersListViewHolderMember::class.java.getDeclaredField("binding").apply { isAccessible = true } + + after("bind", ChannelMembersListAdapter.Item.Member::class.java, Function0::class.java) { (_, user: ChannelMembersListAdapter.Item.Member) -> + val binding = bindingField[this] as WidgetChannelMembersListItemUserBinding + val layout = binding.root as ConstraintLayout + + var (tag, verified) = settings.getTag(user.userId) + val isDev = user.userId == UserTags.DEV_ID + + if (isDev) tag = "DEV" + if (tag != null && !user.isBot) layout.findAndSetTag(tag, isDev, verified) + } +} + +fun PatcherAPI.patchTagInProfile() { + val settings = PluginManager.plugins["UserTags"]!!.settings + val profileBinding = UserProfileHeaderView::class.java.getDeclaredField("binding").apply { isAccessible = true } + + after("updateViewState", UserProfileHeaderViewModel.ViewState.Loaded::class.java) {(_, loadedState: UserProfileHeaderViewModel.ViewState.Loaded) -> + val binding = profileBinding[this] as UserProfileHeaderViewBinding + val user = loadedState.user ?: return@after + val isDev = user.id == UserTags.DEV_ID + + var (tag, verified) = settings.getTag(user.id) + + if (isDev) tag = "UserTags Developer" + if (tag != null && !user.isBot) binding.a.findAndSetTag(tag, isDev, verified) + } +} \ No newline at end of file diff --git a/UserTags/src/main/java/xyz/wingio/plugins/usertags/UserTags.kt b/UserTags/src/main/java/xyz/wingio/plugins/usertags/UserTags.kt new file mode 100644 index 00000000..44a88431 --- /dev/null +++ b/UserTags/src/main/java/xyz/wingio/plugins/usertags/UserTags.kt @@ -0,0 +1,27 @@ +package xyz.wingio.plugins.usertags + +import android.content.Context +import com.aliucord.annotations.AliucordPlugin +import com.aliucord.entities.Plugin + +@AliucordPlugin +class UserTags: Plugin() { + + override fun start(context: Context) { + patcher.patchTagInMessage() + patcher.patchTagInMemberList() + patcher.patchTagInProfile() + + commands.registerCommand() + } + + override fun stop(context: Context) { + patcher.unpatchAll() + commands.unregisterAll() + } + + companion object { + const val DEV_ID = 298295889720770563L + } + +} diff --git a/UserTags/src/main/java/xyz/wingio/plugins/usertags/Util.kt b/UserTags/src/main/java/xyz/wingio/plugins/usertags/Util.kt new file mode 100644 index 00000000..347307ef --- /dev/null +++ b/UserTags/src/main/java/xyz/wingio/plugins/usertags/Util.kt @@ -0,0 +1,22 @@ +package xyz.wingio.plugins.usertags + +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import com.aliucord.Utils.getResId +import com.aliucord.api.SettingsAPI +import com.lytefast.flexinput.R + +private val tagViewId = getResId("username_tag", "id") + +fun SettingsAPI.getTag(userId: Long): Pair + = getString(userId.toString(), null) to getBool("${userId}_verified", false) + +fun ViewGroup.findAndSetTag(tag: String, isDev: Boolean, verified: Boolean) { + val tagText = findViewById(tagViewId) + tagText.visibility = View.VISIBLE + tagText.text = tag + if (isDev || verified) { + tagText.setCompoundDrawablesWithIntrinsicBounds(R.e.ic_verified_10dp, 0, 0, 0) + } +} \ No newline at end of file diff --git a/UserTags/src/main/res/drawable/ic_editfriend.xml b/UserTags/src/main/res/drawable/ic_editfriend.xml deleted file mode 100644 index da7a6e18..00000000 --- a/UserTags/src/main/res/drawable/ic_editfriend.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a6b35ee4..5f48e502 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,6 +12,7 @@ include(":MessageLinkContext") include(":MoreHighlight") include(":RCM") include(":ShowPerms") +include(":ShowPermsV2") include(":Sessions") include(":TwemojiEverywhere") include(":UserTags")