From 124f31ebe1b2d048ad9d57dced3aafec39bec91f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 6 Jun 2024 10:58:29 +0300 Subject: [PATCH] Global screenshot policy #920 #138 --- .../org/koitharu/kotatsu/core/AppModule.kt | 3 + .../kotatsu/core/prefs/AppSettings.kt | 5 +- .../kotatsu/core/prefs/ScreenshotsPolicy.kt | 4 +- .../koitharu/kotatsu/core/ui/BaseActivity.kt | 6 ++ .../kotatsu/details/ui/DetailsActivity.kt | 4 ++ .../main/ui/protect/ScreenshotPolicyHelper.kt | 61 +++++++++++++++++++ .../kotatsu/reader/ui/ReaderActivity.kt | 12 +--- .../kotatsu/reader/ui/ReaderViewModel.kt | 12 ++-- .../kotatsu/search/ui/MangaListActivity.kt | 20 ++++-- .../userdata/UserDataSettingsFragment.kt | 7 +++ app/src/main/res/values/arrays.xml | 1 + app/src/main/res/values/constants.xml | 5 -- app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/pref_reader.xml | 8 --- app/src/main/res/xml/pref_user_data.xml | 7 +++ 15 files changed, 114 insertions(+), 42 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ScreenshotPolicyHelper.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt index de03baa97..8c93fc4b6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt @@ -48,6 +48,7 @@ import org.koitharu.kotatsu.local.data.LocalStorageChanges import org.koitharu.kotatsu.local.domain.model.LocalManga import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper +import org.koitharu.kotatsu.main.ui.protect.ScreenshotPolicyHelper import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider import org.koitharu.kotatsu.settings.backup.BackupObserver @@ -152,10 +153,12 @@ interface AppModule { appProtectHelper: AppProtectHelper, activityRecreationHandle: ActivityRecreationHandle, acraScreenLogger: AcraScreenLogger, + screenshotPolicyHelper: ScreenshotPolicyHelper, ): Set<@JvmSuppressWildcards Application.ActivityLifecycleCallbacks> = arraySetOf( appProtectHelper, activityRecreationHandle, acraScreenLogger, + screenshotPolicyHelper, ) @Provides diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 2fa4c6890..4f652cfe6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -298,10 +298,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { get() = prefs.getBoolean(KEY_PAGES_NUMBERS, false) val screenshotsPolicy: ScreenshotsPolicy - get() = runCatching { - val key = prefs.getString(KEY_SCREENSHOTS_POLICY, null)?.uppercase(Locale.ROOT) - if (key == null) ScreenshotsPolicy.ALLOW else ScreenshotsPolicy.valueOf(key) - }.getOrDefault(ScreenshotsPolicy.ALLOW) + get() = prefs.getEnumValue(KEY_SCREENSHOTS_POLICY, ScreenshotsPolicy.ALLOW) var userSpecifiedMangaDirectories: Set get() { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt index b92d71ec6..cb673e570 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/ScreenshotsPolicy.kt @@ -3,5 +3,5 @@ package org.koitharu.kotatsu.core.prefs enum class ScreenshotsPolicy { // Do not rename this - ALLOW, BLOCK_NSFW, BLOCK_ALL; -} \ No newline at end of file + ALLOW, BLOCK_NSFW, BLOCK_INCOGNITO, BLOCK_ALL; +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt index 26f0214f4..15413de01 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/BaseActivity.kt @@ -19,6 +19,8 @@ import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver @@ -26,10 +28,12 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.core.util.ext.isWebViewUnavailable +import org.koitharu.kotatsu.main.ui.protect.ScreenshotPolicyHelper @Suppress("LeakingThis") abstract class BaseActivity : AppCompatActivity(), + ScreenshotPolicyHelper.ContentContainer, WindowInsetsDelegate.WindowInsetsListener { private var isAmoledTheme = false @@ -151,6 +155,8 @@ abstract class BaseActivity : } } + override fun isNsfwContent(): Flow = flowOf(false) + private fun putDataToExtras(intent: Intent?) { intent?.putExtra(EXTRA_DATA, intent.data) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index cad4bccc4..a5bcf81d4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -34,10 +34,12 @@ import coil.util.CoilUtils import com.google.android.material.chip.Chip import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.R import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.core.model.FavouriteCategory @@ -197,6 +199,8 @@ class DetailsActivity : addMenuProvider(menuProvider) } + override fun isNsfwContent(): Flow = viewModel.manga.map { it?.isNsfw == true } + override fun onClick(v: View) { when (v.id) { R.id.button_read -> openReader(isIncognitoMode = false) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ScreenshotPolicyHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ScreenshotPolicyHelper.kt new file mode 100644 index 000000000..382ad55a5 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/protect/ScreenshotPolicyHelper.kt @@ -0,0 +1,61 @@ +package org.koitharu.kotatsu.main.ui.protect + +import android.app.Activity +import android.os.Bundle +import android.view.WindowManager +import androidx.annotation.MainThread +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy +import org.koitharu.kotatsu.core.prefs.observeAsFlow +import org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks +import javax.inject.Inject + +class ScreenshotPolicyHelper @Inject constructor( + private val settings: AppSettings, +) : DefaultActivityLifecycleCallbacks { + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + (activity as? ContentContainer)?.setupScreenshotPolicy(activity) + } + + private fun ContentContainer.setupScreenshotPolicy(activity: Activity) = + lifecycleScope.launch(Dispatchers.Default) { + settings.observeAsFlow(AppSettings.KEY_SCREENSHOTS_POLICY) { screenshotsPolicy } + .flatMapLatest { policy -> + when (policy) { + ScreenshotsPolicy.ALLOW -> flowOf(false) + ScreenshotsPolicy.BLOCK_NSFW -> withContext(Dispatchers.Main) { + isNsfwContent() + }.distinctUntilChanged() + + ScreenshotsPolicy.BLOCK_ALL -> flowOf(true) + ScreenshotsPolicy.BLOCK_INCOGNITO -> settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { + isIncognitoModeEnabled + } + } + }.collect { isSecure -> + withContext(Dispatchers.Main) { + if (isSecure) { + activity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) + } else { + activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + } + } + } + + interface ContentContainer : LifecycleOwner { + + @MainThread + fun isNsfwContent(): Flow + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index 728f422da..0af267564 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -28,6 +28,7 @@ import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koitharu.kotatsu.BuildConfig @@ -142,7 +143,6 @@ class ReaderActivity : viewModel.content.observe(this) { onLoadingStateChanged(viewModel.isLoading.value) } - viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure) viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn) viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged) viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this)) @@ -179,6 +179,8 @@ class ReaderActivity : viewModel.onPause() } + override fun isNsfwContent(): Flow = viewModel.isMangaNsfw + override fun onIdle() { viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState()) } @@ -297,14 +299,6 @@ class ReaderActivity : .show() } - private fun setWindowSecure(isSecure: Boolean) { - if (isSecure) { - window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - } - private fun setKeepScreenOn(isKeep: Boolean) { if (isKeep) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index a608f1132..2406b1045 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first @@ -38,7 +39,6 @@ import org.koitharu.kotatsu.core.parser.MangaDataRepository import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ReaderMode -import org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsStateFlow import org.koitharu.kotatsu.core.ui.BaseViewModel @@ -166,13 +166,9 @@ class ReaderViewModel @Inject constructor( }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null), ) - val isScreenshotsBlockEnabled = combine( - mangaFlow, - settings.observeAsFlow(AppSettings.KEY_SCREENSHOTS_POLICY) { screenshotsPolicy }, - ) { manga, policy -> - policy == ScreenshotsPolicy.BLOCK_ALL || - (policy == ScreenshotsPolicy.BLOCK_NSFW && manga != null && manga.isNsfw) - }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false) + val isMangaNsfw = mangaFlow.map { + it?.isNsfw == true + } val isBookmarkAdded = currentState.flatMapLatest { state -> val manga = mangaData.value?.toManga() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt index b7505ec2f..75f472d85 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/MangaListActivity.kt @@ -15,11 +15,14 @@ import androidx.fragment.app.commit import com.google.android.material.appbar.AppBarLayout import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.isNsfw import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags import org.koitharu.kotatsu.core.parser.MangaIntent @@ -58,6 +61,8 @@ class MangaListActivity : "Cannot find FilterOwner fragment in ${supportFragmentManager.fragments}" }.filter + private var source: MangaSource? = null + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(ActivityMangaListBinding.inflate(layoutInflater)) @@ -66,16 +71,19 @@ class MangaListActivity : if (viewBinding.containerFilterHeader != null) { viewBinding.appbar.addOnOffsetChangedListener(this) } - val source = intent.getStringExtra(EXTRA_SOURCE)?.let(::MangaSource) ?: tags?.firstOrNull()?.source - if (source == null) { + source = intent.getStringExtra(EXTRA_SOURCE)?.let(::MangaSource) ?: tags?.firstOrNull()?.source + val src = source + if (src == null) { finishAfterTransition() - return + } else { + viewBinding.buttonOrder?.setOnClickListener(this) + title = if (src == MangaSource.LOCAL) getString(R.string.local_storage) else src.title + initList(src, tags) } - viewBinding.buttonOrder?.setOnClickListener(this) - title = if (source == MangaSource.LOCAL) getString(R.string.local_storage) else source.title - initList(source, tags) } + override fun isNsfwContent(): Flow = flowOf(source?.isNsfw() == true) + override fun onWindowInsetsChanged(insets: Insets) { viewBinding.root.updatePadding( left = insets.left, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt index 96fdc2986..b52a1c3e3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/userdata/UserDataSettingsFragment.kt @@ -10,6 +10,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatDelegate import androidx.core.view.postDelayed import androidx.fragment.app.viewModels +import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference import androidx.preference.Preference import androidx.preference.TwoStatePreference @@ -22,6 +23,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy import org.koitharu.kotatsu.core.prefs.SearchSuggestionType import org.koitharu.kotatsu.core.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle @@ -29,6 +31,7 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.util.FileSize import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent +import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat import org.koitharu.kotatsu.core.util.ext.tryLaunch import org.koitharu.kotatsu.local.data.CacheDir import org.koitharu.kotatsu.parsers.util.mapToSet @@ -63,6 +66,10 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac appShortcutManager.isDynamicShortcutsAvailable() findPreference(AppSettings.KEY_PROTECT_APP) ?.isChecked = !settings.appPassword.isNullOrEmpty() + findPreference(AppSettings.KEY_SCREENSHOTS_POLICY)?.run { + entryValues = ScreenshotsPolicy.entries.names() + setDefaultValueCompat(ScreenshotsPolicy.ALLOW.name) + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7acd5f705..06552452b 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -23,6 +23,7 @@ @string/screenshots_allow @string/screenshots_block_nsfw + @string/screenshots_block_incognito @string/screenshots_block_all diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml index 77dd0dc98..8c22f632a 100644 --- a/app/src/main/res/values/constants.xml +++ b/app/src/main/res/values/constants.xml @@ -30,11 +30,6 @@ favourites - - allow - block_nsfw - block_all - 1 2 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 722f09e2b..a2e0983cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -653,4 +653,5 @@ New All languages + Block when incognito mode diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml index d5828f93d..e13444949 100644 --- a/app/src/main/res/xml/pref_reader.xml +++ b/app/src/main/res/xml/pref_reader.xml @@ -125,14 +125,6 @@ android:title="@string/keep_screen_on" app:allowDividerAbove="true" /> - - + +