add authorization

This commit is contained in:
2024-11-15 18:27:59 +08:00
parent edff9c7a0c
commit 165e5f2850
13 changed files with 287 additions and 97 deletions

View File

@@ -120,6 +120,7 @@ class _Settings with ChangeNotifier {
'enableTurnPageByVolumeKey': true,
'enableClockAndBatteryInfoInReader': true,
'ignoreCertificateErrors': false,
'authorizationRequired': false,
};
operator [](String key) {

View File

@@ -5,7 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:rhttp/rhttp.dart';
import 'package:venera/foundation/log.dart';
import 'package:venera/network/app_dio.dart';
import 'package:venera/pages/auth_page.dart';
import 'package:venera/pages/comic_source_page.dart';
import 'package:venera/pages/main_page.dart';
import 'package:venera/pages/settings/settings_page.dart';
@@ -65,15 +65,59 @@ class MyApp extends StatefulWidget {
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
checkUpdates();
App.registerForceRebuild(forceRebuild);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
WidgetsBinding.instance.addObserver(this);
super.initState();
}
bool isAuthPageActive = false;
OverlayEntry? hideContentOverlay;
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(!App.isMobile) {
return;
}
if (state == AppLifecycleState.inactive && hideContentOverlay == null) {
hideContentOverlay = OverlayEntry(
builder: (context) {
return Positioned.fill(
child: Container(
width: double.infinity,
height: double.infinity,
color: App.rootContext.colorScheme.surface,
),
);
},
);
Overlay.of(App.rootContext).insert(hideContentOverlay!);
} else if (hideContentOverlay != null &&
state == AppLifecycleState.resumed) {
hideContentOverlay!.remove();
hideContentOverlay = null;
}
if (state == AppLifecycleState.hidden &&
appdata.settings['authorizationRequired'] &&
!isAuthPageActive) {
isAuthPageActive = true;
App.rootContext.to(
() => AuthPage(
onSuccessfulAuth: () {
App.rootContext.pop();
isAuthPageActive = false;
},
),
);
}
super.didChangeAppLifecycleState(state);
}
void forceRebuild() {
void rebuild(Element el) {
el.markNeedsBuild();
@@ -86,14 +130,25 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
Widget home;
if (appdata.settings['authorizationRequired']) {
home = AuthPage(
onSuccessfulAuth: () {
App.rootContext.toReplacement(() => const MainPage());
},
);
} else {
home = const MainPage();
}
return MaterialApp(
home: const MainPage(),
home: home,
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: App.mainColor,
surface: Colors.white,
primary: App.mainColor.shade600,
// ignore: deprecated_member_use
background: Colors.white,
),
fontFamily: App.isWindows ? "Microsoft YaHei" : null,
@@ -105,6 +160,7 @@ class _MyAppState extends State<MyApp> {
brightness: Brightness.dark,
surface: Colors.black,
primary: App.mainColor.shade400,
// ignore: deprecated_member_use
background: Colors.black,
),
fontFamily: App.isWindows ? "Microsoft YaHei" : null,
@@ -171,12 +227,12 @@ class _MyAppState extends State<MyApp> {
}
void checkUpdates() async {
if(!appdata.settings['checkUpdateOnStart']) {
if (!appdata.settings['checkUpdateOnStart']) {
return;
}
var lastCheck = appdata.implicitData['lastCheckUpdate'] ?? 0;
var now = DateTime.now().millisecondsSinceEpoch;
if(now - lastCheck < 24 * 60 * 60 * 1000) {
if (now - lastCheck < 24 * 60 * 60 * 1000) {
return;
}
appdata.implicitData['lastCheckUpdate'] = now;

60
lib/pages/auth_page.dart Normal file
View File

@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
import 'package:venera/utils/translations.dart';
class AuthPage extends StatefulWidget {
const AuthPage({super.key, this.onSuccessfulAuth});
final void Function()? onSuccessfulAuth;
@override
State<AuthPage> createState() => _AuthPageState();
}
class _AuthPageState extends State<AuthPage> {
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (!didPop) {
SystemNavigator.pop();
}
},
child: Material(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.security, size: 36),
const SizedBox(height: 16),
Text("Authentication Required".tl),
const SizedBox(height: 16),
FilledButton(
onPressed: auth,
child: Text("Continue".tl),
),
],
),
),
),
);
}
void auth() async {
var localAuth = LocalAuthentication();
var canCheckBiometrics = await localAuth.canCheckBiometrics;
if (!canCheckBiometrics && !await localAuth.isDeviceSupported()) {
widget.onSuccessfulAuth?.call();
return;
}
var isAuthorized = await localAuth.authenticate(
localizedReason: "Please authenticate to continue".tl,
);
if (isAuthorized) {
widget.onSuccessfulAuth?.call();
}
}
}

View File

@@ -36,12 +36,12 @@ class _AppSettingsState extends State<AppSettings> {
if (App.isAndroid) {
var channel = const MethodChannel("venera/storage");
var permission = await channel.invokeMethod('');
if(permission != true) {
if (permission != true) {
context.showMessage(message: "Permission denied".tl);
return;
}
var path = await selectDirectory();
if(path != null) {
if (path != null) {
// check if the path is writable
var testFile = File(FilePath.join(path, "test"));
try {
@@ -177,6 +177,29 @@ class _AppSettingsState extends State<AppSettings> {
App.forceRebuild();
},
).toSliver(),
if (!App.isLinux)
_SwitchSetting(
title: "Authorization Required".tl,
settingKey: "authorizationRequired",
onChanged: () async {
var current = appdata.settings['authorizationRequired'];
if (current) {
final auth = LocalAuthentication();
final bool canAuthenticateWithBiometrics =
await auth.canCheckBiometrics;
final bool canAuthenticate = canAuthenticateWithBiometrics ||
await auth.isDeviceSupported();
if (!canAuthenticate) {
context.showMessage(message: "Biometrics not supported".tl);
setState(() {
appdata.settings['authorizationRequired'] = false;
});
appdata.saveData();
return;
}
}
},
).toSliver(),
],
);
}

View File

@@ -33,9 +33,10 @@ class _SwitchSettingState extends State<_SwitchSetting> {
onChanged: (value) {
setState(() {
appdata.settings[widget.settingKey] = value;
appdata.saveData();
});
widget.onChanged?.call();
appdata.saveData().then((_) {
widget.onChanged?.call();
});
},
),
);

View File

@@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
import 'package:local_auth/local_auth.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:venera/components/components.dart';
import 'package:venera/foundation/app.dart';