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