diff --git a/README.md b/README.md index d7d0330..4ea5100 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ -- 占位. +- 待做事项: + 1. UUID工具 + 2. markdown编写/解析工具 + 3. quill编辑器 + 4. 二维码生成/解析工具(qr_flutter),如果可以的话,调用摄像头扫描二维码 + 5. 条形码生成/解析工具 \ No newline at end of file diff --git a/lib/base_page.dart b/lib/base_page.dart index 8696a03..7d15d4e 100644 --- a/lib/base_page.dart +++ b/lib/base_page.dart @@ -4,134 +4,146 @@ import 'package:flutter/material.dart'; import 'package:refreshed/refreshed.dart'; import 'package:simple_icons/simple_icons.dart'; import 'package:tools/func_enum.dart'; +import 'package:tools/global_variable.dart'; import 'package:web/web.dart' as web; +// class Man + class BasePage extends StatelessWidget { final Future Function() child; final String title; - final RxString searchRx = RxString(""); - late final Rxn future = Rxn(); + final Rxn body = Rxn(); BasePage({super.key, required this.title, required this.child}); // bool get isDark => Theme.of(Get.context!).brightness == Brightness.dark; @override Widget build(BuildContext context) { - if (future.value == null) { - child().then((value) => future.value = value); - } + if (body.value == null) child().then(body.call); + bool isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - Get.changeThemeMode(isDark ? ThemeMode.light : ThemeMode.dark); - }, - child: Icon(isDark ? Icons.light_mode : Icons.dark_mode), - ), - drawer: Drawer( - child: Column( - children: [ - Expanded( - child: ListView( - /// 内容居中 - physics: const BouncingScrollPhysics(), - padding: EdgeInsets.zero, - children: [ - DrawerHeader( - decoration: const BoxDecoration( - color: Color.fromARGB(255, 44, 78, 105), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - const Text( - '功能列表', - style: TextStyle(color: Colors.white, fontSize: 24), + drawer: Drawer( + child: Column( + children: [ + DrawerHeader( + decoration: const BoxDecoration( + color: Color.fromARGB(255, 44, 78, 105), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const Text( + '功能列表', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + + /// 搜索框 + ClipRRect( + borderRadius: BorderRadius.circular(20), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + border: + Border.all(color: Colors.white.withOpacity(0.2)), + ), + child: TextField( + onChanged: GlobalVariable.searchRx.call, + controller: TextEditingController( + text: GlobalVariable.searchRx.value), + decoration: const InputDecoration( + hintText: "搜索工具", + prefixIcon: Icon(Icons.search), + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, vertical: 14), ), + ), + ), + ), + ) + ], + ), + ), + Expanded( + child: ListView( + /// 内容居中 + physics: const BouncingScrollPhysics(), + padding: EdgeInsets.zero, + children: [ + ValueListenableBuilder( + valueListenable: GlobalVariable.searchRx, + builder: (context, value, child) { + List list = FunctionEnum.values + .where((element) => + (GlobalVariable.searchRx.value.isNotEmpty + ? element.name + .contains(GlobalVariable.searchRx.value) + : true)) + .toList(); + if (list.isEmpty) { + return const ListTile( + title: Text("没有找到相关工具"), + ); + } - /// 搜索框 - ClipRRect( - borderRadius: BorderRadius.circular(20), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), - child: Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: Colors.white.withOpacity(0.2)), - ), - child: TextField( - onChanged: searchRx.call, - controller: TextEditingController( - text: searchRx.value), - decoration: const InputDecoration( - hintText: "搜索工具", - prefixIcon: Icon(Icons.search), - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 16, vertical: 14), - ), - ), - ), - ), - ) + return Column( + children: (list) + .map((e) => ListTile( + title: Text(e.name), + tileColor: e.route == Get.currentRoute + ? Colors.blue.withOpacity(0.2) + : null, + onTap: e.onTap)) + .toList(), + ); + }), + ], + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + TextButton( + onPressed: () { + web.window.open("https://github.com/bymoye/tools"); + }, + child: const Row( + children: [ + Icon(SimpleIcons.github, size: 20), + SizedBox(width: 8), + Text("bymoye/tools") ], ), ), - ValueListenableBuilder( - valueListenable: searchRx, - builder: (context, value, child) { - final List list = FunctionEnum.values - .where((element) => (searchRx.value.isNotEmpty - ? element.name.contains(searchRx.value) - : true)) - .toList(); - if (list.isEmpty) { - return const ListTile( - title: Text("没有找到相关工具"), - ); - } - - return Column( - children: list - .map((e) => ListTile( - title: Text(e.name), - tileColor: e.route == Get.currentRoute - ? Colors.blue.withOpacity(0.2) - : null, - onTap: e.onTap)) - .toList(), - ); - }), + const Spacer(), + IconButton( + onPressed: () { + Get.changeThemeMode( + isDark ? ThemeMode.light : ThemeMode.dark); + }, + icon: Icon(isDark ? Icons.light_mode : Icons.dark_mode)) ], - ), - ), - const Divider(), - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - TextButton( - onPressed: () { - web.window.open("https://github.com/bymoye/tools"); - }, - child: const Row(children: [ - Icon(SimpleIcons.github, size: 20), - SizedBox(width: 8), - Text("bymoye/tools") - ])) - ], - )) - ], - ), - ), - appBar: AppBar( - title: Text(title), + )) + ], ), - body: Obx(() => future.value == null - ? const Center(child: CircularProgressIndicator()) - : future.value!)); + ), + appBar: AppBar( + title: Text(title), + ), + body: Obx( + () => + body.value ?? + const Center( + child: CircularProgressIndicator(), + ), + ), + ); } } diff --git a/lib/global_variable.dart b/lib/global_variable.dart new file mode 100644 index 0000000..8823846 --- /dev/null +++ b/lib/global_variable.dart @@ -0,0 +1,5 @@ +import 'package:refreshed/refreshed.dart'; + +class GlobalVariable { + static RxString searchRx = RxString(""); +} diff --git a/lib/uuid_tools/controller.dart b/lib/uuid_tools/controller.dart index 71092e4..61468f3 100644 --- a/lib/uuid_tools/controller.dart +++ b/lib/uuid_tools/controller.dart @@ -1,25 +1,33 @@ -import 'package:flutter/material.dart'; -import 'package:refreshed/refreshed.dart'; -import 'package:tools/utils/uuid/uuid_analysis.dart'; +// import 'package:flutter/material.dart'; +// import 'package:refreshed/refreshed.dart'; +// import 'package:tools/utils/uuid/uuid_analysis.dart'; -class UuidToolsController extends GetxController { - final TextEditingController textEditingController = TextEditingController(); +// class UuidToolsController extends GetxController { +// final TextEditingController textEditingController = TextEditingController(); - final RxString helper = "".obs; +// final RxString helper = RxString(""); - final Rx borderColor = Rx(Colors.grey); +// final Rx borderColor = Rx(Colors.grey); - void onEditingComplete() { - try { - final UuidAnalysis analysis = UuidAnalysis(textEditingController.text); - } catch (e) { - if (e is FormatException) { - borderColor.value = Colors.red; - // textEditingController. - helper.value = "UUID格式错误"; - update(); - } - print(e); - } - } -} +// final Rxn analysis = Rxn(); + +// void onEditingComplete() { +// if (textEditingController.text.isEmpty) { +// borderColor.value = Colors.red; +// helper.value = "UUID不能为空"; +// return; +// } +// analysis.value = null; +// try { +// analysis.value = UuidAnalysis(textEditingController.text); +// } catch (e) { +// if (e is FormatException) { +// borderColor.value = Colors.red; +// // textEditingController. +// helper.value = "UUID格式错误"; +// update(); +// } +// print(e); +// } +// } +// } diff --git a/lib/uuid_tools/index.dart b/lib/uuid_tools/index.dart index d43d5f9..464444e 100644 --- a/lib/uuid_tools/index.dart +++ b/lib/uuid_tools/index.dart @@ -1,55 +1,73 @@ import 'package:flutter/material.dart'; import 'package:refreshed/refreshed.dart'; -import 'package:tools/uuid_tools/controller.dart'; +import 'package:tools/utils/uuid/uuid_analysis.dart'; +// import 'package:tools/uuid_tools/controller.dart'; -class UuidToolsPage extends StatelessWidget { +class UuidToolsPage extends StatefulWidget { const UuidToolsPage({super.key}); + @override + State createState() => _UUidToolsState(); +} + +class _UUidToolsState extends State { + final TextEditingController textEditingController = TextEditingController(); + + UuidAnalysis? analysis; + + String? helper; + + Color borderColor = Colors.grey; + + // double get width { + // final double width = Get.width * 0.5; + // return width < 800 ? 800 : width; + // } + + void onEditingComplete() { + if (textEditingController.text.isEmpty) { + borderColor = Colors.red; + helper = "UUID不能为空"; + return; + } + analysis = null; + try { + analysis = UuidAnalysis(textEditingController.text); + } catch (e) { + if (e is FormatException) { + borderColor = Colors.red; + // textEditingController. + helper = "UUID格式错误"; + setState(() {}); + } + print(e); + } + } + @override Widget build(BuildContext context) { - return GetBuilder( - init: UuidToolsController(), - builder: (UuidToolsController controller) { - return Center( - child: TextField( - controller: controller.textEditingController, - // onChanged: controller.onChanged, - onEditingComplete: controller.onEditingComplete, - - /// 输入框样式 - decoration: InputDecoration( - /// 输入框内部填充 - // label: const Text("UUID"), - labelText: "请在此处输入需要解析的UUID", - // labelStyle: const TextStyle(color: Colors.grey), - helper: ValueListenableBuilder( - valueListenable: controller.helper, - builder: (BuildContext context, String value, Widget? child) { - return Text( - controller.helper.value, - style: const TextStyle(color: Colors.grey), - ); - }, - ), - contentPadding: const EdgeInsets.all(10), - - /// 输入框边框 - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: - BorderSide(color: controller.borderColor.value)), - - /// 输入框填充颜色 - filled: true, - - /// 输入框提示文字 - hintText: "请在此处输入需要解析的UUID", - - /// 输入框提示文字样式 - hintStyle: const TextStyle(color: Colors.grey), - ), + return Center( + child: SizedBox( + width: Get.mediaQuery.size.width * 0.5, + child: TextField( + controller: textEditingController, + onEditingComplete: onEditingComplete, + decoration: InputDecoration( + labelText: "请在此处输入需要解析的UUID", + helper: Text( + helper ?? "", + style: const TextStyle(color: Colors.grey), ), - ); - }); + contentPadding: const EdgeInsets.all(10), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: borderColor)), + filled: true, + hintText: "请在此处输入需要解析的UUID", + hintStyle: const TextStyle(color: Colors.grey), + ), + ), + ), + ); } } diff --git a/lib/world_time/index.dart b/lib/world_time/index.dart index bf32784..6f5b1ea 100644 --- a/lib/world_time/index.dart +++ b/lib/world_time/index.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:tools/agent/api.dart'; +import 'package:web/web.dart' as web; class WorldTime extends StatefulWidget { const WorldTime({super.key}); @@ -11,8 +14,12 @@ class WorldTime extends StatefulWidget { class _WorldTimeState extends State with TickerProviderStateMixin { Ticker? _ticker; + Timer? _visibilityChangeTimer; + StreamSubscription? _visibilityChangeSubscription; + Duration _duration = const Duration(seconds: 1); - late final DateTime _currentTime = DateTime.now(); + int _visibilityChangeInt = 0; + final DateTime _currentTime = DateTime.now(); DateTime? remoteTime; double? delay; bool lock = false; @@ -33,7 +40,33 @@ class _WorldTimeState extends State with TickerProviderStateMixin { @override void initState() { super.initState(); + web.document.title = "世界时间"; fetchWorldTime(); + + _visibilityChangeSubscription = + web.document.onVisibilityChange.listen((event) { + if (web.document.hidden) { + _visibilityChangeTimer = + Timer.periodic(const Duration(seconds: 1), (timer) { + _visibilityChangeInt = timer.tick; + web.document.title = "⏰ ${timer.tick} 秒"; + if (timer.tick > 30) { + timer.cancel(); + _ticker?.stop(); + remoteTime = null; + web.document.title = "💤 已休眠"; + } + }); + } else { + // _visibilityChangeTicker?.dispose(); + _visibilityChangeTimer?.cancel(); + if (_visibilityChangeInt > 30) { + fetchWorldTime(); + } + _visibilityChangeInt = 0; + web.document.title = "世界时间"; + } + }); } void fetchWorldTime() async { @@ -49,7 +82,6 @@ class _WorldTimeState extends State with TickerProviderStateMixin { setState(() { _duration = duration; }); - // _duration = duration; if (duration.inSeconds != 0 && duration.inSeconds % 60 == 0) { fetchWorldTime(); } @@ -58,6 +90,43 @@ class _WorldTimeState extends State with TickerProviderStateMixin { lock = false; } + Widget _buildTimeSection(BuildContext context, + {required bool isDarkMode, + required String title, + required String content}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: Column( + children: [ + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.lightBlue[100] : Colors.blue[700], + ), + ), + const SizedBox(height: 8), + Text( + content, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + ), + ), + const SizedBox(height: 8), + Divider( + color: isDarkMode + ? Colors.grey[700] + : Theme.of(context).primaryColor), + ], + ), + ); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -123,43 +192,11 @@ class _WorldTimeState extends State with TickerProviderStateMixin { ); } - Widget _buildTimeSection(BuildContext context, - {required bool isDarkMode, - required String title, - required String content}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12.0), - child: Column( - children: [ - Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: isDarkMode ? Colors.lightBlue[100] : Colors.blue[700], - ), - ), - const SizedBox(height: 8), - Text( - content, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, - color: isDarkMode ? Colors.white : Colors.black87, - ), - ), - const SizedBox(height: 8), - Divider(color: isDarkMode ? Colors.grey[700] : Colors.grey[300]), - ], - ), - ); - } - @override void dispose() { _ticker?.dispose(); + _visibilityChangeTimer?.cancel(); + _visibilityChangeSubscription?.cancel(); super.dispose(); } } diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 9322cd7..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,28 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - // await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -}