Add option to hide fab (close #1466)

This commit is contained in:
Koitharu
2025-07-16 20:04:35 +03:00
parent 3e36e1e11c
commit 8142a6811b
14 changed files with 81 additions and 60 deletions

View File

@@ -15,12 +15,17 @@ import androidx.core.os.LocaleListCompat
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.network.DoHProvider
import org.koitharu.kotatsu.core.util.ext.connectivityManager
import org.koitharu.kotatsu.core.util.ext.getEnumValue
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeChanges
import org.koitharu.kotatsu.core.util.ext.putAll
import org.koitharu.kotatsu.core.util.ext.putEnumValue
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
@@ -82,6 +87,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isNavBarPinned: Boolean
get() = prefs.getBoolean(KEY_NAV_PINNED, false)
val isMainFabEnabled: Boolean
get() = prefs.getBoolean(KEY_MAIN_FAB, true)
var gridSize: Int
get() = prefs.getInt(KEY_GRID_SIZE, 100)
set(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }
@@ -598,7 +606,12 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
prefs.unregisterOnSharedPreferenceChangeListener(listener)
}
fun observe() = prefs.observe()
fun observeChanges() = prefs.observeChanges()
fun observe(vararg keys: String): Flow<String?> = prefs.observeChanges()
.filter { key -> key == null || key in keys }
.onStart { emit(null) }
.flowOn(Dispatchers.IO)
fun getAllValues(): Map<String, *> = prefs.all
@@ -743,6 +756,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_NAV_MAIN = "nav_main"
const val KEY_NAV_LABELS = "nav_labels"
const val KEY_NAV_PINNED = "nav_pinned"
const val KEY_MAIN_FAB = "main_fab"
const val KEY_32BIT_COLOR = "enhanced_colors"
const val KEY_SOURCES_ORDER = "sources_sort_order"
const val KEY_SOURCES_CATALOG = "sources_catalog"

View File

