From 5f3b73d12c44ba420b6e3d2fba5eda9b7243cfa5 Mon Sep 17 00:00:00 2001 From: niuhuan Date: Sat, 14 May 2022 21:45:00 +0800 Subject: [PATCH] Application lock (iOS) --- ios/Podfile.lock | 12 +++--- ios/Runner/AppDelegate.swift | 13 ++++++ lib/basic/Method.dart | 4 ++ lib/basic/config/Authentication.dart | 48 +++++++++++++++++++++ lib/basic/config/ChooserRoot.dart | 3 ++ lib/screens/AccountScreen.dart | 14 +++++++ lib/screens/InitScreen.dart | 63 ++++++++++++++++++++++------ lib/screens/SettingsScreen.dart | 30 +++++++------ 8 files changed, 157 insertions(+), 30 deletions(-) create mode 100644 lib/basic/config/Authentication.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 35c5df8..ec8eb0d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -37,7 +37,7 @@ PODS: - image_cropper (0.0.4): - Flutter - TOCropViewController (~> 2.6.1) - - image_picker (0.0.1): + - image_picker_ios (0.0.1): - Flutter - "permission_handler (5.1.0+2)": - Flutter @@ -53,7 +53,7 @@ DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) - - image_picker (from `.symlinks/plugins/image_picker/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - permission_handler (from `.symlinks/plugins/permission_handler/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -72,8 +72,8 @@ EXTERNAL SOURCES: :path: Flutter image_cropper: :path: ".symlinks/plugins/image_cropper/ios" - image_picker: - :path: ".symlinks/plugins/image_picker/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" permission_handler: :path: ".symlinks/plugins/permission_handler/ios" url_launcher_ios: @@ -85,7 +85,7 @@ SPEC CHECKSUMS: file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98 - image_picker: 541dcbb3b9cf32d87eacbd957845d8651d6c62c3 + image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 SDWebImage: 0905f1b7760fc8ac4198cae0036600d67478751e SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 @@ -94,4 +94,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 48d907a..b5944e9 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,6 +1,7 @@ import UIKit import Flutter import Mobile +import LocalAuthentication @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { @@ -34,6 +35,18 @@ import Mobile result(FlutterError(code: "", message: "params error", details: "")) } } + else if call.method == "verifyAuthentication"{ + let context = LAContext() + let can = context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) + guard can == true else { + result(false) + return + } + context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "身份验证") { (success, error) in + result(success) + } + + } else if call.method == "iosSaveFileToImage"{ if let args = call.arguments as? Dictionary, let path = args["path"] as? String{ diff --git a/lib/basic/Method.dart b/lib/basic/Method.dart index 12fba35..0ccf184 100644 --- a/lib/basic/Method.dart +++ b/lib/basic/Method.dart @@ -717,4 +717,8 @@ class Method { List list = json.decode(rsp); return list.map((e) => Collection.fromJson(e)).toList(); } + + Future verifyAuthentication() async { + return await _channel.invokeMethod("verifyAuthentication"); + } } diff --git a/lib/basic/config/Authentication.dart b/lib/basic/config/Authentication.dart new file mode 100644 index 0000000..7a251e0 --- /dev/null +++ b/lib/basic/config/Authentication.dart @@ -0,0 +1,48 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +import '../Common.dart'; +import '../Method.dart'; + +const _propertyName = "authentication"; +late bool _authentication; + +Future initAuthentication() async { + _authentication = + (await method.loadProperty(_propertyName, "false")) == "true"; +} + +bool currentAuthentication() { + return _authentication; +} + +Future _chooseAuthentication(BuildContext context) async { + if (await method.verifyAuthentication()) { + String? result = + await chooseListDialog(context, "进入APP时验证身份", ["是", "否"]); + if (result != null) { + var target = result == "是"; + await method.saveProperty(_propertyName, "$target"); + _authentication = target; + } + } +} + +Widget authenticationSetting() { + if (Platform.isIOS != true) { + return Container(); + } + return StatefulBuilder( + builder: (BuildContext context, void Function(void Function()) setState) { + return ListTile( + title: const Text("进入APP时验证身份"), + subtitle: Text(_authentication ? "是" : "否"), + onTap: () async { + await _chooseAuthentication(context); + setState(() {}); + }, + ); + }, + ); +} diff --git a/lib/basic/config/ChooserRoot.dart b/lib/basic/config/ChooserRoot.dart index 016f6f4..37e37dc 100644 --- a/lib/basic/config/ChooserRoot.dart +++ b/lib/basic/config/ChooserRoot.dart @@ -63,6 +63,9 @@ Future _inputChooserRoot(BuildContext context) async { } Widget chooserRootSetting() { + if (Platform.isIOS) { + return Container(); + } return StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setState) { return ListTile( diff --git a/lib/screens/AccountScreen.dart b/lib/screens/AccountScreen.dart index 4bd39bd..96e2233 100644 --- a/lib/screens/AccountScreen.dart +++ b/lib/screens/AccountScreen.dart @@ -5,6 +5,7 @@ import 'package:pikapika/basic/Method.dart'; import 'package:pikapika/basic/config/Themes.dart'; import 'package:pikapika/basic/enum/ErrorTypes.dart'; import 'package:pikapika/screens/RegisterScreen.dart'; +import 'package:pikapika/screens/SettingsScreen.dart'; import 'package:pikapika/screens/components/NetworkSetting.dart'; import 'AppScreen.dart'; @@ -59,6 +60,19 @@ class _AccountScreenState extends State { appBar: AppBar( title: const Text('配置选项'), actions: [ + IconButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const SettingsScreen( + hiddenAccountInfo: true, + ), + ), + ); + }, + icon: const Text('设置'), + ), IconButton( onPressed: () { if (androidNightModeDisplay) { diff --git a/lib/screens/InitScreen.dart b/lib/screens/InitScreen.dart index e87f586..9152031 100644 --- a/lib/screens/InitScreen.dart +++ b/lib/screens/InitScreen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:pikapika/basic/config/Address.dart'; import 'package:pikapika/basic/config/AndroidDisplayMode.dart'; import 'package:pikapika/basic/config/AndroidSecureFlag.dart'; +import 'package:pikapika/basic/config/Authentication.dart'; import 'package:pikapika/basic/config/AutoClean.dart'; import 'package:pikapika/basic/config/AutoFullScreen.dart'; import 'package:pikapika/basic/config/ChooserRoot.dart'; @@ -43,6 +44,8 @@ class InitScreen extends StatefulWidget { } class _InitScreenState extends State { + var _authenticating = false; + @override initState() { _init(); @@ -83,7 +86,51 @@ class _InitScreenState extends State { await initExportRename(); await initVersion(); await initUsingRightClickPop(); + await initAuthentication(); autoCheckNewVersion(); + setState(() { + _authenticating = currentAuthentication(); + }); + if (_authenticating) { + _goAuthentication(); + } else { + _goApplication(); + } + } + + @override + Widget build(BuildContext context) { + if (_authenticating) { + return Scaffold( + appBar: AppBar( + title: const Text("身份验证"), + ), + body: Center( + child: Container( + padding: const EdgeInsets.all(20), + child: MaterialButton( + onPressed: () { + _goAuthentication(); + }, + child: const Text('您在之前使用APP时开启了身份验证, 请点这段文字进行身份核查, 核查通过后将会进入APP'), + ), + ), + ), + ); + } + return Scaffold( + backgroundColor: const Color(0xfffffced), + body: ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: Image.asset( + "lib/assets/init.jpg", + fit: BoxFit.contain, + ), + ), + ); + } + + Future _goApplication() async { // 登录, 如果token失效重新登录, 网络不好的时候可能需要1分钟 if (await method.preLogin()) { // 如果token或username+password有效则直接进入登录好的界面 @@ -100,17 +147,9 @@ class _InitScreenState extends State { } } - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: const Color(0xfffffced), - body: ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: Image.asset( - "lib/assets/init.jpg", - fit: BoxFit.contain, - ), - ), - ); + Future _goAuthentication() async { + if (await method.verifyAuthentication()) { + _goApplication(); + } } } diff --git a/lib/screens/SettingsScreen.dart b/lib/screens/SettingsScreen.dart index b9ca292..9b53d8e 100644 --- a/lib/screens/SettingsScreen.dart +++ b/lib/screens/SettingsScreen.dart @@ -29,13 +29,17 @@ import 'package:pikapika/basic/config/shadowCategoriesMode.dart'; import 'package:pikapika/screens/components/NetworkSetting.dart'; import 'package:pikapika/screens/components/RightClickPop.dart'; +import '../basic/config/Authentication.dart'; import '../basic/config/UsingRightClickPop.dart'; import 'CleanScreen.dart'; import 'MigrateScreen.dart'; import 'ModifyPasswordScreen.dart'; class SettingsScreen extends StatelessWidget { - const SettingsScreen({Key? key}) : super(key: key); + final bool hiddenAccountInfo; + + const SettingsScreen({Key? key, this.hiddenAccountInfo = false}) + : super(key: key); @override Widget build(BuildContext context) { @@ -51,16 +55,18 @@ class SettingsScreen extends StatelessWidget { body: ListView( children: [ const Divider(), - ListTile( - onTap: () async { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const ModifyPasswordScreen()), - ); - }, - title: const Text('修改密码'), - ), + hiddenAccountInfo + ? Container() + : ListTile( + onTap: () async { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ModifyPasswordScreen()), + ); + }, + title: const Text('修改密码'), + ), const Divider(), const NetworkSetting(), const Divider(), @@ -94,6 +100,7 @@ class SettingsScreen extends StatelessWidget { const Divider(), androidDisplayModeSetting(), androidSecureFlagSetting(), + authenticationSetting(), const Divider(), chooserRootSetting(), downloadThreadCountSetting(), @@ -131,5 +138,4 @@ class SettingsScreen extends StatelessWidget { } return Container(); } - }