Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions plugins/AvatarInHeader/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version = "1.0.1"
description = "Displays configurable user, group, and server avatars in the header"

aliucord.changelog.set("""
# 1.0.1
* Fix positioning of the avatar
""".trimIndent())
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.github.ushie

import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import com.aliucord.Utils
import com.aliucord.annotations.AliucordPlugin
import com.aliucord.entities.Plugin
import com.aliucord.patcher.after
import com.aliucord.utils.DimenUtils
import com.aliucord.wrappers.ChannelWrapper.Companion.guildId
import com.aliucord.wrappers.ChannelWrapper.Companion.isDM
import com.aliucord.wrappers.ChannelWrapper.Companion.isGuild
import com.aliucord.wrappers.ChannelWrapper.Companion.recipients
import com.discord.databinding.WidgetHomeBinding
import com.discord.models.user.CoreUser
import com.discord.stores.StoreStream
import com.discord.utilities.icon.IconUtils
import com.discord.utilities.images.MGImages
import com.discord.widgets.home.WidgetHome
import com.discord.widgets.home.WidgetHomeHeaderManager
import com.discord.widgets.home.WidgetHomeModel
import com.facebook.drawee.view.SimpleDraweeView

private enum class AvatarType(val settingKey: String, val defaultValue: Boolean) {
DM("showDmAvatar", true),
GROUP("showGroupAvatar", true),
GUILD("showServerAvatar", false)
}

