From f8fa58e564f3b2280ff1f26ee8d547a6e4d0417a Mon Sep 17 00:00:00 2001 From: Hedon-dev <158850208+Hedon-dev@users.noreply.github.com> Date: Sun, 1 Dec 2024 18:26:37 +0100 Subject: [PATCH] Add app preview hiding --- .../managers/shared_prefs_manager.dart | 1 + lib/main.dart | 129 ++++++++++++------ lib/ui/screens/settings/settings_misc.dart | 29 ++++ pubspec.lock | 20 ++- pubspec.yaml | 1 + 5 files changed, 136 insertions(+), 44 deletions(-) diff --git a/lib/backend/managers/shared_prefs_manager.dart b/lib/backend/managers/shared_prefs_manager.dart index a04814e7..1e7a9ac7 100644 --- a/lib/backend/managers/shared_prefs_manager.dart +++ b/lib/backend/managers/shared_prefs_manager.dart @@ -29,6 +29,7 @@ Future setDefaultSettings([forceReset = false]) async { await sharedStorage.setBool("enable_watch_history", true); await sharedStorage.setBool("enable_search_history", true); await sharedStorage.setBool("keyboard_incognito_mode", true); + await sharedStorage.setBool("hide_app_preview", true); await sharedStorage.setBool("auto_play", false); await sharedStorage.setBool("show_progress_thumbnails", true); await sharedStorage.setInt("preferred_video_quality", 2160); // 4K diff --git a/lib/main.dart b/lib/main.dart index eb43efe9..b7c6c373 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,10 @@ +import 'dart:io'; + import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:secure_app_switcher/secure_app_switcher.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '/backend/custom_logger.dart'; @@ -24,6 +27,9 @@ final logger = Logger( ); late PackageInfo packageInfo; +/// This stores the global setting of whether the preview should be hidden +bool hidePreview = true; + void main() async { WidgetsFlutterBinding.ensureInitialized(); logger.i("Initializing app"); @@ -46,7 +52,7 @@ class ViewerApp extends StatefulWidget { context.findAncestorStateOfType(); } -class ViewerAppState extends State { +class ViewerAppState extends State with WidgetsBindingObserver { bool updateAvailable = false; bool isDownloadingUpdate = false; bool updateFailed = false; @@ -55,6 +61,8 @@ class ViewerAppState extends State { double downloadProgress = 0.0; UpdateManager updateManager = UpdateManager(); + /// This controls whether the preview should be currently blocked + bool blockPreview = false; int _selectedIndex = 0; static List screenList = [ const HomeScreen(), @@ -66,6 +74,17 @@ class ViewerAppState extends State { @override void initState() { super.initState(); + // Hide app preview by default + SecureAppSwitcher.on(); + sharedStorage.getBool("hide_app_preview").then((value) { + if (!value!) { + SecureAppSwitcher.off(); + } + setState(() => hidePreview = value); + }); + + // For detecting app state + WidgetsBinding.instance.addObserver(this); Future> updateResponseFuture = updateManager.checkForUpdate(); updateResponseFuture.whenComplete(() async { List updateFuture = await updateResponseFuture; @@ -84,10 +103,37 @@ class ViewerAppState extends State { @override void dispose() { + WidgetsBinding.instance.removeObserver(this); updateManager.removeListener(() {}); super.dispose(); } + // This is only necessary for desktops, as the mobile platforms have that feature built in + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (hidePreview) { + logger.i("Lifecycle state changed to $state"); + if (state == AppLifecycleState.paused || + state == AppLifecycleState.inactive) { + logger.i("Lifecycle state is paused or inactive"); + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + logger.i("Blurring app"); + setState(() { + blockPreview = true; + }); + } + } else if (state == AppLifecycleState.resumed) { + logger.i("Lifecycle state is resumed"); + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + logger.i("Unblurring app"); + setState(() { + blockPreview = false; + }); + } + } + } + } + @override Widget build(BuildContext context) { return DynamicColorBuilder(builder: (lightColorScheme, darkColorScheme) { @@ -112,43 +158,50 @@ class ViewerAppState extends State { brightness: Brightness.dark), ), themeMode: snapshot.data, - home: updateAvailable - ? buildUpdateDialogue() - : Scaffold( - bottomNavigationBar: NavigationBar( - destinations: [ - NavigationDestination( - icon: _selectedIndex == 0 - ? const Icon(Icons.home) - : const Icon(Icons.home_outlined), - label: "Home", - ), - NavigationDestination( - icon: _selectedIndex == 1 - ? const Icon(Icons.subscriptions) - : const Icon(Icons.subscriptions_outlined), - label: "Subscriptions", - ), - NavigationDestination( - icon: _selectedIndex == 2 - ? const Icon(Icons.video_library) - : const Icon(Icons.video_library_outlined), - label: "Library", - ), - NavigationDestination( - icon: _selectedIndex == 3 - ? const Icon(Icons.settings) - : const Icon(Icons.settings_outlined), - label: "Settings", - ), - ], - selectedIndex: _selectedIndex, - onDestinationSelected: (index) { - setState(() { - _selectedIndex = index; - }); - }), - body: screenList.elementAt(_selectedIndex)), + home: Stack(children: [ + updateAvailable + ? buildUpdateDialogue() + : Scaffold( + bottomNavigationBar: NavigationBar( + destinations: [ + NavigationDestination( + icon: _selectedIndex == 0 + ? const Icon(Icons.home) + : const Icon(Icons.home_outlined), + label: "Home", + ), + NavigationDestination( + icon: _selectedIndex == 1 + ? const Icon(Icons.subscriptions) + : const Icon(Icons.subscriptions_outlined), + label: "Subscriptions", + ), + NavigationDestination( + icon: _selectedIndex == 2 + ? const Icon(Icons.video_library) + : const Icon(Icons.video_library_outlined), + label: "Library", + ), + NavigationDestination( + icon: _selectedIndex == 3 + ? const Icon(Icons.settings) + : const Icon(Icons.settings_outlined), + label: "Settings", + ), + ], + selectedIndex: _selectedIndex, + onDestinationSelected: (index) { + setState(() { + _selectedIndex = index; + }); + }), + body: screenList.elementAt(_selectedIndex)), + if (blockPreview) ...[ + Positioned.fill( + child: Container(color: Colors.black), + ), + ] + ]), ); }); }); diff --git a/lib/ui/screens/settings/settings_misc.dart b/lib/ui/screens/settings/settings_misc.dart index e5827664..9bb262fb 100644 --- a/lib/ui/screens/settings/settings_misc.dart +++ b/lib/ui/screens/settings/settings_misc.dart @@ -1,4 +1,7 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:secure_app_switcher/secure_app_switcher.dart'; import '/main.dart'; import 'custom_widgets/options_switch.dart'; @@ -24,6 +27,32 @@ class _MiscScreenState extends State { padding: const EdgeInsets.all(8), child: Column( children: [ + FutureBuilder( + future: sharedStorage.getBool("hide_app_preview"), + builder: (context, snapshot) { + // only build when data finished loading + if (snapshot.data == null) { + return const SizedBox(); + } + return OptionsSwitch( + title: "Hide app preview", + subTitle: "Hide app preview in app switcher", + switchState: snapshot.data!, + onToggled: (value) async { + await sharedStorage.setBool( + "hide_app_preview", value); + // Force an immediate update + if (Platform.isAndroid || Platform.isIOS) { + if (!value) { + SecureAppSwitcher.off(); + } else { + SecureAppSwitcher.on(); + } + } + // the hidePreview var is from main.dart + setState(() => hidePreview = value); + }); + }), FutureBuilder( future: sharedStorage.getBool("keyboard_incognito_mode"), diff --git a/pubspec.lock b/pubspec.lock index 177aeff3..a5d381e6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -438,10 +438,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: "8c4967f8b7cb46dc914e178daa29813d83ae502e0529d7b0478330616a691ef7" url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.14" path_provider_foundation: dependency: transitive description: @@ -554,6 +554,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + secure_app_switcher: + dependency: "direct main" + description: + name: secure_app_switcher + sha256: "6f8a1755d15358291e697d212a2a5a28a302cc14798c0849e8630c12b4df4efd" + url: "https://pub.dev" + source: hosted + version: "0.1.0+1" share_plus: dependency: "direct main" description: @@ -675,10 +683,10 @@ packages: dependency: transitive description: name: sqlite3 - sha256: bb174b3ec2527f9c5f680f73a89af8149dd99782fbb56ea88ad0807c5638f2ed + sha256: cb7f4e9dc1b52b1fa350f7b3d41c662e75fc3d399555fa4e5efcf267e9a4fbb5 url: "https://pub.dev" source: hosted - version: "2.4.7" + version: "2.5.0" sqlite3_flutter_libs: dependency: "direct main" description: @@ -915,10 +923,10 @@ packages: dependency: transitive description: name: win32 - sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" + sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.9.0" window_manager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index de71657d..4077a7de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: share_plus: ^10.0.2 html_unescape: ^2.0.0 linkify: ^5.0.0 + secure_app_switcher: ^0.1.0+1 dev_dependencies: