Skip to content

Commit 3044ece

Browse files
Disable screenshots when encryption is enabled
Close #17
1 parent 85e80ac commit 3044ece

9 files changed

+152
-6
lines changed

android/app/src/main/AndroidManifest.xml

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
android:allowBackup="true"
3939
android:fullBackupOnly="true">
4040

41+
<property android:name="REQUIRE_SECURE_ENV" android:value="1" />
42+
4143
<activity
4244
android:name="MainActivity"
4345
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"

lib/core/window_manager.dart

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (C) 2024 Yaroslav Pronin <[email protected]>
2+
//
3+
// This file is part of Blink Comparison.
4+
//
5+
// Blink Comparison is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// Blink Comparison is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with Blink Comparison. If not, see <http://www.gnu.org/licenses/>.
17+
18+
import 'package:flutter_windowmanager_plus/flutter_windowmanager_plus.dart';
19+
import 'package:injectable/injectable.dart';
20+
21+
abstract class WindowManager {
22+
/// Allow/disallow screenshots
23+
Future<bool> setSecureFlag(bool enable);
24+
}
25+
26+
@Singleton(as: WindowManager)
27+
class WindowManagerImpl implements WindowManager {
28+
@override
29+
Future<bool> setSecureFlag(bool enable) async {
30+
if (enable) {
31+
return FlutterWindowManagerPlus.addFlags(
32+
FlutterWindowManagerPlus.FLAG_SECURE,
33+
);
34+
} else {
35+
return FlutterWindowManagerPlus.clearFlags(
36+
FlutterWindowManagerPlus.FLAG_SECURE,
37+
);
38+
}
39+
}
40+
}

