setting to enable screenshot protection

This commit is contained in:
Fabian Freund
2026-05-01 05:18:32 +02:00
parent e7ccd1cf89
commit 0f011351ff
14 changed files with 2347 additions and 3193 deletions
@@ -107,6 +107,23 @@ class EngineSettingsReplicationService
},
);
ref.listen(
fireImmediately: true,
generalSettingsWithDefaultsProvider.select(
(settings) => settings.screenshotProtectionEnabled,
),
(previous, next) async {
await _service.setScreenshotProtectionEnabled(next);
},
onError: (error, stackTrace) {
logger.e(
'Error listening to screenshotProtectionEnabled',
error: error,
stackTrace: stackTrace,
);
},
);
ref.listen(
fireImmediately: true,
generalSettingsWithDefaultsProvider.select(
@@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider
}
String _$engineSettingsReplicationServiceHash() =>
r'71b611a99af83187e392d16288cec280ef44269f';
r'8f37476c9beecd6cc70a07b8cd3a51864b077176';
abstract class _$EngineSettingsReplicationService extends $Notifier<void> {
void build();
@@ -144,6 +144,7 @@ class _PrivacySignalsSection extends StatelessWidget {
children: [
SettingSection(name: 'Privacy Signals & Modes'),
_IncognitoModeSection(),
_ScreenshotProtectionTile(),
_GlobalPrivacyControlTile(),
],
);
@@ -452,6 +453,36 @@ class _GlobalPrivacyControlTile extends HookConsumerWidget {
}
}
class _ScreenshotProtectionTile extends HookConsumerWidget {
const _ScreenshotProtectionTile();
@override
Widget build(BuildContext context, WidgetRef ref) {
final enabled = ref.watch(
generalSettingsWithDefaultsProvider.select(
(s) => s.screenshotProtectionEnabled,
),
);
return SwitchListTile.adaptive(
title: const Text('Screenshot protection'),
subtitle: const Text(
'Blocks screenshots and screen recordings for this app on Android.',
),
secondary: const Icon(MdiIcons.cameraOff),
value: enabled,
onChanged: (value) async {
await ref
.read(saveGeneralSettingsControllerProvider.notifier)
.save(
(currentSettings) => currentSettings.copyWith
.screenshotProtectionEnabled(value),
);
},
);
}
}
class _HttpsOnlyModeSection extends HookConsumerWidget {
const _HttpsOnlyModeSection();
@@ -76,6 +76,7 @@ class GeneralSettings with FastEquatable {
final bool enableReadability;
final bool enforceReadability;
final Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit;
final bool screenshotProtectionEnabled;
@BangKeyConverter()
final BangKey? defaultSearchProvider;
final SearchSuggestionProviders defaultSearchSuggestionsProvider;
@@ -131,6 +132,7 @@ class GeneralSettings with FastEquatable {
required this.enableReadability,
required this.enforceReadability,
required this.deleteBrowsingDataOnQuit,
required this.screenshotProtectionEnabled,
required this.defaultSearchProvider,
required this.defaultSearchSuggestionsProvider,
required this.createChildTabsOption,
@@ -185,6 +187,7 @@ class GeneralSettings with FastEquatable {
bool? enableReadability,
bool? enforceReadability,
this.deleteBrowsingDataOnQuit,
bool? screenshotProtectionEnabled,
BangKey? defaultSearchProvider,
SearchSuggestionProviders? defaultSearchSuggestionsProvider,
bool? createChildTabsOption,
@@ -233,9 +236,10 @@ class GeneralSettings with FastEquatable {
uiScaleFactor = uiScaleFactor ?? defaultUiScaleFactor,
disableAnimations = disableAnimations ?? false,
showModalBarrier = showModalBarrier ?? true,
enableReadability = enableReadability ?? true,
enforceReadability = enforceReadability ?? false,
defaultSearchProvider = defaultSearchProvider ?? _fallbackSearchProvider,
enableReadability = enableReadability ?? true,
enforceReadability = enforceReadability ?? false,
screenshotProtectionEnabled = screenshotProtectionEnabled ?? false,
defaultSearchProvider = defaultSearchProvider ?? _fallbackSearchProvider,
defaultSearchSuggestionsProvider =
defaultSearchSuggestionsProvider ?? _fallbackAutocompleteProvider,
createChildTabsOption = createChildTabsOption ?? false,
@@ -320,6 +324,7 @@ class GeneralSettings with FastEquatable {
enableReadability,
enforceReadability,
deleteBrowsingDataOnQuit,
screenshotProtectionEnabled,
defaultSearchProvider,
defaultSearchSuggestionsProvider,
createChildTabsOption,
@@ -23,6 +23,8 @@ abstract class _$GeneralSettingsCWProxy {
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
);
GeneralSettings screenshotProtectionEnabled(bool screenshotProtectionEnabled);
GeneralSettings defaultSearchProvider(BangKey? defaultSearchProvider);
GeneralSettings defaultSearchSuggestionsProvider(
@@ -144,6 +146,7 @@ abstract class _$GeneralSettingsCWProxy {
bool enableReadability,
bool enforceReadability,
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
bool screenshotProtectionEnabled,
BangKey? defaultSearchProvider,
SearchSuggestionProviders defaultSearchSuggestionsProvider,
bool createChildTabsOption,
@@ -226,6 +229,11 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Set<DeleteBrowsingDataType>? deleteBrowsingDataOnQuit,
) => call(deleteBrowsingDataOnQuit: deleteBrowsingDataOnQuit);
@override
GeneralSettings screenshotProtectionEnabled(
bool screenshotProtectionEnabled,
) => call(screenshotProtectionEnabled: screenshotProtectionEnabled);
@override
GeneralSettings defaultSearchProvider(BangKey? defaultSearchProvider) =>
call(defaultSearchProvider: defaultSearchProvider);
@@ -431,6 +439,7 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
Object? enableReadability = const $CopyWithPlaceholder(),
Object? enforceReadability = const $CopyWithPlaceholder(),
Object? deleteBrowsingDataOnQuit = const $CopyWithPlaceholder(),
Object? screenshotProtectionEnabled = const $CopyWithPlaceholder(),
Object? defaultSearchProvider = const $CopyWithPlaceholder(),
Object? defaultSearchSuggestionsProvider = const $CopyWithPlaceholder(),
Object? createChildTabsOption = const $CopyWithPlaceholder(),
@@ -516,6 +525,12 @@ class _$GeneralSettingsCWProxyImpl implements _$GeneralSettingsCWProxy {
? _value.deleteBrowsingDataOnQuit
// ignore: cast_nullable_to_non_nullable
: deleteBrowsingDataOnQuit as Set<DeleteBrowsingDataType>?,
screenshotProtectionEnabled:
screenshotProtectionEnabled == const $CopyWithPlaceholder() ||
screenshotProtectionEnabled == null
? _value.screenshotProtectionEnabled
// ignore: cast_nullable_to_non_nullable
: screenshotProtectionEnabled as bool,
defaultSearchProvider:
defaultSearchProvider == const $CopyWithPlaceholder()
? _value.defaultSearchProvider
@@ -805,6 +820,7 @@ GeneralSettings _$GeneralSettingsFromJson(
deleteBrowsingDataOnQuit: (json['deleteBrowsingDataOnQuit'] as List<dynamic>?)
?.map((e) => $enumDecode(_$DeleteBrowsingDataTypeEnumMap, e))
.toSet(),
screenshotProtectionEnabled: json['screenshotProtectionEnabled'] as bool?,
defaultSearchProvider: const BangKeyConverter().fromJson(
json['defaultSearchProvider'] as String?,
),
@@ -908,6 +924,7 @@ Map<String, dynamic> _$GeneralSettingsToJson(
'deleteBrowsingDataOnQuit': instance.deleteBrowsingDataOnQuit
?.map((e) => _$DeleteBrowsingDataTypeEnumMap[e]!)
.toList(),
'screenshotProtectionEnabled': instance.screenshotProtectionEnabled,
'defaultSearchProvider': const BangKeyConverter().toJson(
instance.defaultSearchProvider,
),
@@ -71,6 +71,8 @@ class GeneralSettingsRepository extends _$GeneralSettingsRepository {
'deleteBrowsingDataOnQuit': settings['deleteBrowsingDataOnQuit']
?.readAs(DriftSqlType.string, db.typeMapping)
.mapNotNull(jsonDecode),
'screenshotProtectionEnabled': settings['screenshotProtectionEnabled']
?.readAs(DriftSqlType.bool, db.typeMapping),
'defaultSearchProvider': settings['defaultSearchProvider']?.readAs(
DriftSqlType.string,
db.typeMapping,
@@ -35,7 +35,7 @@ final class GeneralSettingsRepositoryProvider
}
String _$generalSettingsRepositoryHash() =>
r'9d34ea4b802d2d1b1c9543f41ad5fdf28f279e06';
r'f75d5cb1963c16404f5fd02db0097b72451717eb';
abstract class _$GeneralSettingsRepository
extends $StreamNotifier<GeneralSettings> {
@@ -16,6 +16,7 @@ import android.os.Environment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import android.widget.FrameLayout
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
@@ -34,6 +35,7 @@ import eu.weblibre.flutter_mozilla_components.integration.ReaderViewIntegration
import eu.weblibre.flutter_mozilla_components.activities.ExternalAppBrowserActivity
import eu.weblibre.flutter_mozilla_components.services.DownloadService
import io.flutter.Log
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
import mozilla.components.browser.state.state.WebExtensionState
import mozilla.components.browser.thumbnails.BrowserThumbnails
import mozilla.components.concept.engine.EngineView
@@ -150,6 +152,18 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
private lateinit var requestSitePermissionsLauncher: ActivityResultLauncher<Array<String>>
private lateinit var requestPromptsPermissionsLauncher: ActivityResultLauncher<Array<String>>
private fun updateSecureWindowState() {
val tab = components.core.store.state.findCustomTabOrSelectedTab(sessionId)
val shouldSecure =
GlobalComponents.screenshotProtectionEnabled || tab?.content?.private == true
if (shouldSecure) {
requireActivity().window.addFlags(FLAG_SECURE)
} else {
requireActivity().window.clearFlags(FLAG_SECURE)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -268,6 +282,10 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
GlobalComponents.onPullToRefreshEnabledChanged = { enabled ->
_binding?.swipeToRefresh?.isEnabled = enabled
}
GlobalComponents.onScreenshotProtectionEnabledChanged = {
updateSecureWindowState()
}
updateSecureWindowState()
shareResourceFeature.set(
ShareResourceFeature(
@@ -461,6 +479,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
window = requireActivity().window,
store = components.core.store,
customTabId = sessionId,
isSecure = { session ->
session.content.private ||
GlobalComponents.screenshotProtectionEnabled
},
clearFlagOnStop = false,
),
owner = this,
view = binding.root,
@@ -663,6 +686,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
browserHandlingScrollFeature = null
GlobalComponents.onPullToRefreshEnabledChanged = null
GlobalComponents.onScreenshotProtectionEnabledChanged = null
val engineView = fragmentEngineView
engineView?.setActivityContext(null)
if (components.activeEngineView == engineView) {
@@ -75,6 +75,14 @@ object GlobalComponents {
var onPullToRefreshEnabledChanged: ((Boolean) -> Unit)? = null
var screenshotProtectionEnabled: Boolean = false
set(value) {
field = value
onScreenshotProtectionEnabledChanged?.invoke(value)
}
var onScreenshotProtectionEnabledChanged: ((Boolean) -> Unit)? = null
// Viewport events for keyboard visibility notifications
var viewportEvents: GeckoViewportEvents? = null
@@ -419,6 +419,10 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi {
GlobalComponents.pullToRefreshEnabled = enabled
}
override fun setScreenshotProtectionEnabled(enabled: Boolean) {
GlobalComponents.screenshotProtectionEnabled = enabled
}
override fun setAppLinksMode(mode: AppLinksMode) {
val context = components.profileApplicationContext
val prefKey = context.getString(R.string.pref_key_open_links_in_apps)
@@ -6752,6 +6752,7 @@ interface GeckoSyncApi {
interface GeckoEngineSettingsApi {
fun setDefaultSettings(settings: GeckoEngineSettings)
fun updateRuntimeSettings(settings: GeckoEngineSettings)
fun setScreenshotProtectionEnabled(enabled: Boolean)
fun setPullToRefreshEnabled(enabled: Boolean)
/**
* Sets the app links mode preference (stored in SharedPreferences).
@@ -6811,6 +6812,24 @@ interface GeckoEngineSettingsApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.setScreenshotProtectionEnabled$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val enabledArg = args[0] as Boolean
val wrapped: List<Any?> = try {
api.setScreenshotProtectionEnabled(enabledArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoEngineSettingsApi.setPullToRefreshEnabled$separatedMessageChannelSuffix", codec)
if (api != null) {
@@ -189,6 +189,10 @@ class GeckoEngineSettingsService {
return _api.updateRuntimeSettings(GeckoEngineSettings(lnaEnabled: state));
}
Future<void> setScreenshotProtectionEnabled(bool enabled) {
return _api.setScreenshotProtectionEnabled(enabled);
}
Future<void> setPullToRefreshEnabled(bool enabled) {
return _api.setPullToRefreshEnabled(enabled);
}
File diff suppressed because it is too large Load Diff
@@ -1494,6 +1494,7 @@ abstract class GeckoSyncApi {
abstract class GeckoEngineSettingsApi {
void setDefaultSettings(GeckoEngineSettings settings);
void updateRuntimeSettings(GeckoEngineSettings settings);
void setScreenshotProtectionEnabled(bool enabled);
void setPullToRefreshEnabled(bool enabled);
/// Sets the app links mode preference (stored in SharedPreferences).