@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.transform
fun <T> AppSettings.observeAsFlow(key: String, valueProducer: AppSettings.() -> T) = flow {
var lastValue: T = valueProducer()
emit(lastValue)
observe().collect {
observeChanges().collect {
if (it == key) {
val value = valueProducer()
if (value != lastValue) {
@@ -25,7 +25,7 @@ fun <T> AppSettings.observeAsStateFlow(
scope: CoroutineScope,
key: String,
valueProducer: AppSettings.() -> T,
): StateFlow<T> = observe().transform {
): StateFlow<T> = observeChanges().transform {
if (it == key) {
emit(valueProducer())
}

View File

@@ -37,7 +37,7 @@ fun <E : Enum<E>> SharedPreferences.Editor.putEnumValue(key: String, value: E?)
putString(key, value?.name)
}
fun SharedPreferences.observe(): Flow<String?> = callbackFlow {
fun SharedPreferences.observeChanges(): Flow<String?> = callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
trySendBlocking(key)
}
@@ -49,7 +49,7 @@ fun SharedPreferences.observe(): Flow<String?> = callbackFlow {
fun <T> SharedPreferences.observe(key: String, valueProducer: suspend () -> T): Flow<T> = flow {
emit(valueProducer())
observe().collect { upstreamKey ->
observeChanges().collect { upstreamKey ->
if (upstreamKey == key) {
emit(valueProducer())
}

View File

@@ -4,9 +4,7 @@ import androidx.room.withTransaction
import dagger.Reusable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toManga
@@ -204,9 +202,7 @@ class HistoryRepository @Inject constructor(
fun shouldSkip(manga: Manga): Boolean = settings.isIncognitoModeEnabled(manga.isNsfw())
fun observeShouldSkip(manga: Manga): Flow<Boolean> {
return settings.observe()
.filter { key -> key == AppSettings.KEY_INCOGNITO_MODE || key == AppSettings.KEY_INCOGNITO_NSFW }
.onStart { emit("") }
return settings.observe(AppSettings.KEY_INCOGNITO_MODE, AppSettings.KEY_INCOGNITO_NSFW)
.map { shouldSkip(manga) }
.distinctUntilChanged()
}

View File

@@ -63,7 +63,7 @@ abstract class MangaListViewModel(
protected fun observeListModeWithTriggers(): Flow<ListMode> = combine(
listMode,
mangaDataRepository.observeOverridesTrigger(emitInitialState = true),
settings.observe().filter { key ->
settings.observeChanges().filter { key ->
key == AppSettings.KEY_PROGRESS_INDICATORS
|| key == AppSettings.KEY_TRACKER_ENABLED
|| key == AppSettings.KEY_QUICK_FILTER

View File

@@ -2,12 +2,13 @@ package org.koitharu.kotatsu.main.domain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.history.data.HistoryRepository
import javax.inject.Inject
@@ -17,15 +18,21 @@ class ReadingResumeEnabledUseCase @Inject constructor(
private val settings: AppSettings,
) {
operator fun invoke(): Flow<Boolean> = settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) {
isIncognitoModeEnabled
}.flatMapLatest { incognito ->
if (incognito) {
flowOf(false)
} else {
combine(networkState, historyRepository.observeLast()) { isOnline, last ->
last != null && (isOnline || last.isLocal)
operator fun invoke(): Flow<Boolean> = settings.observe(
AppSettings.KEY_MAIN_FAB,
AppSettings.KEY_INCOGNITO_MODE,
).map {
settings.isMainFabEnabled && !settings.isIncognitoModeEnabled
}.distinctUntilChanged()
.flatMapLatest { isFabEnabled ->
if (isFabEnabled) {
observeCanResume()
} else {
flowOf(false)
}
}
}
private fun observeCanResume() = combine(networkState, historyRepository.observeLast()) { isOnline, last ->
last != null && (isOnline || last.isLocal)
}.distinctUntilChanged()
}

View File

@@ -320,6 +320,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
topFragment: Fragment? = navigationDelegate.primaryFragment,
isSearchOpened: Boolean = viewBinding.searchView.isShowing,
) {
navigationDelegate.navRailHeader?.railFab?.isVisible = isResumeEnabled
val fab = viewBinding.fab ?: return
if (isResumeEnabled && !actionModeDelegate.isActionModeStarted && !isSearchOpened && topFragment is HistoryListFragment) {
if (!fab.isVisible) {

View File

@@ -16,16 +16,12 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.navigationrail.NavigationRailView
import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksFragment
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -56,7 +52,7 @@ class MainNavigationDelegate(
NavigationBarView.OnItemReselectedListener, View.OnClickListener {
private val listeners = LinkedList<OnFragmentChangedListener>()
private val navRailHeader = (navBar as? NavigationRailView)?.headerView?.let {
val navRailHeader = (navBar as? NavigationRailView)?.headerView?.let {
NavigationRailFabBinding.bind(it)
}
@@ -267,12 +263,7 @@ class MainNavigationDelegate(
}
private fun observeSettings(lifecycleOwner: LifecycleOwner) {
settings.observe()
.filter { x ->
x == AppSettings.KEY_TRACKER_ENABLED || x == AppSettings.KEY_SUGGESTIONS || x == AppSettings.KEY_NAV_LABELS
}
.onStart { emit("") }
.flowOn(Dispatchers.IO)
settings.observe(AppSettings.KEY_TRACKER_ENABLED, AppSettings.KEY_SUGGESTIONS, AppSettings.KEY_NAV_LABELS)
.onEach {
setItemVisibility(R.id.nav_suggestions, settings.isSuggestionsEnabled)
setItemVisibility(R.id.nav_feed, settings.isTrackerEnabled)

View File

@@ -8,7 +8,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.core.util.ext.getEnumValue
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeChanges
import org.koitharu.kotatsu.core.util.ext.putAll
import org.koitharu.kotatsu.core.util.ext.putEnumValue
import org.koitharu.kotatsu.reader.domain.TapGridArea
@@ -44,7 +44,7 @@ class TapGridSettings @Inject constructor(@ApplicationContext context: Context)
initPrefs(withDefaultValues = false)
}
fun observe() = prefs.observe().flowOn(Dispatchers.IO)
fun observeChanges() = prefs.observeChanges().flowOn(Dispatchers.IO)
fun getAllValues(): Map<String, *> = prefs.all

View File

@@ -122,7 +122,7 @@ data class ReaderSettings(
private suspend fun observeImpl() {
combine(
mangaId.flatMapLatest { mangaDataRepository.observeColorFilter(it) },
settings.observe().filter { x -> x == null || x in settingsKeys }.onStart { emit(null) },
settings.observeChanges().filter { x -> x == null || x in settingsKeys }.onStart { emit(null) },
) { mangaCf, settingsKey ->
ReaderSettings(settings, mangaCf)
}.collect {

View File

@@ -20,7 +20,7 @@ class ReaderTapGridConfigViewModel @Inject constructor(
private val tapGridSettings: TapGridSettings,
) : BaseViewModel() {
val content = tapGridSettings.observe()
val content = tapGridSettings.observeChanges()
.onStart { emit(null) }
.map { getData() }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyMap())

View File

@@ -45,7 +45,7 @@ class SourcesListProducer @Inject constructor(
}
init {
settings.observe()
settings.observeChanges()
.filter { it == AppSettings.KEY_TIPS_CLOSED || it == AppSettings.KEY_DISABLE_NSFW }
.flowOn(Dispatchers.Default)
.onEach { onInvalidated(emptySet()) }

View File

@@ -856,4 +856,7 @@
<string name="book_effect">Yellowish background (blue filter)</string>
<string name="local_storage_cleanup">Local storage cleanup</string>
<string name="packup_creation_failed">Failed to create backup</string>
<string name="main_screen">Main screen</string>
<string name="main_screen_fab">Show floating Continue button</string>
<string name="main_screen_fab_summary">Allows to continue reading in a one click. This button will not appear in incognito mode or when the history is empty</string>
</resources>

View File

@@ -23,6 +23,10 @@
android:summary="@string/black_dark_theme_summary"
android:title="@string/black_dark_theme" />
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
android:key="app_locale"
android:title="@string/language" />
<PreferenceCategory android:title="@string/manga_list">
<ListPreference
@@ -84,31 +88,36 @@
</PreferenceCategory>
<PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment"
android:key="nav_main"
android:title="@string/main_screen_sections"
app:allowDividerAbove="true" />
<PreferenceCategory android:title="@string/main_screen">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="nav_labels"
android:title="@string/show_labels_in_navbar" />
<PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment"
android:key="nav_main"
android:title="@string/main_screen_sections" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="nav_pinned"
android:summary="@string/pin_navigation_ui_summary"
android:title="@string/pin_navigation_ui" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="main_fab"
android:summary="@string/main_screen_fab_summary"
android:title="@string/main_screen_fab" />
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
android:key="app_locale"
android:title="@string/language" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="nav_labels"
android:title="@string/show_labels_in_navbar" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="exit_confirm"
android:summary="@string/exit_confirmation_summary"
android:title="@string/exit_confirmation" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="nav_pinned"
android:summary="@string/pin_navigation_ui_summary"
android:title="@string/pin_navigation_ui" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="exit_confirm"
android:summary="@string/exit_confirmation_summary"
android:title="@string/exit_confirmation" />
</PreferenceCategory>
</PreferenceScreen>