lib/injector.config.dart

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/main.dart

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import 'package:blink_comparison/core/crash_catcher/handler/notification_crash_handler.dart';
1919
import 'package:blink_comparison/core/settings/app_settings.dart';
20+
import 'package:blink_comparison/core/window_manager.dart';
2021
import 'package:blink_comparison/core/workmanager/thumbnails_migrator_worker.dart';
2122
import 'package:blink_comparison/core/workmanager/workmanager.dart';
2223
import 'package:blink_comparison/ui/model/app_cubit.dart';
@@ -50,7 +51,10 @@ Future<void> _main() async {
5051

5152
runApp(
5253
BlocProvider(
53-
create: (context) => AppCubit(getIt<AppSettings>()),
54+
create: (context) => AppCubit(
55+
getIt<AppSettings>(),
56+
getIt<WindowManager>(),
57+
),
5458
child: App(
5559
enableDevicePreview: false,
5660
navigatorKey: _navigatorKey,

lib/ui/model/app_cubit.dart

+18-3
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@
1818
import 'dart:async';
1919

2020
import 'package:blink_comparison/core/settings/app_settings.dart';
21+
import 'package:blink_comparison/core/window_manager.dart';
2122
import 'package:blink_comparison/ui/model/app_state.dart';
2223
import 'package:bloc/bloc.dart';
2324

2425
class AppCubit extends Cubit<AppState> {
2526
final AppSettings _pref;
27+
final WindowManager _windowManager;
2628
late final StreamSubscription subscription;
2729

28-
AppCubit(this._pref) : super(const AppState.initial());
30+
AppCubit(
31+
this._pref,
32+
this._windowManager,
33+
) : super(const AppState.initial());
2934

3035
Future<void> load() async {
3136
emit(AppState.loaded(
@@ -43,7 +48,7 @@ class AppCubit extends Cubit<AppState> {
4348
case AppSettingsKey.cameraFullscreenMode:
4449
setCameraFullscreenMode(await _pref.cameraFullscreenMode);
4550
case AppSettingsKey.encryptionPreference:
46-
setEncryptPreference(_pref.encryptionPreferenceSync);
51+
await setEncryptPreference(_pref.encryptionPreferenceSync);
4752
}
4853
});
4954
}
@@ -93,7 +98,7 @@ class AppCubit extends Cubit<AppState> {
9398
}
9499
}
95100

96-
void setEncryptPreference(EncryptionPreference? value) {
101+
Future<void> setEncryptPreference(EncryptionPreference? value) async {
97102
if (state
98103
case AppState(
99104
:final theme?,
@@ -106,6 +111,16 @@ class AppCubit extends Cubit<AppState> {
106111
cameraFullscreenMode: cameraFullscreenMode,
107112
encrypt: value,
108113
));
114+
await _switchWindowSecureFlag(value);
115+
}
116+
}
117+
118+
Future<void> _switchWindowSecureFlag(EncryptionPreference? preference) async {
119+
switch (preference) {
120+
case null || EncryptionPreferenceNone():
121+
await _windowManager.setSecureFlag(false);
122+
case EncryptionPreferencePassword():
123+
await _windowManager.setSecureFlag(true);
109124
}
110125
}
111126
}

pubspec.lock

+9-1
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,14 @@ packages:
602602
description: flutter
603603
source: sdk
604604
version: "0.0.0"
605+
flutter_windowmanager_plus:
606+
dependency: "direct main"
607+
description:
608+
name: flutter_windowmanager_plus
609+
sha256: "4e2bf7c7f374699fd74d59785f1d74efd40052c24a5edde5a4d825cc72608d40"
610+
url: "https://pub.dev"
611+
source: hosted
612+
version: "1.0.1"
605613
fluttertoast:
606614
dependency: "direct main"
607615
description:
@@ -1721,5 +1729,5 @@ packages:
17211729
source: hosted
17221730
version: "3.1.2"
17231731
sdks:
1724-
dart: ">=3.5.0 <4.0.0"
1732+
dart: ">=3.5.1 <4.0.0"
17251733
flutter: ">=3.24.0"

pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ dependencies:
9191
dynamic_color: ^1.7.0
9292
material_symbols_icons: ^4.2785.1
9393
shimmer_animation: ^2.2.1
94+
flutter_windowmanager_plus: ^1.0.1
9495

9596
dev_dependencies:
9697
analyzer: ^6.7.0

test/mock/mock_window_manager.dart

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (C) 2024 Yaroslav Pronin <[email protected]>
2+
//
3+
// This file is part of Blink Comparison.
4+
//
5+
// Blink Comparison is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// Blink Comparison is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with Blink Comparison. If not, see <http://www.gnu.org/licenses/>.
17+
18+
import 'package:blink_comparison/core/window_manager.dart';
19+
import 'package:mocktail/mocktail.dart';
20+
21+
class MockWindowManager extends Mock implements WindowManager {}

test/ui/app_cubit_test.dart

+54-1
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,22 @@ import 'dart:async';
1919
import 'dart:ui';
2020

2121
import 'package:blink_comparison/core/settings/app_settings.dart';
22+
import 'package:blink_comparison/core/window_manager.dart';
2223
import 'package:blink_comparison/ui/model/app_cubit.dart';
2324
import 'package:blink_comparison/ui/model/app_state.dart';
2425
import 'package:bloc_test/bloc_test.dart';
2526
import 'package:flutter_test/flutter_test.dart';
2627
import 'package:mocktail/mocktail.dart';
2728

2829
import '../mock/mock.dart';
30+
import '../mock/mock_window_manager.dart';
2931

3032
void main() {
3133
group('AppCubit |', () {
3234
late AppCubit cubit;
3335
late AppSettings mockPref;
3436
late StreamController<String> streamController;
37+
late WindowManager mockWindowManager;
3538

3639
setUpAll(() {
3740
streamController = StreamController.broadcast();
@@ -43,16 +46,19 @@ void main() {
4346

4447
setUp(() async {
4548
mockPref = MockAppSettings();
49+
mockWindowManager = MockWindowManager();
4650
when(() => mockPref.theme).thenAnswer(
4751
(_) async => const AppThemeType.system(),
4852
);
4953
when(() => mockPref.locale).thenAnswer(
5054
(_) async => const AppLocaleType.system(),
5155
);
5256
when(() => mockPref.cameraFullscreenMode).thenAnswer((_) async => true);
57+
when(() => mockPref.encryptionPreferenceSync)
58+
.thenReturn(EncryptionPreference.none());
5359
when(() => mockPref.changePrefStream())
5460
.thenAnswer((_) => streamController.stream);
55-
cubit = AppCubit(mockPref);
61+
cubit = AppCubit(mockPref, mockWindowManager);
5662
await cubit.load();
5763
});
5864

@@ -194,5 +200,52 @@ void main() {
194200
),
195201
],
196202
);
203+
204+
blocTest(
205+
'Change encryption preference',
206+
build: () => cubit,
207+
act: (AppCubit cubit) async {
208+
when(() => mockWindowManager.setSecureFlag(any()))
209+
.thenAnswer((_) async => true);
210+
await cubit.setEncryptPreference(EncryptionPreference.password());
211+
verify(() => mockWindowManager.setSecureFlag(true)).called(1);
212+
await cubit.setEncryptPreference(EncryptionPreference.none());
213+
verify(() => mockWindowManager.setSecureFlag(false)).called(1);
214+
},
215+
expect: () => [
216+
const AppState.encryptPreferenceChanged(
217+
theme: AppThemeType.system(),
218+
locale: AppLocaleType.system(),
219+
cameraFullscreenMode: true,
220+
encrypt: EncryptionPreference.password(),
221+
),
222+
const AppState.encryptPreferenceChanged(
223+
theme: AppThemeType.system(),
224+
locale: AppLocaleType.system(),
225+
cameraFullscreenMode: true,
226+
encrypt: EncryptionPreference.none(),
227+
),
228+
],
229+
);
230+
231+
blocTest(
232+
'Listen encryption preference change',
233+
build: () => cubit,
234+
act: (AppCubit cubit) {
235+
when(() => mockPref.encryptionPreferenceSync)
236+
.thenReturn(EncryptionPreference.none());
237+
when(() => mockWindowManager.setSecureFlag(any()))
238+
.thenAnswer((_) async => true);
239+
streamController.add(AppSettingsKey.encryptionPreference);
240+
},
241+
expect: () => [
242+
const AppState.encryptPreferenceChanged(
243+
theme: AppThemeType.system(),
244+
locale: AppLocaleType.system(),
245+
cameraFullscreenMode: true,
246+
encrypt: EncryptionPreference.none(),
247+
),
248+
],
249+
);
197250
});
198251
}

0 commit comments

Comments
 (0)