Skip to content

Commit

Permalink
feat: Added initial theming system + AMOLED mode (based on Tachiyomi)
Browse files Browse the repository at this point in the history
refactor: Started to adopt dependency injection pattern
  • Loading branch information
timschneeb committed Oct 12, 2022
1 parent 30801a8 commit b33dc97
Show file tree
Hide file tree
Showing 63 changed files with 1,846 additions and 92 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,4 @@ This app is available for free on Google Play: [https://play.google.com/store/ap
## Credits

* JamesDSP - [James Fung (@james34602)](https://github.com/james34602)
* Theming system based on Tachiyomi
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.recyclerview:recyclerview:1.3.0-rc01")
implementation("androidx.navigation:navigation-fragment-ktx:2.5.2")
implementation("androidx.navigation:navigation-fragment:2.5.2")
implementation("androidx.navigation:navigation-ui-ktx:2.5.2")
Expand All @@ -139,6 +140,9 @@ dependencies {
implementation("androidx.databinding:databinding-runtime:7.3.0")
implementation("com.google.android.material:material:1.8.0-alpha01")

// Dependency injection
implementation("io.insert-koin:koin-android:3.2.0")

// Firebase
implementation(platform("com.google.firebase:firebase-bom:30.4.1"))
implementation("com.google.firebase:firebase-analytics-ktx")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,41 @@ package me.timschneeberger.rootlessjamesdsp

import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.google.firebase.ktx.app
import fr.bipi.tressence.file.FileLoggerTree
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import me.timschneeberger.rootlessjamesdsp.model.preference.ThemeMode
import me.timschneeberger.rootlessjamesdsp.model.room.AppBlocklistDatabase
import me.timschneeberger.rootlessjamesdsp.model.room.AppBlocklistRepository
import me.timschneeberger.rootlessjamesdsp.session.dump.DumpManager
import me.timschneeberger.rootlessjamesdsp.utils.Constants
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.GlobalContext.startKoin
import org.koin.dsl.module
import org.lsposed.hiddenapibypass.HiddenApiBypass
import timber.log.Timber
import timber.log.Timber.*
import java.io.File


class MainApplication : Application() {
class MainApplication : Application(), SharedPreferences.OnSharedPreferenceChangeListener {
init {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
{
HiddenApiBypass.addHiddenApiExemptions("L")
}
}

val prefs: SharedPreferences by lazy { getSharedPreferences(Constants.PREF_APP, Context.MODE_PRIVATE) }

val applicationScope = CoroutineScope(SupervisorJob())
val blockedAppDatabase by lazy { AppBlocklistDatabase.getDatabase(this, applicationScope) }
val blockedAppRepository by lazy { AppBlocklistRepository(blockedAppDatabase.appBlocklistDao()) }
Expand All @@ -51,7 +60,6 @@ class MainApplication : Application() {
dumpFile.delete()
}

val prefs = getSharedPreferences(Constants.PREF_APP, Context.MODE_PRIVATE)
// Soft-disable crashlytics in debug mode by default on each launch
if(BuildConfig.DEBUG) {
prefs
Expand All @@ -67,9 +75,48 @@ class MainApplication : Application() {
FirebaseCrashlytics.getInstance().setCustomKey("buildType", BuildConfig.BUILD_TYPE)
FirebaseCrashlytics.getInstance().setCustomKey("buildCommit", BuildConfig.COMMIT_SHA)

val initialPrefList = arrayOf(
R.string.key_appearance_theme_mode
)
for (pref in initialPrefList)
this.onSharedPreferenceChanged(prefs, getString(pref))
prefs.registerOnSharedPreferenceChangeListener(this)

val appModule = module {
single { DumpManager(androidContext()) }
}

startKoin {
androidLogger()
androidContext(this@MainApplication)
modules(appModule)
}

super.onCreate()
}

override fun onTerminate() {
prefs.unregisterOnSharedPreferenceChangeListener(this)
super.onTerminate()
}

override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
Timber.e("onSharedPreferenceChanged")
sharedPreferences ?: return

Timber.e(key)

if(key == getString(R.string.key_appearance_theme_mode)) {
AppCompatDelegate.setDefaultNightMode(
when (ThemeMode.fromInt(sharedPreferences.getString(key, "0")?.toIntOrNull() ?: 0)) {
ThemeMode.Light -> AppCompatDelegate.MODE_NIGHT_NO
ThemeMode.Dark -> AppCompatDelegate.MODE_NIGHT_YES
ThemeMode.FollowSystem -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
)
}
}

/** A tree which logs important information for crash reporting. */
private class CrashReportingTree : DebugTree() {
private fun priorityAsString(priority: Int): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import me.timschneeberger.rootlessjamesdsp.model.api.AeqSearchResult
import me.timschneeberger.rootlessjamesdsp.utils.ContextExtensions.hideKeyboardFrom
import me.timschneeberger.rootlessjamesdsp.utils.getSerializableAs

class AeqSelectorActivity : AppCompatActivity() {
class AeqSelectorActivity : BaseActivity() {

private lateinit var binding: ActivityAeqSelectorBinding
private lateinit var autoEqClient: AutoEqClient
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package me.timschneeberger.rootlessjamesdsp.activity

import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.delegates.ThemingDelegate
import me.timschneeberger.rootlessjamesdsp.delegates.ThemingDelegateImpl
import me.timschneeberger.rootlessjamesdsp.utils.Constants

open class BaseActivity :
AppCompatActivity(),
SharedPreferences.OnSharedPreferenceChangeListener,
ThemingDelegate by ThemingDelegateImpl() {

protected val appPref: SharedPreferences by lazy {
getSharedPreferences(Constants.PREF_APP, Context.MODE_PRIVATE)
}

override fun onCreate(savedInstanceState: Bundle?) {
applyAppTheme(this)
appPref.registerOnSharedPreferenceChangeListener(this)
super.onCreate(savedInstanceState)
}

override fun onDestroy() {
appPref.unregisterOnSharedPreferenceChangeListener(this)
super.onDestroy()
}

override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if(key == getString(R.string.key_appearance_pure_black) ||
key == getString(R.string.key_appearance_app_theme)) {
ActivityCompat.recreate(this)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.databinding.ActivityBlocklistBinding
import me.timschneeberger.rootlessjamesdsp.fragment.BlocklistFragment

class BlocklistActivity : AppCompatActivity() {
class BlocklistActivity : BaseActivity() {

private lateinit var binding: ActivityBlocklistBinding

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.databinding.ActivityGraphicEqBinding
import me.timschneeberger.rootlessjamesdsp.fragment.GraphicEqualizerFragment

class GraphicEqualizerActivity : AppCompatActivity() {
class GraphicEqualizerActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityGraphicEqBinding.inflate(layoutInflater)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.databinding.ActivityLiveprogParamsBinding
import me.timschneeberger.rootlessjamesdsp.fragment.LiveprogParamsFragment

class LiveprogParamsActivity : AppCompatActivity() {
class LiveprogParamsActivity : BaseActivity() {
private var showReset = false
private var enableReset = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import java.util.*
import kotlin.concurrent.schedule


class MainActivity : AppCompatActivity() {
class MainActivity : BaseActivity() {

private lateinit var binding: ActivityMainBinding
private lateinit var bindingContent: ContentMainBinding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import me.timschneeberger.rootlessjamesdsp.fragment.OnboardingFragment
import me.timschneeberger.rootlessjamesdsp.utils.ContextExtensions.showAlert


class OnboardingActivity : AppCompatActivity(){
class OnboardingActivity : BaseActivity(){
private lateinit var binding: ActivityOnboardingBinding
private lateinit var fragment: OnboardingFragment

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package me.timschneeberger.rootlessjamesdsp.activity

import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentTransaction
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.databinding.ActivitySettingsBinding
import me.timschneeberger.rootlessjamesdsp.fragment.SettingsAboutFragment
import me.timschneeberger.rootlessjamesdsp.fragment.SettingsFragment
import timber.log.Timber


class SettingsActivity : AppCompatActivity(),
class SettingsActivity : BaseActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package me.timschneeberger.rootlessjamesdsp.adapter

import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.recyclerview.widget.RecyclerView
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.databinding.PreferenceThemeItemBinding
import me.timschneeberger.rootlessjamesdsp.delegates.ThemingDelegate
import me.timschneeberger.rootlessjamesdsp.model.preference.AppTheme
import me.timschneeberger.rootlessjamesdsp.utils.Constants
import me.timschneeberger.rootlessjamesdsp.utils.ContextExtensions.getResourceColor

class ThemesPreferenceAdapter(private val context: Context,
private val clickListener: OnItemClickListener) :
RecyclerView.Adapter<ThemesPreferenceAdapter.ThemeViewHolder>() {

private var themes = emptyList<AppTheme>()
private val preferences by lazy { context.getSharedPreferences(Constants.PREF_APP, Context.MODE_PRIVATE) }

private lateinit var binding: PreferenceThemeItemBinding

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThemeViewHolder {
val isAmoled = preferences.getBoolean(context.getString(R.string.key_appearance_pure_black), false)
val themeResIds = ThemingDelegate.getThemeResIds(themes[viewType], isAmoled)
val themedContext = themeResIds.fold(parent.context) {
context, themeResId ->
ContextThemeWrapper(context, themeResId)
}

binding = PreferenceThemeItemBinding.inflate(LayoutInflater.from(themedContext), parent, false)
return ThemeViewHolder(binding.root)
}

override fun getItemViewType(position: Int): Int = position

override fun getItemCount(): Int = themes.size

override fun onBindViewHolder(holder: ThemesPreferenceAdapter.ThemeViewHolder, position: Int) {
holder.bind(themes[position])
}

@SuppressLint("NotifyDataSetChanged")
fun setItems(themes: List<AppTheme>) {
this.themes = themes
notifyDataSetChanged()
}

inner class ThemeViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {

private val selectedColor = view.context.getResourceColor(com.google.android.material.R.attr.colorPrimary)
private val unselectedColor = view.context.getResourceColor(android.R.attr.divider)

fun bind(appTheme: AppTheme) {
binding.name.text = view.context.getString(appTheme.titleResId!!)

// For rounded corners
binding.badges.clipToOutline = true

val storedAppTheme = AppTheme.valueOf(preferences.getString(context.getString(R.string.key_appearance_app_theme), AppTheme.DEFAULT.name) ?: AppTheme.DEFAULT.name)
val isSelected = storedAppTheme == appTheme
binding.themeCard.isChecked = isSelected
binding.themeCard.strokeColor = if (isSelected) selectedColor else unselectedColor

listOf(binding.root, binding.themeCard).forEach {
it.setOnClickListener {
clickListener.onItemClick(bindingAdapterPosition)
}
}
}
}

interface OnItemClickListener {
fun onItemClick(position: Int)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package me.timschneeberger.rootlessjamesdsp.delegates

import android.app.Activity
import android.content.Context
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.model.preference.AppTheme
import me.timschneeberger.rootlessjamesdsp.utils.Constants

interface ThemingDelegate {
fun applyAppTheme(activity: Activity)

companion object {
fun getThemeResIds(appTheme: AppTheme, isAmoled: Boolean): List<Int> {
val resIds = mutableListOf<Int>()
when (appTheme) {
AppTheme.MONET -> {
resIds += R.style.Theme_RootlessJamesDSP_Monet
}
AppTheme.GREEN_APPLE -> {
resIds += R.style.Theme_RootlessJamesDSP_GreenApple
}
AppTheme.LAVENDER -> {
resIds += R.style.Theme_RootlessJamesDSP_Lavender
}
AppTheme.MIDNIGHT_DUSK -> {
resIds += R.style.Theme_RootlessJamesDSP_MidnightDusk
}
AppTheme.STRAWBERRY_DAIQUIRI -> {
resIds += R.style.Theme_RootlessJamesDSP_StrawberryDaiquiri
}
AppTheme.TAKO -> {
resIds += R.style.Theme_RootlessJamesDSP_Tako
}
AppTheme.TEALTURQUOISE -> {
resIds += R.style.Theme_RootlessJamesDSP_TealTurquoise
}
AppTheme.YINYANG -> {
resIds += R.style.Theme_RootlessJamesDSP_YinYang
}
AppTheme.YOTSUBA -> {
resIds += R.style.Theme_RootlessJamesDSP_Yotsuba
}
AppTheme.TIDAL_WAVE -> {
resIds += R.style.Theme_RootlessJamesDSP_TidalWave
}
else -> {
resIds += R.style.Theme_RootlessJamesDSP
}
}

if (isAmoled) {
resIds += R.style.ThemeOverlay_RootlessJamesDSP_Amoled
}

return resIds
}
}
}

// TODO centralize preferences
class ThemingDelegateImpl : ThemingDelegate {
override fun applyAppTheme(activity: Activity) {
val preferences = activity.getSharedPreferences(Constants.PREF_APP, Context.MODE_PRIVATE)
val isAmoled = preferences.getBoolean(activity.getString(R.string.key_appearance_pure_black), false)
val appTheme = AppTheme.valueOf(preferences.getString(activity.getString(R.string.key_appearance_app_theme), AppTheme.DEFAULT.name) ?: AppTheme.DEFAULT.name)
ThemingDelegate.getThemeResIds(appTheme, isAmoled)
.forEach { activity.setTheme(it) }
}
}
Loading

0 comments on commit b33dc97

Please sign in to comment.