From e5f2bb65669db999daab448e12bdf108b9d6449f Mon Sep 17 00:00:00 2001 From: Finnley Somdahl <87634197+rebelonion@users.noreply.github.com> Date: Sun, 26 Nov 2023 21:00:46 -0600 Subject: [PATCH] new theme options --- app/build.gradle | 7 +- app/src/main/AndroidManifest.xml | 2 +- app/src/main/java/ani/dantotsu/media/Media.kt | 2 + .../java/ani/dantotsu/media/MediaAdaptor.kt | 57 +++++++- .../dantotsu/media/MediaDetailsActivity.kt | 7 +- .../ani/dantotsu/settings/SettingsActivity.kt | 62 ++++++++- .../java/ani/dantotsu/themes/ThemeManager.kt | 66 ++++++++-- app/src/main/res/drawable/ic_palette.xml | 10 ++ app/src/main/res/layout/activity_settings.xml | 124 +++++++++++++++--- .../main/res/layout/dialog_color_picker.xml | 16 +++ app/src/main/res/values/strings.xml | 6 +- 11 files changed, 309 insertions(+), 50 deletions(-) create mode 100644 app/src/main/res/drawable/ic_palette.xml create mode 100644 app/src/main/res/layout/dialog_color_picker.xml diff --git a/app/build.gradle b/app/build.gradle index 5caa29de..43f69176 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,11 +28,11 @@ android { buildTypes { debug { applicationIdSuffix ".beta" - manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_beta"] - debuggable true + manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_beta", icon_placeholder_round: "@mipmap/ic_launcher_beta_round"] + debuggable false } release { - manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher"] + manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher", icon_placeholder_round: "@mipmap/ic_launcher_round"] debuggable false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -99,6 +99,7 @@ dependencies { implementation 'com.alexvasilkov:gesture-views:2.8.3' implementation 'com.github.VipulOG:ebook-reader:0.1.6' implementation 'androidx.paging:paging-runtime-ktx:3.2.1' + implementation "com.github.skydoves:colorpickerview:2.3.0" // string matching implementation 'me.xdrop:fuzzywuzzy:1.4.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e3276763..7551c02c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,7 +52,7 @@ android:label="@string/app_name" android:largeHeap="true" android:requestLegacyExternalStorage="true" - android:roundIcon="@mipmap/ic_launcher_round" + android:roundIcon="${icon_placeholder_round}" android:supportsRtl="true" android:theme="@style/Theme.Dantotsu" android:usesCleartextTraffic="true" diff --git a/app/src/main/java/ani/dantotsu/media/Media.kt b/app/src/main/java/ani/dantotsu/media/Media.kt index 872baf50..4dbc9459 100644 --- a/app/src/main/java/ani/dantotsu/media/Media.kt +++ b/app/src/main/java/ani/dantotsu/media/Media.kt @@ -1,5 +1,6 @@ package ani.dantotsu.media +import android.graphics.Bitmap import ani.dantotsu.connections.anilist.api.FuzzyDate import ani.dantotsu.connections.anilist.api.MediaEdge import ani.dantotsu.connections.anilist.api.MediaList @@ -119,4 +120,5 @@ data class Media( object MediaSingleton { var media: Media? = null + var bitmap: Bitmap? = null } diff --git a/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt b/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt index 6f9dabed..b2a75655 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt @@ -4,10 +4,14 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateDecelerateInterpolator +import android.widget.ImageView import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams @@ -248,14 +252,14 @@ class MediaAdaptor( inner class MediaViewHolder(val binding: ItemMediaCompactBinding) : RecyclerView.ViewHolder(binding.root) { init { if (matchParent) itemView.updateLayoutParams { width = -1 } - itemView.setSafeOnClickListener { clicked(bindingAdapterPosition) } + itemView.setSafeOnClickListener { clicked(bindingAdapterPosition, resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)) } itemView.setOnLongClickListener { longClicked(bindingAdapterPosition) } } } inner class MediaLargeViewHolder(val binding: ItemMediaLargeBinding) : RecyclerView.ViewHolder(binding.root) { init { - itemView.setSafeOnClickListener { clicked(bindingAdapterPosition) } + itemView.setSafeOnClickListener { clicked(bindingAdapterPosition, resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)) } itemView.setOnLongClickListener { longClicked(bindingAdapterPosition) } } } @@ -263,7 +267,7 @@ class MediaAdaptor( @SuppressLint("ClickableViewAccessibility") inner class MediaPageViewHolder(val binding: ItemMediaPageBinding) : RecyclerView.ViewHolder(binding.root) { init { - binding.itemCompactImage.setSafeOnClickListener { clicked(bindingAdapterPosition) } + binding.itemCompactImage.setSafeOnClickListener { clicked(bindingAdapterPosition, resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)) } itemView.setOnTouchListener { _, _ -> true } binding.itemCompactImage.setOnLongClickListener { longClicked(bindingAdapterPosition) } } @@ -272,16 +276,17 @@ class MediaAdaptor( @SuppressLint("ClickableViewAccessibility") inner class MediaPageSmallViewHolder(val binding: ItemMediaPageSmallBinding) : RecyclerView.ViewHolder(binding.root) { init { - binding.itemCompactImage.setSafeOnClickListener { clicked(bindingAdapterPosition) } - binding.itemCompactTitleContainer.setSafeOnClickListener { clicked(bindingAdapterPosition) } + binding.itemCompactImage.setSafeOnClickListener { clicked(bindingAdapterPosition, resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)) } + binding.itemCompactTitleContainer.setSafeOnClickListener { clicked(bindingAdapterPosition, resizeBitmap(getBitmapFromImageView(binding.itemCompactImage), 100)) } itemView.setOnTouchListener { _, _ -> true } binding.itemCompactImage.setOnLongClickListener { longClicked(bindingAdapterPosition) } } } - fun clicked(position: Int) { + fun clicked(position: Int, bitmap: Bitmap? = null) { if ((mediaList?.size ?: 0) > position && position != -1) { val media = mediaList?.get(position) + if (bitmap != null) MediaSingleton.bitmap = bitmap ContextCompat.startActivity( activity, Intent(activity, MediaDetailsActivity::class.java).putExtra( @@ -302,4 +307,44 @@ class MediaAdaptor( } return false } + + fun getBitmapFromImageView(imageView: ImageView): Bitmap? { + val drawable = imageView.drawable ?: return null + + // If the drawable is a BitmapDrawable, then just get the bitmap + if (drawable is BitmapDrawable) { + return drawable.bitmap + } + + // Create a bitmap with the same dimensions as the drawable + val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, + drawable.intrinsicHeight, + Bitmap.Config.ARGB_8888) + + // Draw the drawable onto the bitmap + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + + return bitmap + } + + fun resizeBitmap(source: Bitmap?, maxDimension: Int): Bitmap? { + if (source == null) return null + val width = source.width + val height = source.height + val newWidth: Int + val newHeight: Int + + if (width > height) { + newWidth = maxDimension + newHeight = (height * (maxDimension.toFloat() / width)).toInt() + } else { + newHeight = maxDimension + newWidth = (width * (maxDimension.toFloat() / height)).toInt() + } + + return Bitmap.createScaledBitmap(source, newWidth, newHeight, true) + } + } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index 2a89de70..fbea5dbb 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -5,6 +5,7 @@ import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.text.SpannableStringBuilder +import android.util.Log import android.util.TypedValue import android.view.GestureDetector import android.view.MotionEvent @@ -73,7 +74,9 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi @SuppressLint("SetTextI18n", "ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { LangSet.setLocale(this) - ThemeManager(this).applyTheme() + var media: Media = intent.getSerialized("media") ?: return + ThemeManager(this).applyTheme(MediaSingleton.bitmap) + MediaSingleton.bitmap = null super.onCreate(savedInstanceState) binding = ActivityMediaBinding.inflate(layoutInflater) setContentView(binding.root) @@ -119,7 +122,7 @@ class MediaDetailsActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedLi viewPager.isUserInputEnabled = false viewPager.setPageTransformer(ZoomOutPageTransformer(uiSettings)) - var media: Media = intent.getSerialized("media") ?: return + val isDownload = intent.getBooleanExtra("download", false) media.selected = model.loadSelected(media, isDownload) diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 11654b77..2d91126a 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -5,12 +5,14 @@ import android.app.AlertDialog import android.content.Context import android.content.Intent import android.graphics.drawable.Animatable +import android.os.Build import android.os.Build.* import android.os.Build.VERSION.* import android.os.Bundle import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter +import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import androidx.activity.OnBackPressedCallback @@ -36,6 +38,7 @@ import ani.dantotsu.themes.ThemeManager import ani.dantotsu.others.LangSet import com.google.android.material.snackbar.Snackbar import com.google.android.material.textfield.TextInputEditText +import com.skydoves.colorpickerview.listeners.ColorListener import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.network.NetworkPreferences import io.noties.markwon.Markwon @@ -61,7 +64,7 @@ class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) LangSet.setLocale(this) -ThemeManager(this).applyTheme() + ThemeManager(this).applyTheme() binding = ActivitySettingsBinding.inflate(layoutInflater) setContentView(binding.root) @@ -82,10 +85,10 @@ ThemeManager(this).applyTheme() } val info = """ -dantotsu Version: ${BuildConfig.VERSION_NAME} -Device: $BRAND $DEVICE -Architecture: ${getArch()} -OS Version: $CODENAME $RELEASE ($SDK_INT) + dantotsu Version: ${BuildConfig.VERSION_NAME} + Device: $BRAND $DEVICE + Architecture: ${getArch()} + OS Version: $CODENAME $RELEASE ($SDK_INT) """.trimIndent() copyToClipboard(info, false) toast(getString(R.string.copied_device_info)) @@ -106,12 +109,30 @@ OS Version: $CODENAME $RELEASE ($SDK_INT) binding.settingsUseMaterialYou.isChecked = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_material_you", false) binding.settingsUseMaterialYou.setOnCheckedChangeListener { _, isChecked -> getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_material_you", isChecked).apply() + if(isChecked) binding.settingsUseCustomTheme.isChecked = false restartApp() } + binding.settingsUseCustomTheme.isChecked = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_custom_theme", false) + binding.settingsUseCustomTheme.setOnCheckedChangeListener { _, isChecked -> + getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_custom_theme", isChecked).apply() + if(isChecked) { + binding.settingsUseOLED.isChecked = false + binding.settingsUseMaterialYou.isChecked = false + } + + restartApp() + } + + binding.settingsUseSourceTheme.isChecked = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_source_theme", false) + binding.settingsUseSourceTheme.setOnCheckedChangeListener { _, isChecked -> + getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_source_theme", isChecked).apply() + } + binding.settingsUseOLED.isChecked = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_oled", false) binding.settingsUseOLED.setOnCheckedChangeListener { _, isChecked -> getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putBoolean("use_oled", isChecked).apply() + if(isChecked) binding.settingsUseCustomTheme.isChecked = false restartApp() } @@ -121,13 +142,40 @@ OS Version: $CODENAME $RELEASE ($SDK_INT) binding.themeSwitcher.setAdapter(ArrayAdapter(this, R.layout.item_dropdown, ThemeManager.Companion.Theme.values().map { it.theme.substring(0, 1) + it.theme.substring(1).lowercase() })) binding.themeSwitcher.setOnItemClickListener { _, _, i, _ -> - getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putString("theme", ThemeManager.Companion.Theme.values()[i].theme).apply() + getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putString("custom_theme", ThemeManager.Companion.Theme.values()[i].theme).apply() //ActivityHelper.shouldRefreshMainActivity = true binding.themeSwitcher.clearFocus() restartApp() } + + binding.customTheme.setOnClickListener{ + var passedColor: Int = 0 + val dialogView = layoutInflater.inflate(R.layout.dialog_color_picker, null) + val alertDialog = AlertDialog.Builder(this ,R.style.MyPopup) + .setTitle("Custom Theme") + .setView(dialogView) + .setPositiveButton("OK") { dialog, _ -> + getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit().putInt("custom_theme", passedColor).apply() + logger("Custom Theme: $passedColor") + dialog.dismiss() + restartApp() + } + .setNegativeButton("Cancel") { dialog, _ -> + dialog.dismiss() + } + .create() + val colorPickerView = dialogView.findViewById(R.id.colorPickerView) + colorPickerView.setColorListener(ColorListener { color, fromUser -> + val linearLayout = dialogView.findViewById(R.id.linear) + passedColor = color + linearLayout.setBackgroundColor(color) + }) + + alertDialog.show() + } + //val animeSource = loadData("settings_def_anime_source_s")?.let { if (it >= AnimeSources.names.size) 0 else it } ?: 0 val animeSource = getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("settings_def_anime_source_s_r", 0) if (AnimeSources.names.isNotEmpty() && animeSource in 0 until AnimeSources.names.size) { @@ -177,7 +225,6 @@ OS Version: $CODENAME $RELEASE ($SDK_INT) } - // binding.userAgent.setText(networkPreferences.defaultUserAgent().get()) binding.userAgent.setOnClickListener{ val dialogView = layoutInflater.inflate(R.layout.dialog_user_agent, null) val editText = dialogView.findViewById(R.id.userAgentTextBox) @@ -295,6 +342,7 @@ OS Version: $CODENAME $RELEASE ($SDK_INT) } binding.settingsUiLight.setOnClickListener { + binding.settingsUseOLED.isChecked = false uiTheme(false, it) } diff --git a/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt b/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt index e771b54c..c188e810 100644 --- a/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt +++ b/app/src/main/java/ani/dantotsu/themes/ThemeManager.kt @@ -3,27 +3,40 @@ package ani.dantotsu.themes import android.app.Activity import android.content.Context import android.content.res.Configuration +import android.graphics.Bitmap +import android.util.Log +import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.logger +import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap +import ani.dantotsu.media.manga.mangareader.BaseImageAdapter.Companion.loadBitmap_old import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColorsOptions +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout class ThemeManager(private val context: Context) { - fun applyTheme() { + fun applyTheme(fromImage: Bitmap? = null) { val useOLED = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_oled", false) && isDarkThemeActive(context) - if(context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_material_you", false)){ - if (useOLED) { - val options = DynamicColorsOptions.Builder() - .setThemeOverlay(R.style.AppTheme_Amoled) - .build() - val activity = context as Activity - DynamicColors.applyToActivityIfAvailable(activity, options) - } else { - val activity = context as Activity - DynamicColors.applyToActivityIfAvailable(activity) - } + val useCustomTheme = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_custom_theme", false) + val customTheme = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getInt("custom_theme", 16712221) + val useSource = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_source_theme", false) + val useMaterial = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getBoolean("use_material_you", false) + if(useSource){ + applyDynamicColors(useMaterial, context, useOLED, fromImage, useCustom = if(useCustomTheme) customTheme else null) return + } else if (useCustomTheme) { + applyDynamicColors(useMaterial, context, useOLED, useCustom = customTheme) + return + } else { + val returnedEarly = applyDynamicColors(useMaterial, context, useOLED, useCustom = null) + if(!returnedEarly) return } val theme = context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).getString("theme", "PURPLE")!! @@ -42,6 +55,35 @@ class ThemeManager(private val context: Context) { context.setTheme(themeToApply) } + private fun applyDynamicColors(useMaterialYou: Boolean, context: Context, useOLED: Boolean, bitmap: Bitmap? = null, useCustom: Int? = null): Boolean { + val builder = DynamicColorsOptions.Builder() + var needMaterial = true + + // Set content-based source if a bitmap is provided + if (bitmap != null) { + builder.setContentBasedSource(bitmap) + needMaterial = false + }else if (useCustom != null){ + builder.setContentBasedSource(useCustom) + needMaterial = false + } + + // Set the theme overlay based on conditions + if (useOLED) { + builder.setThemeOverlay(R.style.AppTheme_Amoled) + needMaterial = false + } + if(needMaterial && !useMaterialYou) return true + + // Build the options + val options = builder.build() + + // Apply the dynamic colors to the activity + val activity = context as Activity + DynamicColors.applyToActivityIfAvailable(activity, options) + return false + } + private fun isDarkThemeActive(context: Context): Boolean { return when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { Configuration.UI_MODE_NIGHT_YES -> true diff --git a/app/src/main/res/drawable/ic_palette.xml b/app/src/main/res/drawable/ic_palette.xml new file mode 100644 index 00000000..581c3ba9 --- /dev/null +++ b/app/src/main/res/drawable/ic_palette.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index ac29f7b7..2dd69457 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -160,23 +160,6 @@ - + + + + + + + + + + +