MCSI-7 feat: add side navigation bar and logo, update window size and strings
This commit is contained in:
parent
8e4a039ee6
commit
5117e45e95
BIN
assets/img/mcsi_logo.png
Normal file
BIN
assets/img/mcsi_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 MiB |
@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
abstract class Constants {
|
||||
static const appName = 'Minecraft Server Installer';
|
||||
static const gameVersionListUrl = 'https://www.dropbox.com/s/mtz3moc9dpjtz7s/GameVersions.txt?dl=1';
|
||||
static const serverFileName = 'server.jar';
|
||||
static const eulaFileName = 'eula.txt';
|
||||
|
@ -5,9 +5,11 @@ import 'package:minecraft_server_installer/main/adapter/presentation/installatio
|
||||
import 'package:minecraft_server_installer/main/application/use_case/download_file_use_case.dart';
|
||||
import 'package:minecraft_server_installer/main/application/use_case/grant_file_permission_use_case.dart';
|
||||
import 'package:minecraft_server_installer/main/application/use_case/write_file_use_case.dart';
|
||||
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/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';
|
||||
import 'package:minecraft_server_installer/vanilla/adapter/presentation/vanilla_bloc.dart';
|
||||
import 'package:minecraft_server_installer/vanilla/application/use_case/get_game_version_list_use_case.dart';
|
||||
@ -16,8 +18,7 @@ import 'package:minecraft_server_installer/vanilla/framework/api/vanilla_api_ser
|
||||
class MinecraftServerInstaller extends StatelessWidget {
|
||||
const MinecraftServerInstaller({super.key});
|
||||
|
||||
Widget get _body =>
|
||||
const Padding(padding: EdgeInsets.symmetric(horizontal: 24, vertical: 32), child: BasicConfigurationTab());
|
||||
Widget get _body => const Padding(padding: EdgeInsets.all(32), child: BasicConfigurationTab());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -34,7 +35,7 @@ class MinecraftServerInstaller extends StatelessWidget {
|
||||
final getGameVersionListUseCase = GetGameVersionListUseCase(gameVersionRepository);
|
||||
|
||||
return MaterialApp(
|
||||
title: 'Minecraft Server Installer',
|
||||
title: Constants.appName,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
brightness: Brightness.light,
|
||||
@ -55,14 +56,21 @@ class MinecraftServerInstaller extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
child: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
if (context.watch<InstallationBloc>().state.isLocked) {
|
||||
return MouseRegion(cursor: SystemMouseCursors.forbidden, child: AbsorbPointer(child: _body));
|
||||
}
|
||||
body: Row(
|
||||
children: [
|
||||
const SideNavigationBar(),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (context.watch<InstallationBloc>().state.isLocked) {
|
||||
return MouseRegion(cursor: SystemMouseCursors.forbidden, child: AbsorbPointer(child: _body));
|
||||
}
|
||||
|
||||
return _body;
|
||||
},
|
||||
return _body;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
174
lib/main/framework/ui/side_navigation_bar.dart
Normal file
174
lib/main/framework/ui/side_navigation_bar.dart
Normal file
@ -0,0 +1,174 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:minecraft_server_installer/main/constants.dart';
|
||||
import 'package:minecraft_server_installer/main/framework/ui/strings.dart';
|
||||
|
||||
class SideNavigationBar extends StatefulWidget {
|
||||
const SideNavigationBar({super.key});
|
||||
|
||||
@override
|
||||
State<SideNavigationBar> createState() => _SideNavigationBarState();
|
||||
}
|
||||
|
||||
class _SideNavigationBarState extends State<SideNavigationBar> {
|
||||
bool _isExpanded = false;
|
||||
NavigationItem _selectedNavigationItem = NavigationItem.basicConfiguration;
|
||||
|
||||
double get width => _isExpanded ? 360 : 80;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
width: width,
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
boxShadow: [
|
||||
const BoxShadow(color: Colors.black26, offset: Offset(0, 0), blurRadius: 8),
|
||||
]),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
_animatedText(
|
||||
text: Constants.appName,
|
||||
leading: SizedBox.square(
|
||||
dimension: 36,
|
||||
child: Image.asset('assets/img/mcsi_logo.png', width: 2048, height: 2048),
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 4),
|
||||
expandedKey: const ValueKey('expandedTitle'),
|
||||
collapsedKey: const ValueKey('collapsedTitle'),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w900, color: Colors.blueGrey.shade900),
|
||||
),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: InkWell(
|
||||
key: ValueKey(_isExpanded),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: _toggleIsExpanded,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Icon(_isExpanded ? Icons.menu_open : Icons.menu, color: Colors.blueGrey.shade600),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(32),
|
||||
_navigationButton(NavigationItem.basicConfiguration),
|
||||
const Gap(8),
|
||||
_navigationButton(NavigationItem.modConfiguration),
|
||||
const Gap(8),
|
||||
_navigationButton(NavigationItem.serverProperties),
|
||||
const Gap(8),
|
||||
_navigationButton(NavigationItem.about),
|
||||
const Spacer(),
|
||||
_animatedText(
|
||||
text: 'Version 1.0.0',
|
||||
expandedKey: const ValueKey('expandedVersion'),
|
||||
collapsedKey: const ValueKey('collapsedVersion'),
|
||||
alignment: Alignment.bottomCenter,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
void _toggleIsExpanded() => setState(() => _isExpanded = !_isExpanded);
|
||||
|
||||
Widget _animatedText({
|
||||
required String text,
|
||||
required Key expandedKey,
|
||||
required Key collapsedKey,
|
||||
EdgeInsetsGeometry padding = const EdgeInsets.only(left: 8),
|
||||
TextStyle? style,
|
||||
AlignmentGeometry alignment = Alignment.centerLeft,
|
||||
Widget? leading,
|
||||
}) =>
|
||||
Expanded(
|
||||
child: ClipRect(
|
||||
child: Container(
|
||||
alignment: alignment,
|
||||
padding: padding,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: _isExpanded
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (leading != null)
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: leading,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
key: expandedKey,
|
||||
style: style,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.visible,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: SizedBox.shrink(key: collapsedKey),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _navigationButton(NavigationItem navigationItem) {
|
||||
final isSelected = _selectedNavigationItem == navigationItem;
|
||||
final color = isSelected ? Colors.blue.shade900 : Colors.blueGrey.shade600;
|
||||
return Material(
|
||||
color: isSelected ? Colors.blueGrey.shade100 : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => setState(() => _selectedNavigationItem = navigationItem),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Icon(navigationItem.iconData, color: color),
|
||||
),
|
||||
_animatedText(
|
||||
text: navigationItem.title,
|
||||
expandedKey: ValueKey('expanded${navigationItem.toString()}'),
|
||||
collapsedKey: ValueKey('collapsed${navigationItem.toString()}'),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500, color: color),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum NavigationItem {
|
||||
basicConfiguration(iconData: Icons.dashboard_rounded, title: Strings.tabBasicConfiguration),
|
||||
modConfiguration(iconData: Icons.extension, title: Strings.tabModConfiguration),
|
||||
serverProperties(iconData: Icons.settings, title: Strings.tabServerPropertiesConfiguration),
|
||||
about(iconData: Icons.info, title: Strings.tabAbout);
|
||||
|
||||
final IconData iconData;
|
||||
final String title;
|
||||
|
||||
const NavigationItem({required this.iconData, required this.title});
|
||||
}
|
@ -8,6 +8,11 @@ abstract class Strings {
|
||||
static const fieldMaxRamSize = '最大 RAM 大小';
|
||||
static const buttonStartToInstall = '開始安裝';
|
||||
static const buttonBrowse = '瀏覽';
|
||||
static const tabBasicConfiguration = '基本設定';
|
||||
static const tabModConfiguration = '模組設定';
|
||||
static const tabServerPropertiesConfiguration = '伺服器選項';
|
||||
static const tabAbout = '關於與說明';
|
||||
static const tabInstallationProgress = '安裝進度';
|
||||
static const tooltipEulaInfo = '點擊查看 EULA 條款';
|
||||
static const dialogTitleSelectDirectory = '選擇安裝目錄';
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ Future<void> main() async {
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
final windowOptions = const WindowOptions(
|
||||
size: Size(400, 600),
|
||||
minimumSize: Size(400, 600),
|
||||
size: Size(800, 600),
|
||||
minimumSize: Size(800, 600),
|
||||
center: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
skipTaskbar: false,
|
||||
|
@ -22,7 +22,7 @@ static void my_application_activate(GApplication* application) {
|
||||
|
||||
gtk_window_set_decorated(window, FALSE);
|
||||
|
||||
gtk_window_set_default_size(window, 400, 600);
|
||||
gtk_window_set_default_size(window, 800, 600);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
|
@ -68,6 +68,8 @@ flutter:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
assets:
|
||||
- assets/img/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
Loading…
x
Reference in New Issue
Block a user