mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
结构初始化
This commit is contained in:
27
lib/app.dart
Normal file
27
lib/app.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/page/home.dart';
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'ToolBox',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(title: 'ToolBox'),
|
||||
);
|
||||
}
|
||||
}
|
||||
34
lib/core/analysis.dart
Normal file
34
lib/core/analysis.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:countly_flutter/countly_flutter.dart';
|
||||
|
||||
class Analysis {
|
||||
static const _url = 'https://countly.xuty.cc';
|
||||
static const _key = '80372a2a66424b32d0ac8991bfa1ef058bd36b1f';
|
||||
|
||||
static bool _enabled = false;
|
||||
|
||||
static Future<void> init(bool debug) async {
|
||||
if (_url.isEmpty || _key.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_enabled = true;
|
||||
await Countly.setLoggingEnabled(debug);
|
||||
await Countly.init(_url, _key);
|
||||
await Countly.start();
|
||||
await Countly.enableCrashReporting();
|
||||
await Countly.giveAllConsent();
|
||||
print('Countly init successfully.');
|
||||
}
|
||||
|
||||
static void recordView(String view) {
|
||||
if (!_enabled) return;
|
||||
Countly.recordView(view);
|
||||
}
|
||||
|
||||
static void recordException(Object exception, [bool fatal = false]) {
|
||||
if (!_enabled) return;
|
||||
Countly.logException(exception.toString(), !fatal, null);
|
||||
}
|
||||
}
|
||||
25
lib/core/build_mode.dart
Normal file
25
lib/core/build_mode.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
/// See: https://github.com/flutter/flutter/issues/11392
|
||||
///
|
||||
enum _BuildMode {
|
||||
release,
|
||||
debug,
|
||||
profile,
|
||||
}
|
||||
|
||||
_BuildMode _buildMode = (() {
|
||||
if (const bool.fromEnvironment('dart.vm.product')) {
|
||||
return _BuildMode.release;
|
||||
}
|
||||
var result = _BuildMode.profile;
|
||||
assert(() {
|
||||
result = _BuildMode.debug;
|
||||
return true;
|
||||
}());
|
||||
return result;
|
||||
}());
|
||||
|
||||
class BuildMode {
|
||||
static bool isDebug = (_buildMode == _BuildMode.debug);
|
||||
static bool isProfile = (_buildMode == _BuildMode.profile);
|
||||
static bool isRelease = (_buildMode == _BuildMode.release);
|
||||
}
|
||||
78
lib/core/persistant_store.dart
Normal file
78
lib/core/persistant_store.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
class PersistentStore<E> {
|
||||
late Box<E> box;
|
||||
|
||||
Future<PersistentStore<E>> init({String boxName = 'defaultBox'}) async {
|
||||
box = await Hive.openBox(boxName);
|
||||
return this;
|
||||
}
|
||||
|
||||
StoreProperty<T> property<T>(String key, {T? defaultValue}) {
|
||||
return StoreProperty<T>(box, key, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
class StoreProperty<T> {
|
||||
StoreProperty(this._box, this._key, this.defaultValue);
|
||||
|
||||
final Box _box;
|
||||
final String _key;
|
||||
T? defaultValue;
|
||||
|
||||
ValueListenable<T> listenable() {
|
||||
return PropertyListenable<T>(_box, _key, defaultValue);
|
||||
}
|
||||
|
||||
T? fetch() {
|
||||
return _box.get(_key, defaultValue: defaultValue);
|
||||
}
|
||||
|
||||
Future<void> put(T value) {
|
||||
return _box.put(_key, value);
|
||||
}
|
||||
|
||||
Future<void> delete() {
|
||||
return _box.delete(_key);
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyListenable<T> extends ValueListenable<T> {
|
||||
PropertyListenable(this.box, this.key, this.defaultValue);
|
||||
|
||||
final Box box;
|
||||
final String key;
|
||||
T? defaultValue;
|
||||
|
||||
final List<VoidCallback> _listeners = [];
|
||||
StreamSubscription? _subscription;
|
||||
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
_subscription ??= box.watch().listen((event) {
|
||||
if (key == event.key) {
|
||||
for (var listener in _listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeListener(VoidCallback listener) {
|
||||
_listeners.remove(listener);
|
||||
|
||||
if (_listeners.isEmpty) {
|
||||
_subscription?.cancel();
|
||||
_subscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
T get value => box.get(key, defaultValue: defaultValue);
|
||||
}
|
||||
36
lib/core/provider_base.dart
Normal file
36
lib/core/provider_base.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ProviderBase with ChangeNotifier {
|
||||
void setState(void Function() callback) {
|
||||
callback();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
enum ProviderState {
|
||||
idle,
|
||||
busy,
|
||||
}
|
||||
|
||||
class BusyProvider extends ProviderBase {
|
||||
bool _isBusy = false;
|
||||
bool get isBusy => _isBusy;
|
||||
|
||||
setBusyState([bool isBusy = true]) {
|
||||
_isBusy = isBusy;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
FutureOr<T> busyRun<T>(FutureOr<T> Function() func) async {
|
||||
setBusyState(true);
|
||||
try {
|
||||
return await Future.sync(func);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
} finally {
|
||||
setBusyState(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
lib/core/route.dart
Normal file
14
lib/core/route.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/analysis.dart';
|
||||
|
||||
class AppRoute {
|
||||
final Widget page;
|
||||
final String title;
|
||||
|
||||
AppRoute(this.page, this.title);
|
||||
|
||||
void go(BuildContext context) {
|
||||
Analysis.recordView(title);
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => page));
|
||||
}
|
||||
}
|
||||
42
lib/core/update.dart
Normal file
42
lib/core/update.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/utils.dart';
|
||||
import 'package:toolbox/data/provider/app.dart';
|
||||
import 'package:toolbox/data/res/build_data.dart';
|
||||
import 'package:toolbox/data/service/app.dart';
|
||||
import 'package:toolbox/locator.dart';
|
||||
|
||||
Future<bool> isFileAvailable(String url) async {
|
||||
try {
|
||||
final resp = await Dio().head(url);
|
||||
return resp.statusCode == 200;
|
||||
} catch (e) {
|
||||
print('update file not available: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> doUpdate(BuildContext context, {bool force = false}) async {
|
||||
final update = await locator<AppService>().getUpdate();
|
||||
|
||||
locator<AppProvider>().setNewestBuild(update.newest);
|
||||
|
||||
if (!force && update.newest <= BuildData.build) {
|
||||
print('Update ignored due to current: ${BuildData.build}, '
|
||||
'update: ${update.newest}');
|
||||
return;
|
||||
}
|
||||
print('Update available: ${update.newest}');
|
||||
|
||||
if (Platform.isAndroid && !await isFileAvailable(update.android)) {
|
||||
return;
|
||||
}
|
||||
|
||||
showSnackBarWithAction(
|
||||
context,
|
||||
'${BuildData.name}有更新啦,Ver:${update.newest}\n${update.changelog}',
|
||||
'更新',
|
||||
() => openUrl(Platform.isAndroid ? update.android : update.ios));
|
||||
}
|
||||
42
lib/core/utils.dart
Normal file
42
lib/core/utils.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
void unawaited(Future<void> future) {}
|
||||
|
||||
bool isDarkMode(BuildContext context) =>
|
||||
Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
void showSnackBar(BuildContext context, Widget child) =>
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: child));
|
||||
|
||||
void showSnackBarWithAction(
|
||||
BuildContext context, String content, String action, Function onTap) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(content),
|
||||
action: SnackBarAction(
|
||||
label: action,
|
||||
onPressed: () => onTap,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Future<bool> openUrl(String url) async {
|
||||
print('openUrl $url');
|
||||
|
||||
if (!await canLaunch(url)) {
|
||||
print('canLaunch false');
|
||||
return false;
|
||||
}
|
||||
|
||||
final ok = await launch(url, forceSafariVC: false);
|
||||
|
||||
if (ok == true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
print('launch $url failed');
|
||||
|
||||
return false;
|
||||
}
|
||||
44
lib/data/model/update.dart
Normal file
44
lib/data/model/update.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
///
|
||||
/// Code generated by jsonToDartModel https://ashamp.github.io/jsonToDartModel/
|
||||
///
|
||||
class AppUpdate {
|
||||
/*
|
||||
{
|
||||
"newest": 33,
|
||||
"android": "https://v2.custed.lolli.tech/res/tiku/apk/33.apk",
|
||||
"ios": "https://",
|
||||
"min": 30,
|
||||
"changelog": ""
|
||||
}
|
||||
*/
|
||||
|
||||
late int newest;
|
||||
late String android;
|
||||
late String ios;
|
||||
late int min;
|
||||
late String changelog;
|
||||
|
||||
AppUpdate({
|
||||
required this.newest,
|
||||
required this.android,
|
||||
required this.ios,
|
||||
required this.min,
|
||||
required this.changelog,
|
||||
});
|
||||
AppUpdate.fromJson(Map<String, dynamic> json) {
|
||||
newest = json["newest"]?.toInt();
|
||||
android = json["android"].toString();
|
||||
ios = json["ios"].toString();
|
||||
min = json["min"].toInt();
|
||||
changelog = json["changelog"].toString();
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data["newest"] = newest;
|
||||
data["android"] = android;
|
||||
data["ios"] = ios;
|
||||
data["min"] = min;
|
||||
data["changelog"] = changelog;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
22
lib/data/provider/app.dart
Normal file
22
lib/data/provider/app.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:toolbox/core/provider_base.dart';
|
||||
import 'package:toolbox/data/service/app.dart';
|
||||
|
||||
class AppProvider extends BusyProvider {
|
||||
Map? _notify;
|
||||
Map? get notify => _notify;
|
||||
int? _newestBuild;
|
||||
int? get newestBuild => _newestBuild;
|
||||
|
||||
Future<void> loadData() async {
|
||||
setBusyState(true);
|
||||
final service = AppService();
|
||||
_notify = await service.getNotify();
|
||||
setBusyState(false);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setNewestBuild(int build) {
|
||||
_newestBuild = build;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
65
lib/data/provider/debug.dart
Normal file
65
lib/data/provider/debug.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DebugProvider extends ChangeNotifier {
|
||||
final widgets = <Widget>[];
|
||||
|
||||
void addText(String text) {
|
||||
_addText(text);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _addText(String text) {
|
||||
_addWidget(Text(text));
|
||||
}
|
||||
|
||||
void addError(Object error) {
|
||||
_addError(error);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _addError(Object error) {
|
||||
_addMultiline(error, Colors.red);
|
||||
}
|
||||
|
||||
void addMultiline(Object data, [Color color = Colors.blue]) {
|
||||
_addMultiline(data, color);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _addMultiline(Object data, [Color color = Colors.blue]) {
|
||||
final widget = Text(
|
||||
'$data',
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
),
|
||||
);
|
||||
_addWidget(SingleChildScrollView(
|
||||
child: widget,
|
||||
scrollDirection: Axis.horizontal,
|
||||
));
|
||||
}
|
||||
|
||||
void addWidget(Widget widget) {
|
||||
_addWidget(widget);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _addWidget(Widget widget) {
|
||||
final outlined = Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
child: widget,
|
||||
);
|
||||
|
||||
widgets.add(outlined);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
widgets.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
10
lib/data/res/build_data.dart
Normal file
10
lib/data/res/build_data.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
// This file is generated by ./make.dart
|
||||
|
||||
class BuildData {
|
||||
static const String name = "ToolBox";
|
||||
static const int build = 40;
|
||||
static const String engine =
|
||||
"Flutter 2.2.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision f4abaa0735 (9 weeks ago) • 2021-07-01 12:46:11 -0700\nEngine • revision 241c87ad80\nTools • Dart 2.13.4\n";
|
||||
static const String buildAt = "2021-09-04 09:34:17.507782";
|
||||
static const int modifications = 0;
|
||||
}
|
||||
16
lib/data/res/color.dart
Normal file
16
lib/data/res/color.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/core/utils.dart';
|
||||
|
||||
class DynamicColor {
|
||||
/// 白天模式显示的颜色
|
||||
Color light;
|
||||
|
||||
/// 暗黑模式显示的颜色
|
||||
Color dark;
|
||||
|
||||
DynamicColor(this.light, this.dark);
|
||||
|
||||
resolve(BuildContext context) => isDarkMode(context) ? dark : light;
|
||||
}
|
||||
|
||||
final mainColor = DynamicColor(Colors.black87, Colors.white70);
|
||||
79
lib/data/res/hikotoko.dart
Normal file
79
lib/data/res/hikotoko.dart
Normal file
@@ -0,0 +1,79 @@
|
||||
const hitokotos = [
|
||||
'与众不同的生活方式很累人呢,因为找不到借口',
|
||||
'面对就好,去经历就好。',
|
||||
'将愿望倾入不愿忘却的回忆中',
|
||||
'努力是不会背叛自己的,虽然梦想有时会背叛自己',
|
||||
'用你的笑容去改变这个世界,别让这个世界改变了你的笑容',
|
||||
'我有在反省,但我不后悔',
|
||||
'世界上没有一个人能代替另一个人。',
|
||||
'我的腿让我停下,可是心却不允许我那么做',
|
||||
'可是就算我们通一千次短信,我们各自的心,大概也只会相互靠近一厘米吧',
|
||||
'勇气,就是即便害怕也会去做',
|
||||
'只要有想见面的人,自己就不再是孤单一人',
|
||||
'我很好奇!',
|
||||
'在走廊上跌倒会流鼻血,在人生中跌倒会流眼泪。',
|
||||
'已经无法回到过去了。也不知道将来会是什么模样。',
|
||||
'相信十年后的八月,我们还会相遇。',
|
||||
'看到的感受到的永远都不会消失永远都不会忘记,与各种各样的相遇一同永存。',
|
||||
'我们走过风走过雨,就是没能走进彼此的内心。',
|
||||
'即使从梦中醒来,还会有回忆留下。',
|
||||
'悲伤教会了我喜悦。',
|
||||
'梦总是有会醒来的时候,不会醒的梦总有一天会变成悲伤。',
|
||||
'真正让我难受的,大概是因为让你看到如此狼狈的自己。',
|
||||
'真正重要的东西,总是没有的人比拥有的人清楚。',
|
||||
'我们所过的每个平凡的日常,也许就是连续发生的奇迹',
|
||||
'要是因为烦恼很痛苦,就选择了轻松的选项,将来一定会后悔',
|
||||
'心,可是很重的',
|
||||
'人生在世何其痛苦,所以咖啡至少该甜一点',
|
||||
'每个人的生命都由秒来计算,因为你永远都不知道下一秒会发生什么。',
|
||||
'为什么要担心?如果努力了,担心不会让结果变得更好。',
|
||||
'爱欲于人,犹如执炬,逆风而行,必有烧手之患',
|
||||
'我们是独立的个体,却不是孤独的存在。',
|
||||
'幸运的人一生都在被童年治愈,不幸的人一生都在治愈童年。',
|
||||
'只要结局有可能变好,我们就不能胆怯,就像只要能看到一点蓝天,就不能对天气绝望。',
|
||||
'无论我们能活多久,我们能够享受的只有无法分割的此刻,此外别无其他。',
|
||||
'认真的思索,真诚的明辨是非,有这种态度,大概可算是善良吧。',
|
||||
'多少事,从来急,天地转,光阴迫,一万年太久,只争朝夕。',
|
||||
'物质决定意识,意识反作用于物质',
|
||||
'因为痛苦太有价值,因为回忆太珍贵,所以我们更要继续往前走。',
|
||||
'愿生活不太拥挤,愿笑容不必刻意。愿孤独不再长久,愿碎镜终有重圆。',
|
||||
'世界之大为何我们相遇,难道是缘分,难道是天意。',
|
||||
'我们生活在阴沟里,但有人依然仰望星空。',
|
||||
'宇宙是蚂蚁的梦。',
|
||||
'时间不在于你拥有多少,而在于你怎样使用。',
|
||||
'要保持希望在每天清晨太阳升起。',
|
||||
'我在最没有能力的年纪,遇见了最想照顾一生的人。',
|
||||
'搞怪的不是红绿灯,而是我数不清的犹豫。',
|
||||
'醉后不知天在水,满船清梦压星河。',
|
||||
'人生得意须尽欢,莫让金樽空对月。',
|
||||
'云想衣裳花想容,春风拂槛露华浓。',
|
||||
'疏影横斜水清浅,暗香浮动月黄昏。',
|
||||
'仰天大笑出门去,我辈岂是蓬蒿人。',
|
||||
'溪云初起日沉阁,山雨欲来风满楼。',
|
||||
'劝君莫惜金缕衣,劝君惜取少年时。',
|
||||
'最是人间留不住,朱颜辞镜花辞树。',
|
||||
'当人们做不到一些事情的时候,他们会对你说你也同样不能。',
|
||||
'爱你所爱,行你所行,听从你心,无问西东。',
|
||||
'你现在的气质里,藏着你走过的路,读过的书和爱过的人。',
|
||||
'我希望在20出头的生命里,做一件到八十岁想起来都还会微笑的事。',
|
||||
'时光会把你雕刻成你应有的样子。',
|
||||
'冬天之所以那么冷是为了告诉大家身边人的温暖有多重要。',
|
||||
'你需要找出面对明天的力量。',
|
||||
'面对无知的嘲笑,我只能为他们默哀。',
|
||||
'既然忘不掉,就把它留在心中,让时间去冲淡它。',
|
||||
'我曾经失落失望失掉所有方向,直到看见平凡才是唯一的答案。',
|
||||
'合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。',
|
||||
'世间所有的相遇,都是久别重逢。',
|
||||
'斜晖脉脉水悠悠,肠断白频洲.',
|
||||
'不要跟过去的自己比,要期待未来的自己,珍爱现在的自己。',
|
||||
'我知道这个世上有人在等我,尽管我不知道我在等谁。当因为这样,我每天都很快乐。'
|
||||
'我从远方赶来,恰巧你们也在。',
|
||||
'Write the code, Change the world.',
|
||||
'生活不止眼前的苟且,还应该有诗和远方的田野。',
|
||||
'善恶终有报,天道好轮回。不信抬头看,苍天饶过谁。',
|
||||
'年轻时最大的财富,不是你的青春,不是你的美貌,也不是你有充沛的精力,而是你有犯错误的机会。',
|
||||
'闾阎扑地,钟鸣鼎食之家;舸舰迷津,青雀黄龙之舳。',
|
||||
'鹤汀凫渚,穷岛屿之萦回;桂殿兰宫,即冈峦之体势。',
|
||||
'螃蟹在剥我的壳,笔记本在写我。漫天的我落在枫叶上雪花上。而你在想我。',
|
||||
'一个人至少拥有一个梦想,有一个理由去坚强。心若没有栖息的地方,到哪里都是在流浪。',
|
||||
];
|
||||
3
lib/data/res/url.dart
Normal file
3
lib/data/res/url.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
const backendUrl = 'https://v2.custed.lolli.tech';
|
||||
const baseUrl = backendUrl + '/res/toolbox';
|
||||
const joinQQGroupUrl = 'https://jq.qq.com/?_wv=1027&k=G0hUmPAq';
|
||||
15
lib/data/service/app.dart
Normal file
15
lib/data/service/app.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:toolbox/data/model/update.dart';
|
||||
import 'package:toolbox/data/res/url.dart';
|
||||
|
||||
class AppService {
|
||||
Future<Map> getNotify() async {
|
||||
final resp = await Dio().get('$baseUrl/notify.json');
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
Future<AppUpdate> getUpdate() async {
|
||||
final resp = await Dio().get('$baseUrl/update.json');
|
||||
return AppUpdate.fromJson(resp.data);
|
||||
}
|
||||
}
|
||||
6
lib/data/store/setting.dart
Normal file
6
lib/data/store/setting.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:toolbox/core/persistant_store.dart';
|
||||
|
||||
class SettingStore extends PersistentStore {
|
||||
StoreProperty<bool> get receiveNotification =>
|
||||
property('notify', defaultValue: true);
|
||||
}
|
||||
28
lib/locator.dart
Normal file
28
lib/locator.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:toolbox/data/provider/app.dart';
|
||||
import 'package:toolbox/data/provider/debug.dart';
|
||||
import 'package:toolbox/data/service/app.dart';
|
||||
import 'package:toolbox/data/store/setting.dart';
|
||||
|
||||
GetIt locator = GetIt.instance;
|
||||
|
||||
void setupLocatorForServices() {
|
||||
locator.registerLazySingleton(() => AppService());
|
||||
}
|
||||
|
||||
void setupLocatorForProviders() {
|
||||
locator.registerSingleton(AppProvider());
|
||||
locator.registerSingleton(DebugProvider());
|
||||
}
|
||||
|
||||
Future<void> setupLocatorForStores() async {
|
||||
final setting = SettingStore();
|
||||
await setting.init(boxName: 'setting');
|
||||
locator.registerSingleton(setting);
|
||||
}
|
||||
|
||||
Future<void> setupLocator() async {
|
||||
await setupLocatorForStores();
|
||||
setupLocatorForProviders();
|
||||
setupLocatorForServices();
|
||||
}
|
||||
6
lib/main.dart
Normal file
6
lib/main.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toolbox/app.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
141
lib/page/home.dart
Normal file
141
lib/page/home.dart
Normal file
@@ -0,0 +1,141 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({Key? key, required this.title}) : super(key: key);
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
late TextEditingController _textEditingController;
|
||||
late TextEditingController _textEditingControllerResult;
|
||||
late MediaQueryData _media;
|
||||
late ThemeData _theme;
|
||||
|
||||
static const List<String> _typeOption = [
|
||||
'base64 decode',
|
||||
'base64 encode',
|
||||
'URL encode',
|
||||
'URL decode'
|
||||
];
|
||||
int _typeOptionIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textEditingController = TextEditingController();
|
||||
_textEditingControllerResult = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_media = MediaQuery.of(context);
|
||||
_theme = Theme.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildInputTop(),
|
||||
_buildTypeOption(),
|
||||
_buildResult(),
|
||||
],
|
||||
)),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
_textEditingControllerResult.text = doConvert();
|
||||
},
|
||||
tooltip: 'convert',
|
||||
child: const Icon(Icons.send),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String doConvert() {
|
||||
final text = _textEditingController.text;
|
||||
switch (_typeOptionIndex) {
|
||||
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 '未知编码';
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildInputTop() {
|
||||
return SizedBox(
|
||||
height: _media.size.height * 0.33,
|
||||
child: _buildInput(_textEditingController),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTypeOption() {
|
||||
return Card(
|
||||
child: ExpansionTile(
|
||||
title: Text(
|
||||
_typeOption[_typeOptionIndex],
|
||||
style: const TextStyle(fontSize: 16.0, fontWeight: FontWeight.w500),
|
||||
),
|
||||
children: _typeOption
|
||||
.map((e) => ListTile(
|
||||
title: Text(
|
||||
e,
|
||||
style: TextStyle(
|
||||
color:
|
||||
_theme.textTheme.bodyText2!.color!.withAlpha(177)),
|
||||
),
|
||||
trailing: _buildRadio(_typeOption.indexOf(e)),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildResult() {
|
||||
return SizedBox(
|
||||
height: _media.size.height * 0.33,
|
||||
child: _buildInput(_textEditingControllerResult),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInput(TextEditingController controller) {
|
||||
return Card(
|
||||
child: TextField(
|
||||
maxLines: 20,
|
||||
decoration: InputDecoration(
|
||||
fillColor: Theme.of(context).cardColor,
|
||||
filled: true,
|
||||
border: InputBorder.none),
|
||||
controller: controller,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Radio _buildRadio(int index) {
|
||||
return Radio<int>(
|
||||
value: index,
|
||||
groupValue: _typeOptionIndex,
|
||||
onChanged: (int? value) {
|
||||
setState(() {
|
||||
_typeOptionIndex = value!;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user