From ee14df5ae080184de8a9f4343688b4c713688e5b Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Sat, 12 Jul 2025 04:55:53 +0800 Subject: [PATCH] MCSI-9 feat: add AboutTab with action buttons and integrate SVG assets --- assets/svg/bug.svg | 5 + assets/svg/github.svg | 5 + assets/svg/send.svg | 5 + assets/svg/youtube.svg | 5 + lib/main/framework/ui/about_tab.dart | 130 ++++++++++++++++++ .../ui/minecraft_server_installer.dart | 4 +- .../framework/ui/side_navigation_bar.dart | 28 ++-- lib/main/main.dart | 4 +- linux/runner/my_application.cc | 2 +- pubspec.lock | 64 +++++++++ pubspec.yaml | 2 + 11 files changed, 232 insertions(+), 22 deletions(-) create mode 100644 assets/svg/bug.svg create mode 100644 assets/svg/github.svg create mode 100644 assets/svg/send.svg create mode 100644 assets/svg/youtube.svg create mode 100644 lib/main/framework/ui/about_tab.dart diff --git a/assets/svg/bug.svg b/assets/svg/bug.svg new file mode 100644 index 0000000..cc7a7a7 --- /dev/null +++ b/assets/svg/bug.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/assets/svg/github.svg b/assets/svg/github.svg new file mode 100644 index 0000000..d4b3e03 --- /dev/null +++ b/assets/svg/github.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/assets/svg/send.svg b/assets/svg/send.svg new file mode 100644 index 0000000..b62c8a6 --- /dev/null +++ b/assets/svg/send.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/assets/svg/youtube.svg b/assets/svg/youtube.svg new file mode 100644 index 0000000..6539cc5 --- /dev/null +++ b/assets/svg/youtube.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/lib/main/framework/ui/about_tab.dart b/lib/main/framework/ui/about_tab.dart new file mode 100644 index 0000000..4b6eb9a --- /dev/null +++ b/lib/main/framework/ui/about_tab.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:gap/gap.dart'; +import 'package:minecraft_server_installer/main/constants.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class AboutTab extends StatelessWidget { + const AboutTab({super.key}); + + @override + Widget build(BuildContext context) => SizedBox( + width: 460, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset('assets/img/mcsi_logo.png', width: 100, height: 100), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + Constants.appName, + style: Theme.of(context) + .textTheme + .headlineMedium + ?.copyWith(fontWeight: FontWeight.w900, color: Colors.blueGrey.shade900), + ), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) => Text( + 'Version ${snapshot.data?.version ?? ''}', + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey.shade700), + ), + ), + ], + ), + ], + ), + const Gap(32), + Container( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), + decoration: BoxDecoration( + color: Colors.blueGrey.shade50, + borderRadius: BorderRadius.circular(8), + border: Border(left: BorderSide(color: Colors.blueGrey.shade300, width: 6)), + ), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Icon(Icons.format_quote_rounded, color: Colors.grey.shade700), + ), + const Gap(8), + Text( + '讓 Minecraft 伺服器安裝變得更簡單!', + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + color: Colors.grey.shade700, + ), + ), + ], + ), + ), + const Gap(32), + Row( + children: [ + _actionButton(text: '教學影片', svgAssetName: 'assets/svg/youtube.svg'), + const Gap(12), + _actionButton(text: '問題回報', svgAssetName: 'assets/svg/bug.svg'), + const Gap(12), + _actionButton(text: '聯絡作者', svgAssetName: 'assets/svg/send.svg'), + const Gap(12), + _actionButton(text: '原始碼', svgAssetName: 'assets/svg/github.svg'), + ], + ), + const Spacer(), + Text( + 'Copyright © 2025 SquidSpirit', + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey.shade700), + ), + ], + ), + ); + + Widget _actionButton({ + required String text, + required String svgAssetName, + }) => + Builder( + builder: (context) => Expanded( + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(8), + child: Ink( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.blueGrey.shade50, width: 2), + ), + child: InkWell( + onTap: () {}, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16), + child: Column( + children: [ + SvgPicture.asset( + svgAssetName, + width: 32, + height: 32, + colorFilter: ColorFilter.mode(Colors.grey.shade800, BlendMode.srcIn), + ), + const Gap(12), + Text( + text, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: Colors.grey.shade700, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ); +} diff --git a/lib/main/framework/ui/minecraft_server_installer.dart b/lib/main/framework/ui/minecraft_server_installer.dart index 944e569..901b652 100644 --- a/lib/main/framework/ui/minecraft_server_installer.dart +++ b/lib/main/framework/ui/minecraft_server_installer.dart @@ -9,6 +9,7 @@ import 'package:minecraft_server_installer/main/application/use_case/write_file_ import 'package:minecraft_server_installer/main/constants.dart'; import 'package:minecraft_server_installer/main/framework/api/installation_api_service_impl.dart'; import 'package:minecraft_server_installer/main/framework/storage/installation_file_storage_impl.dart'; +import 'package:minecraft_server_installer/main/framework/ui/about_tab.dart'; import 'package:minecraft_server_installer/main/framework/ui/basic_configuration_tab.dart'; import 'package:minecraft_server_installer/main/framework/ui/side_navigation_bar.dart'; import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_repository_impl.dart'; @@ -100,8 +101,9 @@ class MinecraftServerInstaller extends StatelessWidget { return const BasicConfigurationTab(); case NavigationItem.modConfiguration: case NavigationItem.serverProperties: - case NavigationItem.about: return const Placeholder(); + case NavigationItem.about: + return const AboutTab(); } } } diff --git a/lib/main/framework/ui/side_navigation_bar.dart b/lib/main/framework/ui/side_navigation_bar.dart index 1dc44a2..fda99dc 100644 --- a/lib/main/framework/ui/side_navigation_bar.dart +++ b/lib/main/framework/ui/side_navigation_bar.dart @@ -15,17 +15,9 @@ class SideNavigationBar extends StatefulWidget { class _SideNavigationBarState extends State { bool _isExpanded = false; - PackageInfo? _packageInfo; double get width => _isExpanded ? 340 : 80; - @override - void initState() { - super.initState(); - PackageInfo.fromPlatform().then((packageInfo) => - WidgetsBinding.instance.addPostFrameCallback((_) => setState(() => _packageInfo = packageInfo))); - } - @override Widget build(BuildContext context) => AnimatedContainer( duration: const Duration(milliseconds: 300), @@ -50,10 +42,7 @@ class _SideNavigationBarState extends State { text: Constants.appName, leading: Padding( padding: const EdgeInsets.only(right: 4), - child: SizedBox.square( - dimension: 32, - child: Image.asset('assets/img/mcsi_logo.png', width: 2048, height: 2048), - ), + child: Image.asset('assets/img/mcsi_logo.png', width: 32, height: 32), ), padding: const EdgeInsets.only(left: 4), expandedKey: const ValueKey('expandedTitle'), @@ -90,12 +79,15 @@ class _SideNavigationBarState extends State { const Gap(8), _navigationButton(NavigationItem.about), const Spacer(), - _animatedText( - text: 'Version ${_packageInfo?.version ?? ''}', - padding: EdgeInsets.zero, - expandedKey: const ValueKey('expandedVersion'), - collapsedKey: const ValueKey('collapsedVersion'), - alignment: Alignment.bottomCenter, + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) => _animatedText( + text: 'Version ${snapshot.data?.version ?? ''}', + padding: EdgeInsets.zero, + expandedKey: const ValueKey('expandedVersion'), + collapsedKey: const ValueKey('collapsedVersion'), + alignment: Alignment.bottomCenter, + ), ), ], ), diff --git a/lib/main/main.dart b/lib/main/main.dart index 289987e..680850c 100644 --- a/lib/main/main.dart +++ b/lib/main/main.dart @@ -7,8 +7,8 @@ Future main() async { await windowManager.ensureInitialized(); final windowOptions = const WindowOptions( - size: Size(800, 600), - minimumSize: Size(800, 600), + size: Size(900, 600), + minimumSize: Size(900, 600), center: true, backgroundColor: Colors.transparent, skipTaskbar: false, diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc index 32ffeca..ee0e496 100644 --- a/linux/runner/my_application.cc +++ b/linux/runner/my_application.cc @@ -22,7 +22,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_decorated(window, FALSE); - gtk_window_set_default_size(window, 800, 600); + gtk_window_set_default_size(window, 900, 600); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); diff --git a/pubspec.lock b/pubspec.lock index 0ea9c0e..6715466 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -126,6 +134,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.28" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845 + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -256,6 +272,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.0" plugin_platform_interface: dependency: transitive description: @@ -437,6 +469,30 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.4" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331" + url: "https://pub.dev" + source: hosted + version: "1.1.17" vector_math: dependency: transitive description: @@ -477,6 +533,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" sdks: dart: ">=3.7.0 <4.0.0" flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index 396905c..b200d46 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: equatable: ^2.0.7 file_picker: ^10.2.0 flutter_bloc: ^9.1.1 + flutter_svg: ^2.2.0 gap: ^3.0.1 http: ^1.4.0 package_info_plus: ^8.3.0 @@ -71,6 +72,7 @@ flutter: # - images/a_dot_ham.jpeg assets: - assets/img/ + - assets/svg/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images