Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved and Updated Code #19

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
8 changes: 5 additions & 3 deletions animatedThemeManager/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ plugins {
apply from: "${rootProject.projectDir}/scripts/publish-module.gradle"

android {
compileSdkVersion 30
compileSdkVersion 32

defaultConfig {
minSdkVersion 16
targetSdkVersion 30
targetSdkVersion 32
versionCode 11
versionName "1.1.4"
}
Expand All @@ -23,5 +23,7 @@ android {
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.appcompat:appcompat:1.4.1'
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,50 +18,53 @@ import androidx.core.view.ViewCompat
import kotlin.math.sqrt

abstract class ThemeActivity : AppCompatActivity() {
private lateinit var root: View
private lateinit var frontFakeThemeImageView: SimpleImageView
private lateinit var behindFakeThemeImageView: SimpleImageView
private lateinit var decorView: FrameLayout
private var root: View? = null
private var frontFakeThemeImageView: SimpleImageView? = null
private var behindFakeThemeImageView: SimpleImageView? = null
private var decorView: FrameLayout? = null

private var anim: Animator? = null
private var themeAnimationListener: ThemeAnimationListener? = null

override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
ThemeManager.instance.init(this, getStartTheme())
ThemeManager.init(this, getStartTheme())
initViews()
super.setContentView(root)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ThemeManager.instance.init(this, getStartTheme())
ThemeManager.init(this, getStartTheme())
initViews()
super.setContentView(root)
}

override fun onResume() {
super.onResume()
ThemeManager.instance.setActivity(this)
getThemeManager().getCurrentTheme()?.let { syncTheme(it) }
ThemeManager.setActivity(this)
themeManager.currentTheme?.let { syncTheme(it) }
}

fun getThemeManager(): ThemeManager {
return ThemeManager.instance
}
protected val themeManager: ThemeManager
get() = ThemeManager

override fun setContentView(@LayoutRes layoutResID: Int) {
setContentView(LayoutInflater.from(this).inflate(layoutResID, decorView, false))
}

override fun setContentView(view: View?) {
decorView.removeAllViews()
decorView.addView(view)
decorView?.let {
it.removeAllViews()
it.addView(view)
}
}

override fun setContentView(view: View?, params: ViewGroup.LayoutParams?) {
decorView.removeAllViews()
decorView.addView(view, params)
decorView?.let {
it.removeAllViews()
it.addView(view, params)
}
}

private fun initViews() {
Expand Down Expand Up @@ -116,28 +119,28 @@ abstract class ThemeActivity : AppCompatActivity() {
return
}

if (frontFakeThemeImageView.visibility == View.VISIBLE ||
behindFakeThemeImageView.visibility == View.VISIBLE ||
if (frontFakeThemeImageView?.visibility == View.VISIBLE ||
behindFakeThemeImageView?.visibility == View.VISIBLE ||
isRunningChangeThemeAnimation()
) {
return
}

// take screenshot
val w = decorView.measuredWidth
val h = decorView.measuredHeight
val w = decorView?.measuredWidth ?: 0
val h = decorView?.measuredHeight ?: 0
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
decorView.draw(canvas)
decorView?.draw(canvas)

// update theme
syncTheme(newTheme)

//create anim
val finalRadius = sqrt((w * w + h * h).toDouble()).toFloat()
if (isReverse) {
frontFakeThemeImageView.bitmap = bitmap
frontFakeThemeImageView.visibility = View.VISIBLE
frontFakeThemeImageView?.bitmap = bitmap
frontFakeThemeImageView?.visibility = View.VISIBLE
anim = ViewAnimationUtils.createCircularReveal(
frontFakeThemeImageView,
sourceCoordinate.x,
Expand All @@ -146,8 +149,8 @@ abstract class ThemeActivity : AppCompatActivity() {
0f
)
} else {
behindFakeThemeImageView.bitmap = bitmap
behindFakeThemeImageView.visibility = View.VISIBLE
behindFakeThemeImageView?.bitmap = bitmap
behindFakeThemeImageView?.visibility = View.VISIBLE
anim = ViewAnimationUtils.createCircularReveal(
decorView,
sourceCoordinate.x,
Expand All @@ -167,10 +170,10 @@ abstract class ThemeActivity : AppCompatActivity() {
}

override fun onAnimationEnd(animation: Animator) {
behindFakeThemeImageView.bitmap = null
frontFakeThemeImageView.bitmap = null
frontFakeThemeImageView.visibility = View.GONE
behindFakeThemeImageView.visibility = View.GONE
behindFakeThemeImageView?.bitmap = null
frontFakeThemeImageView?.bitmap = null
frontFakeThemeImageView?.visibility = View.GONE
behindFakeThemeImageView?.visibility = View.GONE
themeAnimationListener?.onAnimationEnd(animation)
}

Expand Down Expand Up @@ -202,6 +205,15 @@ abstract class ThemeActivity : AppCompatActivity() {
// it just used for the first time (first activity).
abstract fun getStartTheme(): AppTheme

override fun onDestroy() {
themeManager.clearActivity()
frontFakeThemeImageView = null
behindFakeThemeImageView = null
root = null
decorView = null
super.onDestroy()
}

companion object {
//generated Id for decorView
internal val ROOT_ID = ViewCompat.generateViewId()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
package com.dolatkia.animatedThemeManager

import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.filterNotNull

abstract class ThemeFragment : Fragment() {
abstract class ThemeFragment : Fragment {

override fun onResume() {
getThemeManager()?.getCurrentLiveTheme()?.observe(this) {
syncTheme(it)
constructor() : super() { }

constructor(@LayoutRes contentLayoutId: Int) : super(contentLayoutId)

override fun onCreate(savedInstanceState: Bundle?) {
this.lifecycleScope.launchWhenResumed {
themeManager.theme.filterNotNull().collect {
syncTheme(it)
}
}

super.onResume()
super.onCreate(savedInstanceState)
}

protected fun getThemeManager() : ThemeManager? {
return ThemeManager.instance
}
protected val themeManager: ThemeManager
get() = ThemeManager

// to sync ui with selected theme
abstract fun syncTheme(appTheme: AppTheme)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,64 +7,89 @@ import android.view.View
import android.view.WindowInsetsController
import android.view.WindowManager
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.lang.ref.WeakReference

class ThemeManager {
object ThemeManager {

private var liveTheme: MutableLiveData<AppTheme> = MutableLiveData()
private lateinit var activity: ThemeActivity
private val _liveTheme: MutableStateFlow<AppTheme?> = MutableStateFlow(null)
val theme: SharedFlow<AppTheme?> = _liveTheme.asSharedFlow()
val currentTheme: AppTheme?
get() = _liveTheme.value

companion object {
val instance = ThemeManager()
}
private var activity: WeakReference<ThemeActivity?> = WeakReference(null)

fun init(activity: ThemeActivity, defaultTheme: AppTheme) {
if (liveTheme.value == null) {
this.liveTheme.value = defaultTheme
if (_liveTheme.value == null) {
val success = this._liveTheme.tryEmit(defaultTheme)
if (!success) {
try {
activity.lifecycleScope.launch(Dispatchers.IO) {
_liveTheme.emit(defaultTheme)
}
} catch (ignored: Exception) {
_liveTheme.value = defaultTheme
}
}
}
this.activity = activity
setActivity(activity)
}

fun setActivity(activity: ThemeActivity) {
this.activity = activity
}

fun getCurrentTheme(): AppTheme? {
return getCurrentLiveTheme().value
}

fun getCurrentLiveTheme(): MutableLiveData<AppTheme> {
return liveTheme
clearActivity()
this.activity = WeakReference(activity)
this.activity.enqueue()
}

@JvmOverloads
fun reverseChangeTheme(newTheme: AppTheme, view: View, duration: Long = 600) {
changeTheme(newTheme, getViewCoordinates(view), duration, true)
}

@JvmOverloads
fun reverseChangeTheme(newTheme: AppTheme, sourceCoordinate: Coordinate, duration: Long = 600) {
changeTheme(newTheme, sourceCoordinate, duration, true)

}

@JvmOverloads
fun changeTheme(newTheme: AppTheme, view: View, duration: Long = 600) {
changeTheme(newTheme, getViewCoordinates(view), duration)
}

@JvmOverloads
fun changeTheme(
newTheme: AppTheme,
sourceCoordinate: Coordinate,
duration: Long = 600,
isRevers: Boolean = false
) {

if (getCurrentTheme()?.id() == newTheme.id() || activity.isRunningChangeThemeAnimation()) {
if (currentTheme?.id() == newTheme.id() || activity.get()?.isRunningChangeThemeAnimation() == true) {
return
}

//start animation
activity.changeTheme(newTheme, sourceCoordinate, duration, isRevers)

//set LiveData
getCurrentLiveTheme().value = newTheme
activity.get()?.changeTheme(newTheme, sourceCoordinate, duration, isRevers)

val success = _liveTheme.tryEmit(newTheme)
if (!success) {
val scope = activity.get()?.lifecycleScope
if (scope != null) {
try {
scope.launch(Dispatchers.IO) {
_liveTheme.emit(newTheme)
}
} catch (ignored: Exception) {
this._liveTheme.value = newTheme
}
} else {
this._liveTheme.value = newTheme
}
}
}

fun setStatusBarBackgroundColor(activity: Activity, color: Int) {
Expand Down Expand Up @@ -108,17 +133,17 @@ class ThemeManager {

fun syncNavigationBarButtonsColorWithBackground(
activity: Activity,
navigationbarBackgroundClor: Int
navigationBarBackgroundColor: Int
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
activity.window.insetsController?.setSystemBarsAppearance(
if (isColorLight(navigationbarBackgroundClor)) WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS else 0,
if (isColorLight(navigationBarBackgroundColor)) WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS else 0,
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val decorView = activity.window.decorView
var flags = decorView.systemUiVisibility
flags = if (isColorLight(navigationbarBackgroundClor)) {
flags = if (isColorLight(navigationBarBackgroundColor)) {
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
} else {
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
Expand Down Expand Up @@ -151,4 +176,9 @@ class ThemeManager {
myView.parent as View
)
}

internal fun clearActivity() {
this.activity.clear()
this.activity.enqueue()
}
}
8 changes: 4 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ plugins {
}

android {
compileSdkVersion 30
compileSdkVersion 32
buildToolsVersion "30.0.3"

defaultConfig {
applicationId "com.dolatkia.example"
minSdkVersion 16
targetSdkVersion 30
targetSdkVersion 32
versionCode 1
versionName "1.0"
}
Expand All @@ -35,6 +35,6 @@ android {

dependencies {
implementation project(':animatedThemeManager')
// implementation "com.dolatkia:animated-theme-manager:1.1.4"
implementation 'com.google.android.material:material:1.4.0'
implementation 'com.google.android.material:material:1.5.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
}
Loading