@Suppress("unused")
@AliucordPlugin
class AvatarInHeader : Plugin() {
init {
settingsTab = SettingsTab(PluginSettings::class.java).withArgs(settings)
}

override fun start(context: Context) {
val iconId = Utils.getResId("toolbar_icon", "id")
val viewId = View.generateViewId()

val size = DimenUtils.dpToPx(24f)

patcher.after<WidgetHomeHeaderManager>(
"configure",
WidgetHome::class.java,
WidgetHomeModel::class.java,
WidgetHomeBinding::class.java
) { param ->
val model = param.args[1] as WidgetHomeModel
val layout = (param.args[0] as WidgetHome).actionBarTitleLayout ?: return@after
val icon = layout.findViewById<View>(iconId) ?: return@after
val avatar = layout.findViewWithTag<SimpleDraweeView>("AvatarInHeaderTag")

val channel = model.channel ?: return@after
val recipients = channel.recipients.orEmpty()

val avatarType = when {
channel.isDM() && recipients.size == 1 -> AvatarType.DM
channel.isDM() -> AvatarType.GROUP
channel.isGuild() -> AvatarType.GUILD
else -> null
}

val iconUri = avatarType
?.takeIf { settings.getBool(it.settingKey, it.defaultValue) }
?.let {
when (it) {
AvatarType.DM ->
IconUtils.getForUser(CoreUser(recipients.first()))

AvatarType.GROUP ->
IconUtils.getForChannel(channel, 2048)

AvatarType.GUILD ->
StoreStream.getGuilds().getGuild(channel.guildId)?.let { guild ->
IconUtils.getForGuild(guild.id, guild.icon, guild.icon, false)
}
}
}

if (iconUri == null) {
icon.tag = null
icon.alpha = 1f
avatar?.visibility = View.GONE
return@after
}

if (icon.tag == iconUri) return@after
icon.tag = iconUri
icon.alpha = 0f

val avatarView = avatar ?: SimpleDraweeView(layout.context).apply {
tag = "AvatarInHeaderTag"
id = viewId

val params = icon.layoutParams as ConstraintLayout.LayoutParams
layoutParams = ConstraintLayout.LayoutParams(size, size).apply {
topToTop = params.topToTop
bottomToBottom = params.bottomToBottom
}

(icon.parent as ViewGroup).addView(this)
MGImages.setRoundingParams(this, size / 2f, false, null, null, 0f)
}

avatarView.visibility = View.VISIBLE
avatarView.setImageURI(iconUri)
}
}

override fun stop(context: Context) = patcher.unpatchAll()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.github.ushie

import android.view.View
import com.aliucord.Utils
import com.aliucord.api.SettingsAPI
import com.aliucord.fragments.SettingsPage
import com.discord.views.CheckedSetting

class PluginSettings(private val settings: SettingsAPI) : SettingsPage() {
override fun onViewBound(view: View) {
super.onViewBound(view)
setActionBarTitle("Avatar In Header")
setActionBarSubtitle("Settings")

val ctx = requireContext()

listOf(
Triple("Show DM avatar", "Show the user's avatar in the header when in a DM", "showDmAvatar" to true),
Triple("Show group avatar", "Show the group icon in the header when in a group DM", "showGroupAvatar" to true),
Triple("Show server icon", "Show the server icon in the header when in a channel", "showServerAvatar" to false),
).forEach { (title, subtitle, setting) ->
val (key, default) = setting
addView(
Utils.createCheckedSetting(ctx, CheckedSetting.ViewType.SWITCH, title, subtitle).apply {
isChecked = settings.getBool(key, default)
setOnCheckedListener { settings.setBool(key, it) }
}
)
}
}
}
2 changes: 2 additions & 0 deletions plugins/BetterUserDetails/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
version = "1.0.0"
description = "Shows account creation, server join, and last message dates in the selected server or DM. Tap to toggle relative time."
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package com.github.ushie

import android.content.Context
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.widget.TextViewCompat
import com.aliucord.Utils
import com.aliucord.annotations.AliucordPlugin
import com.aliucord.entities.Plugin
import com.aliucord.patcher.after
import com.aliucord.utils.DimenUtils
import com.aliucord.utils.ReflectUtils
import com.aliucord.utils.RxUtils.subscribe
import com.discord.models.message.Message
import com.discord.stores.StoreSearch
import com.discord.stores.StoreStream
import com.discord.utilities.SnowflakeUtils
import com.discord.utilities.icon.IconUtils
import com.discord.utilities.images.MGImages
import com.discord.utilities.search.network.SearchFetcher
import com.discord.utilities.search.network.SearchQuery
import com.discord.utilities.time.ClockFactory
import com.discord.utilities.time.TimeUtils
import com.discord.widgets.user.usersheet.WidgetUserSheet
import com.discord.widgets.user.usersheet.WidgetUserSheetViewModel
import com.facebook.drawee.view.SimpleDraweeView
import com.lytefast.flexinput.R
import java.text.DateFormat
import java.util.concurrent.TimeUnit

@AliucordPlugin
class BetterUserDetails : Plugin() {
private val userDetailsViewId = View.generateViewId()
private var searchFetcher: SearchFetcher? = null

private val timeViews = mutableListOf<Pair<TextView, Long>>()
private var showReadableTime = false

private val iconSize = DimenUtils.dpToPx(14)
private val iconMarginEnd = DimenUtils.dpToPx(8)
private val entryMarginEnd = DimenUtils.dpToPx(12)
private val drawablePadding = DimenUtils.dpToPx(6)

override fun start(context: Context) {
patcher.after<WidgetUserSheet>(
"configureNote",
WidgetUserSheetViewModel.ViewState.Loaded::class.java
) { param ->
val loaded = param.args[0] as? WidgetUserSheetViewModel.ViewState.Loaded ?: return@after
val user = loaded.user ?: return@after

val layout = WidgetUserSheet.`access$getBinding$p`(
param.thisObject as WidgetUserSheet
).a.findViewById<LinearLayout>(
Utils.getResId("user_sheet_content", "id")
) ?: return@after

val ctx = layout.context

if (layout.findViewById<View>(userDetailsViewId) != null) return@after

val aboutMeCard = layout.findViewById<View>(Utils.getResId("about_me_card", "id"))
?: return@after

val guildId = StoreStream.getGuildSelected().selectedGuildId
val isGuild = guildId != 0L
val dp = DimenUtils.defaultPadding

layout.addView(LinearLayout(ctx).apply {
timeViews.clear()
showReadableTime = false
id = userDetailsViewId
gravity = Gravity.CENTER_VERTICAL
setPadding(dp, dp, dp, dp)
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)

addTime(ctx, SnowflakeUtils.toTimestamp(user.id), icon = R.e.ic_tab_home)

if (isGuild) {
StoreStream.getGuilds()
.getMember(guildId, user.id)
?.joinedAt?.g()
?.let {
addTime(
ctx,
it,
customIcon = {
addView(SimpleDraweeView(ctx).apply {
scaleType = ImageView.ScaleType.CENTER_CROP
setImageURI(IconUtils.getForGuild(guildId, loaded.guildIcon, "", true, 2048))
MGImages.setRoundingParams(this, 20f, false, null, null, 0f)

layoutParams = LinearLayout.LayoutParams(iconSize, iconSize).apply {
marginEnd = iconMarginEnd
}
})
}
)
}
}

val targetId = if (isGuild) guildId else StoreStream.getChannelsSelected().id

getLastMessageTimestamp(user.id, targetId, isGuild) {
it?.let { addTime(ctx, it, icon = R.e.ic_guild_list_dms_24dp) }
}
}, layout.indexOfChild(aboutMeCard) + 1)
}
}

override fun stop(context: Context) {
patcher.unpatchAll()
}

private fun getLastMessageTimestamp(userId: Long, targetId: Long, isGuild: Boolean, onResult: (Long?) -> Unit) {
val fetcher = searchFetcher ?: (ReflectUtils.getField(
StoreStream.getSearch().storeSearchQuery, "searchFetcher"
) as SearchFetcher).also { searchFetcher = it }

val targetType = if (isGuild) StoreSearch.SearchTarget.Type.GUILD else StoreSearch.SearchTarget.Type.CHANNEL

fetcher.makeQuery(
StoreSearch.SearchTarget(targetType, targetId),
null,
SearchQuery(mapOf("author_id" to listOf(userId.toString())), true)
).subscribe {
val lastMessageDate = takeIf { it?.errorCode == null && (it?.totalResults ?: 0) > 0 }
?.hits?.firstOrNull()
?.let { Message(it).id }

Utils.mainThread.post {
onResult(lastMessageDate?.let(SnowflakeUtils::toTimestamp))
}
}
}

private fun LinearLayout.addTime(
ctx: Context,
timestamp: Long,
icon: Int? = null,
customIcon: (LinearLayout.() -> Unit)? = null
): TextView {
customIcon?.invoke(this)

return TextView(ctx, null, 0, R.i.UserProfile_Section_Header).apply {
text = renderDate(ctx, timestamp)
setPadding(0, 0, 0, 0)
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
marginEnd = entryMarginEnd
}

setOnClickListener {
showReadableTime = !showReadableTime
timeViews.forEach { (view, timestamp) ->
view.text = if (showReadableTime) {
toReadable(timestamp)
} else {
renderDate(ctx, timestamp)
}
}
}

icon?.let { iconRes ->
ContextCompat.getDrawable(ctx, iconRes)?.mutate()?.also {
it.setBounds(0, 0, iconSize, iconSize)
setCompoundDrawablesRelative(it, null, null, null)
TextViewCompat.setCompoundDrawableTintList(this, textColors)
compoundDrawablePadding = drawablePadding
}
}

timeViews += this to timestamp
addView(this)
}
}
}

private fun toReadable(timestamp: Long): String {
val days = TimeUnit.DAYS.convert(
ClockFactory.get().currentTimeMillis() - timestamp,
TimeUnit.MILLISECONDS
)

return when (days) {
0L -> "Today"
1L -> "Yesterday"
else -> "$days days ago"
}
}

private fun renderDate(ctx: Context, timestamp: Long) =
TimeUtils.INSTANCE.renderUtcDate(timestamp, ctx, DateFormat.MEDIUM)
6 changes: 6 additions & 0 deletions plugins/ClearURLs/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version = "1.0.0"
description = """
Automatically removes tracking elements from URLs you send.

Uses data from the ClearURLs browser extension.
""".trimIndent()
Loading