Yuito-app-android/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt

241 lines
9.3 KiB
Kotlin

package com.keylesspalace.tusky.components.preference
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.RadioButton
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.keylesspalace.tusky.MainActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.databinding.DialogEmojicompatBinding
import com.keylesspalace.tusky.databinding.ItemEmojiPrefBinding
import com.keylesspalace.tusky.util.EmojiCompatFont
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.BLOBMOJI
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.FONTS
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.NOTOEMOJI
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.SYSTEM_DEFAULT
import com.keylesspalace.tusky.util.EmojiCompatFont.Companion.TWEMOJI
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable
import okhttp3.OkHttpClient
import kotlin.system.exitProcess
/**
* This Preference lets the user select their preferred emoji font
*/
class EmojiPreference(
context: Context,
private val okHttpClient: OkHttpClient
) : Preference(context) {
private lateinit var selected: EmojiCompatFont
private lateinit var original: EmojiCompatFont
private val radioButtons = mutableListOf<RadioButton>()
private var updated = false
private var currentNeedsUpdate = false
private val downloadDisposables = MutableList<Disposable?>(FONTS.size) { null }
override fun onAttachedToHierarchy(preferenceManager: PreferenceManager) {
super.onAttachedToHierarchy(preferenceManager)
// Find out which font is currently active
selected = EmojiCompatFont.byId(
PreferenceManager.getDefaultSharedPreferences(context).getInt(key, 0)
)
// We'll use this later to determine if anything has changed
original = selected
summary = selected.getDisplay(context)
}
override fun onClick() {
val binding = DialogEmojicompatBinding.inflate(LayoutInflater.from(context))
setupItem(BLOBMOJI, binding.itemBlobmoji)
setupItem(TWEMOJI, binding.itemTwemoji)
setupItem(NOTOEMOJI, binding.itemNotoemoji)
setupItem(SYSTEM_DEFAULT, binding.itemNomoji)
AlertDialog.Builder(context)
.setView(binding.root)
.setPositiveButton(android.R.string.ok) { _, _ -> onDialogOk() }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun setupItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
// Initialize all the views
binding.emojiName.text = font.getDisplay(context)
binding.emojiCaption.setText(font.caption)
binding.emojiThumbnail.setImageResource(font.img)
// There needs to be a list of all the radio buttons in order to uncheck them when one is selected
radioButtons.add(binding.emojiRadioButton)
updateItem(font, binding)
// Set actions
binding.emojiDownload.setOnClickListener { startDownload(font, binding) }
binding.emojiDownloadCancel.setOnClickListener { cancelDownload(font, binding) }
binding.emojiRadioButton.setOnClickListener { radioButton: View -> select(font, radioButton as RadioButton) }
binding.root.setOnClickListener {
select(font, binding.emojiRadioButton)
}
}
private fun startDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
// Switch to downloading style
binding.emojiDownload.hide()
binding.emojiCaption.visibility = View.INVISIBLE
binding.emojiProgress.show()
binding.emojiProgress.progress = 0
binding.emojiDownloadCancel.show()
font.downloadFontFile(context, okHttpClient)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ progress ->
// The progress is returned as a float between 0 and 1, or -1 if it could not determined
if (progress >= 0) {
binding.emojiProgress.isIndeterminate = false
val max = binding.emojiProgress.max.toFloat()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
binding.emojiProgress.setProgress((max * progress).toInt(), true)
} else {
binding.emojiProgress.progress = (max * progress).toInt()
}
} else {
binding.emojiProgress.isIndeterminate = true
}
},
{
Toast.makeText(context, R.string.download_failed, Toast.LENGTH_SHORT).show()
updateItem(font, binding)
},
{
finishDownload(font, binding)
}
).also { downloadDisposables[font.id] = it }
}
private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
font.deleteDownloadedFile(context)
downloadDisposables[font.id]?.dispose()
downloadDisposables[font.id] = null
updateItem(font, binding)
}
private fun finishDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
select(font, binding.emojiRadioButton)
updateItem(font, binding)
// Set the flag to restart the app (because an update has been downloaded)
if (selected === original && currentNeedsUpdate) {
updated = true
currentNeedsUpdate = false
}
}
/**
* Select a font both visually and logically
*
* @param font The font to be selected
* @param radio The radio button associated with it's visual item
*/
private fun select(font: EmojiCompatFont, radio: RadioButton) {
selected = font
radioButtons.forEach { radioButton ->
radioButton.isChecked = radioButton == radio
}
}
/**
* Called when a "consistent" state is reached, i.e. it's not downloading the font
*
* @param font The font to be displayed
* @param binding The ItemEmojiPrefBinding to show the item in
*/
private fun updateItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
// There's no download going on
binding.emojiProgress.hide()
binding.emojiDownloadCancel.hide()
binding.emojiCaption.show()
if (font.isDownloaded(context)) {
// Make it selectable
binding.emojiDownload.hide()
binding.emojiRadioButton.show()
binding.root.isClickable = true
} else {
// Make it downloadable
binding.emojiDownload.show()
binding.emojiRadioButton.hide()
binding.root.isClickable = false
}
// Select it if necessary
if (font === selected) {
binding.emojiRadioButton.isChecked = true
// Update available
if (!font.isDownloaded(context)) {
currentNeedsUpdate = true
}
} else {
binding.emojiRadioButton.isChecked = false
}
}
private fun saveSelectedFont() {
val index = selected.id
Log.i(TAG, "saveSelectedFont: Font ID: $index")
PreferenceManager
.getDefaultSharedPreferences(context)
.edit()
.putInt(key, index)
.apply()
summary = selected.getDisplay(context)
}
/**
* User clicked ok -> save the selected font and offer to restart the app if something changed
*/
private fun onDialogOk() {
saveSelectedFont()
if (selected !== original || updated) {
AlertDialog.Builder(context)
.setTitle(R.string.restart_required)
.setMessage(R.string.restart_emoji)
.setNegativeButton(R.string.later, null)
.setPositiveButton(R.string.restart) { _, _ ->
// Restart the app
// From https://stackoverflow.com/a/17166729/5070653
val launchIntent = Intent(context, MainActivity::class.java)
val mPendingIntent = PendingIntent.getActivity(
context,
0x1f973, // This is the codepoint of the party face emoji :D
launchIntent,
NotificationHelper.pendingIntentFlags(false)
)
val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mgr.set(
AlarmManager.RTC,
System.currentTimeMillis() + 100,
mPendingIntent
)
exitProcess(0)
}.show()
}
}
companion object {
private const val TAG = "EmojiPreference"
}
}