diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4b16f871..57638642 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -586,7 +586,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -594,7 +594,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -718,7 +718,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -726,7 +726,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -744,7 +744,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -752,7 +752,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -773,7 +773,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -786,7 +786,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; @@ -812,7 +812,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -825,7 +825,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -848,7 +848,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -861,7 +861,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -884,7 +884,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -896,7 +896,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; @@ -925,7 +925,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -937,7 +937,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; @@ -963,7 +963,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -975,7 +975,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme index 021f347f..010c2a29 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme @@ -15,7 +15,7 @@ @@ -58,7 +58,7 @@ @@ -75,7 +75,7 @@ diff --git a/ios/StatusWidget/StatusWidget.swift b/ios/StatusWidget/StatusWidget.swift index 5c7b09f5..82f4a5b9 100644 --- a/ios/StatusWidget/StatusWidget.swift +++ b/ios/StatusWidget/StatusWidget.swift @@ -100,7 +100,7 @@ struct StatusWidget_Previews: PreviewProvider { struct StatusLoader { static func fetch(completion: @escaping (Result) -> Void) { - guard let url = url, url.count < 12 else { + guard let url = url, url.count >= 12 else { completion(.failure(NSError(domain: domain, code: 0, userInfo: [NSLocalizedDescriptionKey: "https://github.com/lollipopkit/server_box_monitor/wiki"]))) return } @@ -109,7 +109,7 @@ struct StatusLoader { return } let task = URLSession.shared.dataTask(with: url) { (data, response, error) in - guard error == nil else { + if error != nil { completion(.failure(error!)) return } @@ -117,8 +117,7 @@ struct StatusLoader { completion(.failure(NSError(domain: domain, code: 2, userInfo: [NSLocalizedDescriptionKey: "empty network data."]))) return } - let result = getStatus(fromData: data) - switch result { + switch getStatus(fromData: data) { case .success(let status): completion(.success(status)) case .failure(let error): @@ -129,58 +128,36 @@ struct StatusLoader { } static func getStatus(fromData data: Foundation.Data) -> Result { - let jsonAll = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] - let code = jsonAll["code"] as! Int + let jsonAll = try! JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] ?? [:] + let code = jsonAll["code"] as? Int ?? 1 if (code != 0) { switch (code) { default: - let msg = jsonAll["msg"] as! String? ?? "" + let msg = jsonAll["msg"] as? String ?? "" return .failure(NSError(domain: domain, code: code, userInfo: [NSLocalizedDescriptionKey: msg])) } } - let json = jsonAll["data"] as! [String: Any] - let name = json["name"] as! String - let disk = json["disk"] as! String - let cpu = json["cpu"] as! String - let mem = json["mem"] as! String - let net = json["net"] as! String + let json = jsonAll["data"] as? [String: Any] ?? [:] + let name = json["name"] as? String ?? "" + let disk = json["disk"] as? String ?? "" + let cpu = json["cpu"] as? String ?? "" + let mem = json["mem"] as? String ?? "" + let net = json["net"] as? String ?? "" return .success(Status(name: name, cpu: cpu, mem: mem, disk: disk, net: net)) } } -private func dynamicUIColor(color: DynamicColor) -> UIColor { - if #available(iOS 13, *) { // 版本号大于等于13 - return UIColor { (traitCollection: UITraitCollection) -> UIColor in - return traitCollection.userInterfaceStyle == UIUserInterfaceStyle.dark ? - color.dark : color.light - } -} - return color.light -} - -struct DynamicColor { - let dark: UIColor - let light: UIColor -} - -let bgColor = DynamicColor(dark: UIColor(.black), light: UIColor(.white)) -let textColor = DynamicColor(dark: UIColor(.white), light: UIColor(.black)) - -private func dynamicColor(color: DynamicColor) -> Color { - return Color.init(dynamicUIColor(color: color)) -} - struct DetailItem: View { let icon: String let text: String let color: Color var body: some View { - HStack(spacing: 7.7) { + HStack(spacing: 6.7) { Image(systemName: icon).resizable().foregroundColor(color).frame(width: 11, height: 11, alignment: .center) Text(text) - .font(.system(.caption2, design: .monospaced)) + .font(.system(size: 11, design: .monospaced)) .foregroundColor(color) } } diff --git a/lib/core/route.dart b/lib/core/route.dart index a9db118e..4c0d3594 100644 --- a/lib/core/route.dart +++ b/lib/core/route.dart @@ -9,12 +9,13 @@ import 'package:toolbox/view/page/ping.dart'; import 'package:toolbox/view/page/private_key/edit.dart'; import 'package:toolbox/view/page/private_key/list.dart'; import 'package:toolbox/view/page/server/detail.dart'; +import 'package:toolbox/view/page/setting/android.dart'; +import 'package:toolbox/view/page/setting/ios.dart'; import 'package:toolbox/view/page/ssh_term.dart'; import 'package:toolbox/view/page/setting/virt_key.dart'; import 'package:toolbox/view/page/storage/local.dart'; import '../data/model/server/snippet.dart'; -import '../view/page/convert.dart'; import '../view/page/debug.dart'; import '../view/page/editor.dart'; import '../view/page/full_screen.dart'; @@ -143,10 +144,6 @@ class AppRoute { return AppRoute(BackupPage(key: key), 'backup'); } - static AppRoute convert({Key? key}) { - return AppRoute(ConvertPage(key: key), 'convert'); - } - static AppRoute debug({Key? key}) { return AppRoute(DebugPage(key: key), 'debug'); } @@ -191,7 +188,7 @@ class AppRoute { return AppRoute(ProcessPage(key: key, spi: spi), 'process'); } - static AppRoute setting({Key? key}) { + static AppRoute settings({Key? key}) { return AppRoute(SettingPage(key: key), 'setting'); } @@ -202,4 +199,12 @@ class AppRoute { static AppRoute serverDetailOrder({Key? key}) { return AppRoute(ServerDetailOrderPage(key: key), 'server_detail_order'); } + + static AppRoute iosSettings({Key? key}) { + return AppRoute(IOSSettingsPage(key: key), 'ios_setting'); + } + + static AppRoute androidSettings({Key? key}) { + return AppRoute(AndroidSettingsPage(key: key), 'android_setting'); + } } diff --git a/lib/core/utils/platform/base.dart b/lib/core/utils/platform/base.dart index cf7dafb0..c9b69fc4 100644 --- a/lib/core/utils/platform/base.dart +++ b/lib/core/utils/platform/base.dart @@ -11,6 +11,38 @@ enum PlatformType { web, fuchsia, unknown; + + String get prettyName { + switch (this) { + case PlatformType.android: + return 'Android'; + case PlatformType.ios: + return 'iOS'; + case PlatformType.linux: + return 'Linux'; + case PlatformType.macos: + return 'macOS'; + case PlatformType.windows: + return 'Windows'; + case PlatformType.web: + return 'Web'; + case PlatformType.fuchsia: + return 'Fuchsia'; + case PlatformType.unknown: + return 'Unknown'; + } + } + + /// Whether has platform specific settings. + bool get hasSettings { + switch (this) { + case PlatformType.android: + case PlatformType.ios: + return true; + default: + return false; + } + } } final _p = () { diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index cb702076..a010f02c 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,9 +2,9 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 561; + static const int build = 562; static const String engine = "3.13.2"; - static const String buildAt = "2023-09-17 00:01:27"; - static const int modifications = 8; + static const String buildAt = "2023-09-17 00:26:50"; + static const int modifications = 2; static const int script = 15; } diff --git a/lib/data/res/github_id.dart b/lib/data/res/github_id.dart index dce4e1da..f8fd0836 100644 --- a/lib/data/res/github_id.dart +++ b/lib/data/res/github_id.dart @@ -36,5 +36,6 @@ class GithubIds { 'xgzxmytx', 'wind057', 'a1564471347', + 'fanzhebufan1', }; } diff --git a/lib/view/page/convert.dart b/lib/view/page/convert.dart deleted file mode 100644 index fb2f6784..00000000 --- a/lib/view/page/convert.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:toolbox/core/extension/context/snackbar.dart'; -import 'package:toolbox/data/res/ui.dart'; -import 'package:toolbox/view/widget/value_notifier.dart'; - -import '../widget/custom_appbar.dart'; -import '../widget/input_field.dart'; -import '../widget/popup_menu.dart'; -import '../widget/round_rect_card.dart'; - -class ConvertPage extends StatefulWidget { - const ConvertPage({Key? key}) : super(key: key); - - @override - _ConvertPageState createState() => _ConvertPageState(); -} - -class _ConvertPageState extends State - with AutomaticKeepAliveClientMixin { - late TextEditingController _textEditingController; - late TextEditingController _textEditingControllerResult; - late MediaQueryData _media; - late S _s; - - final _typeOptionIndex = ValueNotifier(0); - - @override - void initState() { - super.initState(); - _textEditingController = TextEditingController(text: ''); - _textEditingControllerResult = TextEditingController(text: ''); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _media = MediaQuery.of(context); - _s = S.of(context)!; - } - - @override - void dispose() { - super.dispose(); - _textEditingController.dispose(); - _textEditingControllerResult.dispose(); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return Scaffold( - appBar: CustomAppBar( - title: Text(_s.convert), - ), - body: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 7), - controller: ScrollController(), - child: Column( - children: [ - UIs.height13, - _buildInputTop(), - _buildMiddleBtns(), - _buildResult(), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - try { - _textEditingControllerResult.text = doConvert(); - FocusScope.of(context).requestFocus(FocusNode()); - } catch (e) { - context.showSnackBar('Error: \n$e'); - } - }, - tooltip: _s.convert, - child: const Icon(Icons.send), - ), - ); - } - - String doConvert() { - final text = _textEditingController.text.trim(); - switch (_typeOptionIndex.value) { - case 0: - return utf8.decode(base64.decode(text)); - case 1: - return base64.encode(utf8.encode(text)); - case 2: - return Uri.encodeFull(text); - case 3: - return Uri.decodeFull(text); - default: - return _s.unkownConvertMode; - } - } - - Widget _buildInputTop() { - return SizedBox( - height: _media.size.height * 0.33, - child: Input(controller: _textEditingController), - ); - } - - Widget _buildMiddleBtns() { - final decode = _s.decode; - final encode = _s.encode; - final List typeOption = [ - 'Base64 $decode', - 'Base64 $encode', - 'URL $encode', - 'URL $decode' - ]; - final items = typeOption - .map( - (e) => PopupMenuItem(value: typeOption.indexOf(e), child: Text(e)), - ) - .toList(); - return RoundRectCard( - ListTile( - contentPadding: const EdgeInsets.only(right: 17), - title: Row( - children: [ - TextButton( - child: Icon(Icons.change_circle, semanticLabel: _s.upsideDown), - onPressed: () { - final temp = _textEditingController.text; - _textEditingController.text = _textEditingControllerResult.text; - _textEditingControllerResult.text = temp; - }, - ), - TextButton( - child: Icon(Icons.copy, semanticLabel: _s.copy), - onPressed: () => Clipboard.setData( - ClipboardData( - text: _textEditingControllerResult.text == '' - ? '' - : _textEditingControllerResult.text, - ), - ), - ) - ], - ), - trailing: ValueBuilder( - listenable: _typeOptionIndex, - build: () => PopupMenu( - items: items, - initialValue: _typeOptionIndex.value, - onSelected: (p0) { - _typeOptionIndex.value = p0; - }, - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - typeOption[_typeOptionIndex.value], - textScaleFactor: 1.0, - textAlign: TextAlign.right, - style: const TextStyle( - fontWeight: FontWeight.w500, - color: Colors.grey, - ), - ), - const Icon(Icons.keyboard_arrow_down, color: Colors.grey) - ], - ), - ), - ), - ), - ); - } - - Widget _buildResult() { - return SizedBox( - height: _media.size.height * 0.33, - child: Input(controller: _textEditingControllerResult), - ); - } - - @override - bool get wantKeepAlive => true; -} diff --git a/lib/view/page/full_screen.dart b/lib/view/page/full_screen.dart index e5aff381..3b054e49 100644 --- a/lib/view/page/full_screen.dart +++ b/lib/view/page/full_screen.dart @@ -120,7 +120,7 @@ class _FullScreenPageState extends State with AfterLayoutMixin { Widget _buildSettingBtn() { return IconButton( - onPressed: () => AppRoute.setting().go(context), + onPressed: () => AppRoute.settings().go(context), icon: const Icon(Icons.settings, color: Colors.grey)); } diff --git a/lib/view/page/home.dart b/lib/view/page/home.dart index 422d4651..30f63dcc 100644 --- a/lib/view/page/home.dart +++ b/lib/view/page/home.dart @@ -233,7 +233,7 @@ class _HomePageState extends State ListTile( leading: const Icon(Icons.settings), title: Text(_s.setting), - onTap: () => AppRoute.setting().go(context), + onTap: () => AppRoute.settings().go(context), onLongPress: _onLongPressSetting, ), ListTile( @@ -246,11 +246,6 @@ class _HomePageState extends State title: Text(_s.files), onTap: () => AppRoute.localStorage().go(context), ), - ListTile( - leading: const Icon(Icons.code), - title: Text(_s.convert), - onTap: () => AppRoute.convert().go(context), - ), ListTile( leading: const Icon(Icons.import_export), title: Text(_s.backupAndRestore), diff --git a/lib/view/page/setting/android.dart b/lib/view/page/setting/android.dart new file mode 100644 index 00000000..f204446f --- /dev/null +++ b/lib/view/page/setting/android.dart @@ -0,0 +1,113 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:toolbox/core/extension/context/common.dart'; +import 'package:toolbox/core/extension/context/dialog.dart'; +import 'package:toolbox/core/extension/context/snackbar.dart'; +import 'package:toolbox/data/res/store.dart'; +import 'package:toolbox/view/widget/custom_appbar.dart'; +import 'package:toolbox/view/widget/input_field.dart'; +import 'package:toolbox/view/widget/round_rect_card.dart'; +import 'package:toolbox/view/widget/store_switch.dart'; + +class AndroidSettingsPage extends StatefulWidget { + const AndroidSettingsPage({Key? key}) : super(key: key); + + @override + _AndroidSettingsPageState createState() => _AndroidSettingsPageState(); +} + +class _AndroidSettingsPageState extends State { + late S _s; + late SharedPreferences _sp; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _s = S.of(context)!; + } + + @override + void initState() { + super.initState(); + SharedPreferences.getInstance().then((value) => _sp = value); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: const Text('Android'), + ), + body: ListView( + padding: const EdgeInsets.symmetric(horizontal: 17), + children: [ + _buildBgRun(), + _buildAndroidWidgetSharedPreference(), + ].map((e) => RoundRectCard(e)).toList(), + ), + ); + } + + Widget _buildBgRun() { + return ListTile( + title: Text(_s.bgRun), + trailing: StoreSwitch(prop: Stores.setting.bgRun), + ); + } + + void _saveWidgetSP(String data, Map old) { + context.pop(); + try { + final map = Map.from(json.decode(data)); + final keysDel = old.keys.toSet().difference(map.keys.toSet()); + for (final key in keysDel) { + _sp.remove(key); + } + map.forEach((key, value) { + _sp.setString(key, value); + }); + context.showSnackBar(_s.success); + } catch (e) { + context.showSnackBar(e.toString()); + } + } + + Widget _buildAndroidWidgetSharedPreference() { + return ListTile( + title: Text(_s.homeWidgetUrlConfig), + trailing: const Icon(Icons.keyboard_arrow_right), + onTap: () { + final data = {}; + _sp.getKeys().forEach((key) { + final val = _sp.getString(key); + if (val != null) { + data[key] = val; + } + }); + final ctrl = TextEditingController(text: json.encode(data)); + context.showRoundDialog( + title: Text(_s.homeWidgetUrlConfig), + child: Input( + autoFocus: true, + controller: ctrl, + label: 'JSON', + type: TextInputType.visiblePassword, + maxLines: 7, + onSubmitted: (p0) => _saveWidgetSP(p0, data), + ), + actions: [ + TextButton( + onPressed: () { + _saveWidgetSP(ctrl.text, data); + }, + child: Text(_s.ok), + ), + ], + ); + }, + ); + } +} diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index 687a0d5e..0b151d9c 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -1,12 +1,9 @@ -import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_highlight/theme_map.dart'; import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:toolbox/core/build_mode.dart'; import 'package:toolbox/core/extension/colorx.dart'; import 'package:toolbox/core/extension/context/common.dart'; import 'package:toolbox/core/extension/context/snackbar.dart'; @@ -15,11 +12,8 @@ import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/stringx.dart'; import 'package:toolbox/core/utils/platform/auth.dart'; import 'package:toolbox/core/utils/platform/base.dart'; -import 'package:toolbox/data/res/logger.dart'; -import 'package:toolbox/data/res/misc.dart'; import 'package:toolbox/data/res/provider.dart'; import 'package:toolbox/data/res/store.dart'; -import 'package:watch_connectivity/watch_connectivity.dart'; import '../../../core/persistant_store.dart'; import '../../../core/route.dart'; @@ -59,9 +53,6 @@ class _SettingPageState extends State { final _setting = Stores.setting; late S _s; - late SharedPreferences _sp; - - final wc = WatchConnectivity(); final _selectedColorValue = ValueNotifier(0); final _nightMode = ValueNotifier(0); @@ -75,7 +66,6 @@ class _SettingPageState extends State { final _keyboardType = ValueNotifier(0); final _rotateQuarter = ValueNotifier(0); final _netViewType = ValueNotifier(NetViewType.speed); - final _pushToken = ValueNotifier(null); @override void didChangeDependencies() { @@ -103,7 +93,6 @@ class _SettingPageState extends State { _keyboardType.value = _setting.keyboardType.fetch(); _rotateQuarter.value = _setting.fullScreenRotateQuarter.fetch(); _netViewType.value = _setting.netViewType.fetch(); - SharedPreferences.getInstance().then((value) => _sp = value); } @override @@ -112,8 +101,8 @@ class _SettingPageState extends State { appBar: CustomAppBar( title: Text(_s.setting), actions: [ - InkWell( - onTap: () => context.showRoundDialog( + IconButton( + onPressed: () => context.showRoundDialog( title: Text(_s.attention), child: Text(_s.sureDelete(_s.all)), actions: [ @@ -127,26 +116,26 @@ class _SettingPageState extends State { ), ], ), - onDoubleTap: () => context.showRoundDialog( - title: Text(_s.attention), - child: Text(_s.sureDelete(_s.all)), - actions: [ - TextButton( - onPressed: () { - Stores.docker.box.deleteFromDisk(); - Stores.server.box.deleteFromDisk(); - Stores.setting.box.deleteFromDisk(); - Stores.history.box.deleteFromDisk(); - Stores.snippet.box.deleteFromDisk(); - Stores.key.box.deleteFromDisk(); - context.pop(); - context.showSnackBar(_s.success); - }, - child: Text(_s.ok, style: const TextStyle(color: Colors.red)), - ), - ], - ), - child: const Icon(Icons.delete), + // onDoubleTap: () => context.showRoundDialog( + // title: Text(_s.attention), + // child: Text(_s.sureDelete(_s.all)), + // actions: [ + // TextButton( + // onPressed: () { + // Stores.docker.box.deleteFromDisk(); + // Stores.server.box.deleteFromDisk(); + // Stores.setting.box.deleteFromDisk(); + // Stores.history.box.deleteFromDisk(); + // Stores.snippet.box.deleteFromDisk(); + // Stores.key.box.deleteFromDisk(); + // context.pop(); + // context.showSnackBar(_s.success); + // }, + // child: Text(_s.ok, style: const TextStyle(color: Colors.red)), + // ), + // ], + // ), + icon: const Icon(Icons.delete), ), ], ), @@ -189,17 +178,12 @@ class _SettingPageState extends State { //_buildLaunchPage(), _buildCheckUpdate(), ]; - if (isAndroid) { - children.add(_buildBgRun()); - children.add(_buildAndroidWidgetSharedPreference()); - } if (BioAuth.isPlatformSupported) { children.add(_buildBioAuth()); } - if (isIOS) { - if (BuildMode.isRelease) children.add(_buildPushToken()); - children.add(_buildAutoUpdateHomeWidget()); - children.add(_buildWatchApp()); + /// Platform specific settings + if (platform.hasSettings) { + children.add(_buildPlatformSetting(platform)); } return Column( children: children.map((e) => RoundRectCard(e)).toList(), @@ -221,7 +205,6 @@ class _SettingPageState extends State { children: [ _buildMoveOutServerFuncBtns(), _buildServerOrder(), - _buildServerDetailOrder(), _buildNetViewType(), _buildUpdateInterval(), _buildMaxRetry(), @@ -523,42 +506,6 @@ class _SettingPageState extends State { } } - Widget _buildPushToken() { - return ListTile( - title: Text( - _s.pushToken, - ), - trailing: IconButton( - icon: const Icon(Icons.copy), - alignment: Alignment.centerRight, - padding: EdgeInsets.zero, - onPressed: () { - if (_pushToken.value != null) { - copy2Clipboard(_pushToken.value!); - context.showSnackBar(_s.success); - } else { - context.showSnackBar(_s.getPushTokenFailed); - } - }, - ), - subtitle: FutureWidget( - future: getToken(), - loading: Text(_s.gettingToken), - error: (error, trace) => Text('${_s.error}: $error'), - noData: Text(_s.nullToken), - success: (text) { - _pushToken.value = text; - return Text( - text ?? _s.nullToken, - style: UIs.textGrey, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ); - }, - ), - ); - } - Widget _buildFont() { final fontName = getFileName(_setting.fontPath.fetch()); return ListTile( @@ -612,13 +559,6 @@ class _SettingPageState extends State { context.showSnackBar(_s.failed); } - Widget _buildBgRun() { - return ListTile( - title: Text(_s.bgRun), - trailing: StoreSwitch(prop: _setting.bgRun), - ); - } - Widget _buildTermFontSize() { return ValueBuilder( listenable: _termFontSize, @@ -891,59 +831,6 @@ class _SettingPageState extends State { ); } - void _saveWidgetSP(String data, Map old) { - context.pop(); - try { - final map = Map.from(json.decode(data)); - final keysDel = old.keys.toSet().difference(map.keys.toSet()); - for (final key in keysDel) { - _sp.remove(key); - } - map.forEach((key, value) { - _sp.setString(key, value); - }); - context.showSnackBar(_s.success); - } catch (e) { - context.showSnackBar(e.toString()); - } - } - - Widget _buildAndroidWidgetSharedPreference() { - return ListTile( - title: Text(_s.homeWidgetUrlConfig), - trailing: const Icon(Icons.keyboard_arrow_right), - onTap: () { - final data = {}; - _sp.getKeys().forEach((key) { - final val = _sp.getString(key); - if (val != null) { - data[key] = val; - } - }); - final ctrl = TextEditingController(text: json.encode(data)); - context.showRoundDialog( - title: Text(_s.homeWidgetUrlConfig), - child: Input( - autoFocus: true, - controller: ctrl, - label: 'JSON', - type: TextInputType.visiblePassword, - maxLines: 7, - onSubmitted: (p0) => _saveWidgetSP(p0, data), - ), - actions: [ - TextButton( - onPressed: () { - _saveWidgetSP(ctrl.text, data); - }, - child: Text(_s.ok), - ), - ], - ); - }, - ); - } - Widget _buildNetViewType() { final items = NetViewType.values .map((e) => PopupMenuItem( @@ -975,14 +862,6 @@ class _SettingPageState extends State { ); } - Widget _buildAutoUpdateHomeWidget() { - return ListTile( - title: Text(_s.autoUpdateHomeWidget), - subtitle: Text(_s.whenOpenApp, style: UIs.textGrey), - trailing: StoreSwitch(prop: _setting.autoUpdateHomeWidget), - ); - } - Widget _buildDeleteServers() { return ListTile( title: Text(_s.deleteServers), @@ -1027,16 +906,27 @@ class _SettingPageState extends State { Widget _buildServerOrder() { return ListTile( title: Text(_s.serverOrder), + subtitle: Text('${_s.serverOrder} / ${_s.serverDetailOrder}', + style: UIs.textGrey), trailing: const Icon(Icons.keyboard_arrow_right), - onTap: () => AppRoute.serverOrder().go(context), - ); - } - - Widget _buildServerDetailOrder() { - return ListTile( - title: Text(_s.serverDetailOrder), - trailing: const Icon(Icons.keyboard_arrow_right), - onTap: () => AppRoute.serverDetailOrder().go(context), + onTap: () => context.showRoundDialog( + title: Text(_s.choose), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text(_s.serverOrder), + trailing: const Icon(Icons.keyboard_arrow_right), + onTap: () => AppRoute.serverOrder().go(context), + ), + ListTile( + title: Text(_s.serverDetailOrder), + trailing: const Icon(Icons.keyboard_arrow_right), + onTap: () => AppRoute.serverDetailOrder().go(context), + ), + ], + ), + ), ); } @@ -1148,59 +1038,22 @@ class _SettingPageState extends State { ); } - Widget _buildWatchApp() { - return FutureWidget?>( - future: () async { - if (!await wc.isPaired) { - return null; + Widget _buildPlatformSetting(PlatformType type) { + return ListTile( + title: Text('${type.prettyName} ${_s.setting}'), + trailing: const Icon(Icons.keyboard_arrow_right), + onTap: () { + switch (type) { + case PlatformType.android: + AppRoute.androidSettings().go(context); + break; + case PlatformType.ios: + AppRoute.iosSettings().go(context); + break; + default: + break; } - return await wc.applicationContext; - }(), - loading: UIs.centerLoading, - error: (e, trace) { - Loggers.app.warning('WatchOS error', e, trace); - return ListTile( - title: const Text('Watch app'), - subtitle: Text('${_s.error}: $e', style: UIs.textGrey), - ); }, - success: (ctx) { - if (ctx == null) { - return ListTile( - title: const Text('Watch app'), - subtitle: Text(_s.watchNotPaired, style: UIs.textGrey), - ); - } - return ListTile( - title: const Text('Watch app'), - trailing: const Icon(Icons.keyboard_arrow_right), - onTap: () async => _onTapWatchApp(ctx), - ); - }, - noData: UIs.placeholder, ); } - - void _onTapWatchApp(Map map) async { - /// Encode [map] to String with indent `\t` - final text = Miscs.jsonEncoder.convert(map); - final result = await AppRoute.editor( - text: text, - langCode: 'json', - title: 'Watch app', - ).go(context); - if (result == null) { - return; - } - try { - final newCtx = json.decode(result) as Map; - await wc.updateApplicationContext(newCtx); - } catch (e, trace) { - context.showRoundDialog( - title: Text(_s.error), - child: Text('${_s.save}:\n$e'), - ); - Loggers.app.warning('Update watch config failed', e, trace); - } - } } diff --git a/lib/view/page/setting/ios.dart b/lib/view/page/setting/ios.dart new file mode 100644 index 00000000..78ba4bce --- /dev/null +++ b/lib/view/page/setting/ios.dart @@ -0,0 +1,155 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:toolbox/core/extension/context/dialog.dart'; +import 'package:toolbox/core/extension/context/snackbar.dart'; +import 'package:toolbox/core/route.dart'; +import 'package:toolbox/core/utils/misc.dart'; +import 'package:toolbox/data/res/logger.dart'; +import 'package:toolbox/data/res/misc.dart'; +import 'package:toolbox/data/res/store.dart'; +import 'package:toolbox/data/res/ui.dart'; +import 'package:toolbox/view/widget/custom_appbar.dart'; +import 'package:toolbox/view/widget/future_widget.dart'; +import 'package:toolbox/view/widget/round_rect_card.dart'; +import 'package:toolbox/view/widget/store_switch.dart'; +import 'package:watch_connectivity/watch_connectivity.dart'; + +class IOSSettingsPage extends StatefulWidget { + const IOSSettingsPage({Key? key}) : super(key: key); + + @override + _IOSSettingsPageState createState() => _IOSSettingsPageState(); +} + +class _IOSSettingsPageState extends State { + late S _s; + + final _pushToken = ValueNotifier(null); + + final wc = WatchConnectivity(); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _s = S.of(context)!; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: const Text('iOS'), + ), + body: ListView( + padding: const EdgeInsets.symmetric(horizontal: 17), + children: [ + _buildPushToken(), + _buildAutoUpdateHomeWidget(), + _buildWatchApp(), + ].map((e) => RoundRectCard(e)).toList(), + ), + ); + } + + Widget _buildPushToken() { + return ListTile( + title: Text( + _s.pushToken, + ), + trailing: IconButton( + icon: const Icon(Icons.copy), + alignment: Alignment.centerRight, + padding: EdgeInsets.zero, + onPressed: () { + if (_pushToken.value != null) { + copy2Clipboard(_pushToken.value!); + context.showSnackBar(_s.success); + } else { + context.showSnackBar(_s.getPushTokenFailed); + } + }, + ), + subtitle: FutureWidget( + future: getToken(), + loading: Text(_s.gettingToken), + error: (error, trace) => Text('${_s.error}: $error'), + noData: Text(_s.nullToken), + success: (text) { + _pushToken.value = text; + return Text( + text ?? _s.nullToken, + style: UIs.textGrey, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ); + }, + ), + ); + } + + Widget _buildAutoUpdateHomeWidget() { + return ListTile( + title: Text(_s.autoUpdateHomeWidget), + subtitle: Text(_s.whenOpenApp, style: UIs.textGrey), + trailing: StoreSwitch(prop: Stores.setting.autoUpdateHomeWidget), + ); + } + + Widget _buildWatchApp() { + return FutureWidget?>( + future: () async { + if (!await wc.isPaired) { + return null; + } + return await wc.applicationContext; + }(), + loading: UIs.centerLoading, + error: (e, trace) { + Loggers.app.warning('WatchOS error', e, trace); + return ListTile( + title: const Text('Watch app'), + subtitle: Text('${_s.error}: $e', style: UIs.textGrey), + ); + }, + success: (ctx) { + if (ctx == null) { + return ListTile( + title: const Text('Watch app'), + subtitle: Text(_s.watchNotPaired, style: UIs.textGrey), + ); + } + return ListTile( + title: const Text('Watch app'), + trailing: const Icon(Icons.keyboard_arrow_right), + onTap: () async => _onTapWatchApp(ctx), + ); + }, + noData: UIs.placeholder, + ); + } + + void _onTapWatchApp(Map map) async { + /// Encode [map] to String with indent `\t` + final text = Miscs.jsonEncoder.convert(map); + final result = await AppRoute.editor( + text: text, + langCode: 'json', + title: 'Watch app', + ).go(context); + if (result == null) { + return; + } + try { + final newCtx = json.decode(result) as Map; + await wc.updateApplicationContext(newCtx); + } catch (e, trace) { + context.showRoundDialog( + title: Text(_s.error), + child: Text('${_s.save}:\n$e'), + ); + Loggers.app.warning('Update watch config failed', e, trace); + } + } +} diff --git a/lib/view/page/setting/srv_detail_seq.dart b/lib/view/page/setting/srv_detail_seq.dart index 9c00b4b7..92088070 100644 --- a/lib/view/page/setting/srv_detail_seq.dart +++ b/lib/view/page/setting/srv_detail_seq.dart @@ -50,7 +50,7 @@ class _ServerDetailOrderPageState extends State { property: Stores.setting.detailCardOrder, ); }), - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), + padding: const EdgeInsets.all(17), buildDefaultDragHandles: false, itemBuilder: (_, index) => _buildItem(index, _cardsOrder[index]), itemCount: _cardsOrder.length, diff --git a/lib/view/page/setting/srv_seq.dart b/lib/view/page/setting/srv_seq.dart index 21887bbd..f64ef7be 100644 --- a/lib/view/page/setting/srv_seq.dart +++ b/lib/view/page/setting/srv_seq.dart @@ -34,6 +34,9 @@ class _ServerOrderPageState extends State { } Widget _buildBody() { + if (Providers.server.serverOrder.isEmpty) { + return Center(child: Text(_s.noServerAvailable)); + } return ReorderableListView.builder( footer: const SizedBox(height: 77), onReorder: (oldIndex, newIndex) => setState(() { diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index da056f4e..25107c2d 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -476,9 +476,9 @@ baseConfigurationReference = C1C758C41C4E208965A68933 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -491,9 +491,9 @@ baseConfigurationReference = 15AF97DF993E8968098D6EBE /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -506,9 +506,9 @@ baseConfigurationReference = 7CFA7DE7FABA75685DFB6948 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 561; + CURRENT_PROJECT_VERSION = 562; GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0.561; + MARKETING_VERSION = 1.0.562; PRODUCT_BUNDLE_IDENTIFIER = tech.lolli.serverBox.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0;