From 2d670418c7783dd6af536a2f7bef9bd288eeb544 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 13 Jul 2023 14:39:55 +0300 Subject: [PATCH] Application update indicator --- .../core/ui/util/OptionsMenuBadgeHelper.kt | 38 +++++++++++++++++++ .../koitharu/kotatsu/main/ui/MainActivity.kt | 16 ++++++++ .../koitharu/kotatsu/main/ui/MainViewModel.kt | 9 +++-- app/src/main/res/menu/opt_main.xml | 8 ++++ 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/OptionsMenuBadgeHelper.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/OptionsMenuBadgeHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/OptionsMenuBadgeHelper.kt new file mode 100644 index 000000000..7e1ff5daf --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/OptionsMenuBadgeHelper.kt @@ -0,0 +1,38 @@ +package org.koitharu.kotatsu.core.ui.util + +import androidx.annotation.IdRes +import androidx.appcompat.widget.Toolbar +import com.google.android.material.badge.BadgeDrawable +import com.google.android.material.badge.BadgeUtils +import com.google.android.material.badge.ExperimentalBadgeUtils + +@androidx.annotation.OptIn(ExperimentalBadgeUtils::class) +class OptionsMenuBadgeHelper( + private val toolbar: Toolbar, + @IdRes private val itemId: Int, +) { + + private var badge: BadgeDrawable? = null + + fun setBadgeVisible(isVisible: Boolean) { + if (isVisible) { + showBadge() + } else { + hideBadge() + } + } + + private fun hideBadge() { + badge?.let { + BadgeUtils.detachBadgeDrawable(it, toolbar, itemId) + } + badge = null + } + + private fun showBadge() { + val badgeDrawable = badge ?: BadgeDrawable.create(toolbar.context).also { + badge = it + } + BadgeUtils.attachBadgeDrawable(badgeDrawable, toolbar, itemId) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt index 619ec29f4..0a3a2d35c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -40,6 +40,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.ui.BaseActivity +import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper import org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView import org.koitharu.kotatsu.core.util.ext.hideKeyboard import org.koitharu.kotatsu.core.util.ext.observe @@ -63,6 +64,7 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel import org.koitharu.kotatsu.settings.SettingsActivity +import org.koitharu.kotatsu.settings.about.AppUpdateDialog import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment import javax.inject.Inject @@ -81,6 +83,7 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav private val searchSuggestionViewModel by viewModels() private val closeSearchCallback = CloseSearchCallback() private lateinit var navigationDelegate: MainNavigationDelegate + private lateinit var appUpdateBadge: OptionsMenuBadgeHelper override val appBar: AppBarLayout get() = viewBinding.appbar @@ -119,6 +122,8 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav navigationDelegate.addOnFragmentChangedListener(this) navigationDelegate.onCreate() + appUpdateBadge = OptionsMenuBadgeHelper(viewBinding.toolbar, R.id.action_app_update) + onBackPressedDispatcher.addCallback(ExitCallback(this, viewBinding.container)) onBackPressedDispatcher.addCallback(navigationDelegate) onBackPressedDispatcher.addCallback(closeSearchCallback) @@ -132,6 +137,7 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav viewModel.isLoading.observe(this, this::onLoadingStateChanged) viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged) viewModel.counters.observe(this, ::onCountersChanged) + viewModel.appUpdate.observe(this) { invalidateMenu() } viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged) searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged) } @@ -158,6 +164,9 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav override fun onPrepareOptionsMenu(menu: Menu?): Boolean { menu?.findItem(R.id.action_incognito)?.isChecked = searchSuggestionViewModel.isIncognitoModeEnabled.value + val hasAppUpdate = viewModel.appUpdate.value != null + menu?.findItem(R.id.action_app_update)?.isVisible = hasAppUpdate + appUpdateBadge.setBadgeVisible(hasAppUpdate) return super.onPrepareOptionsMenu(menu) } @@ -179,6 +188,13 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav true } + R.id.action_app_update -> { + viewModel.appUpdate.value?.also { + AppUpdateDialog(this) + .show(it) + } != null + } + else -> super.onOptionsItemSelected(item) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt index 8329bc8a1..df3904911 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainViewModel.kt @@ -7,6 +7,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R @@ -46,12 +47,14 @@ class MainViewModel @Inject constructor( valueProducer = { isTrackerEnabled }, ) + val appUpdate = appUpdateRepository.observeAvailableUpdate() + val counters = combine( - appUpdateRepository.observeAvailableUpdate(), trackingRepository.observeUpdatedMangaCount(), - ) { appUpdate, tracks -> + flow { emit(settings.newSources) }, + ) { tracks, newSources -> val a = SparseIntArray(2) - // a[R.id.nav_tools] = if (appUpdate != null) 1 else 0 + a[R.id.nav_explore] = newSources.size a[R.id.nav_feed] = tracks a }.stateIn( diff --git a/app/src/main/res/menu/opt_main.xml b/app/src/main/res/menu/opt_main.xml index 58a1fc530..ef76a5e4d 100644 --- a/app/src/main/res/menu/opt_main.xml +++ b/app/src/main/res/menu/opt_main.xml @@ -3,6 +3,14 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> + +