tidy: settings page

This commit is contained in:
lollipopkit
2023-09-17 14:30:34 +08:00
parent 1edd54b4df
commit 603e226995
16 changed files with 421 additions and 475 deletions

View File

@@ -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');
}
}

View File

@@ -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 = () {

View File

@@ -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;
}

View File

@@ -36,5 +36,6 @@ class GithubIds {
'xgzxmytx',
'wind057',
'a1564471347',
'fanzhebufan1',
};
}

View File

@@ -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<ConvertPage>
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<String> 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<int>(
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;
}

View File

@@ -120,7 +120,7 @@ class _FullScreenPageState extends State<FullScreenPage> with AfterLayoutMixin {
Widget _buildSettingBtn() {
return IconButton(
onPressed: () => AppRoute.setting().go(context),
onPressed: () => AppRoute.settings().go(context),
icon: const Icon(Icons.settings, color: Colors.grey));
}

View File

@@ -233,7 +233,7 @@ class _HomePageState extends State<HomePage>
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<HomePage>
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),

View File

@@ -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<AndroidSettingsPage> {
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<String, String> old) {
context.pop();
try {
final map = Map<String, String>.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 = <String, String>{};
_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),
),
],
);
},
);
}
}

View File

@@ -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<SettingPage> {
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<SettingPage> {
final _keyboardType = ValueNotifier(0);
final _rotateQuarter = ValueNotifier(0);
final _netViewType = ValueNotifier(NetViewType.speed);
final _pushToken = ValueNotifier<String?>(null);
@override
void didChangeDependencies() {
@@ -103,7 +93,6 @@ class _SettingPageState extends State<SettingPage> {
_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<SettingPage> {
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<SettingPage> {
),
],
),
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<SettingPage> {
//_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<SettingPage> {
children: [
_buildMoveOutServerFuncBtns(),
_buildServerOrder(),
_buildServerDetailOrder(),
_buildNetViewType(),
_buildUpdateInterval(),
_buildMaxRetry(),
@@ -523,42 +506,6 @@ class _SettingPageState extends State<SettingPage> {
}
}
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<String?>(
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<SettingPage> {
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<SettingPage> {
);
}
void _saveWidgetSP(String data, Map<String, String> old) {
context.pop();
try {
final map = Map<String, String>.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 = <String, String>{};
_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<SettingPage> {
);
}
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<SettingPage> {
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<SettingPage> {
);
}
Widget _buildWatchApp() {
return FutureWidget<Map<String, dynamic>?>(
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<String, dynamic> 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<String, dynamic>;
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);
}
}
}

View File

@@ -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<IOSSettingsPage> {
late S _s;
final _pushToken = ValueNotifier<String?>(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<String?>(
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<Map<String, dynamic>?>(
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<String, dynamic> 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<String, dynamic>;
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);
}
}
}

View File

@@ -50,7 +50,7 @@ class _ServerDetailOrderPageState extends State<ServerDetailOrderPage> {
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,

View File

@@ -34,6 +34,9 @@ class _ServerOrderPageState extends State<ServerOrderPage> {
}
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(() {