MCSI-7 Tab and navigation #26
22
lib/main/adapter/presentation/navigation_bloc.dart
Normal file
22
lib/main/adapter/presentation/navigation_bloc.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NavigationBloc extends Bloc<NavigationEvent, NavigationItem> {
|
||||
NavigationBloc() : super(NavigationItem.basicConfiguration) {
|
||||
on<NavigationChangedEvent>((event, emit) => emit(event.item));
|
||||
}
|
||||
}
|
||||
|
||||
sealed class NavigationEvent {}
|
||||
|
||||
class NavigationChangedEvent extends NavigationEvent {
|
||||
final NavigationItem item;
|
||||
|
||||
NavigationChangedEvent(this.item);
|
||||
}
|
||||
|
||||
enum NavigationItem {
|
||||
basicConfiguration,
|
||||
modConfiguration,
|
||||
serverProperties,
|
||||
about,
|
||||
}
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:minecraft_server_installer/main/adapter/gateway/installation_repository_impl.dart';
|
||||
import 'package:minecraft_server_installer/main/adapter/presentation/installation_bloc.dart';
|
||||
import 'package:minecraft_server_installer/main/adapter/presentation/navigation_bloc.dart';
|
||||
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';
|
||||
@ -18,8 +19,6 @@ 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.all(32), child: BasicConfigurationTab());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final installationApiService = InstallationApiServiceImpl();
|
||||
@ -44,6 +43,7 @@ class MinecraftServerInstaller extends StatelessWidget {
|
||||
),
|
||||
home: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => NavigationBloc()),
|
||||
BlocProvider(
|
||||
create: (_) => InstallationBloc(
|
||||
downloadFileUseCase,
|
||||
@ -63,7 +63,10 @@ class MinecraftServerInstaller extends StatelessWidget {
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (context.watch<InstallationBloc>().state.isLocked) {
|
||||
return MouseRegion(cursor: SystemMouseCursors.forbidden, child: AbsorbPointer(child: _body));
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.forbidden,
|
||||
child: AbsorbPointer(child: _body),
|
||||
);
|
||||
}
|
||||
|
||||
return _body;
|
||||
@ -76,4 +79,29 @@ class MinecraftServerInstaller extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _body => BlocConsumer<NavigationBloc, NavigationItem>(
|
||||
listener: (_, __) {},
|
||||
builder: (_, state) => Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: SizedBox(
|
||||
key: ValueKey('tab${state.toString()}'),
|
||||
child: _tabContent(state),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _tabContent(NavigationItem navigationItem) {
|
||||
switch (navigationItem) {
|
||||
case NavigationItem.basicConfiguration:
|
||||
return const BasicConfigurationTab();
|
||||
case NavigationItem.modConfiguration:
|
||||
case NavigationItem.serverProperties:
|
||||
case NavigationItem.about:
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:minecraft_server_installer/main/adapter/presentation/navigation_bloc.dart';
|
||||
import 'package:minecraft_server_installer/main/constants.dart';
|
||||
import 'package:minecraft_server_installer/main/framework/ui/strings.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class SideNavigationBar extends StatefulWidget {
|
||||
const SideNavigationBar({super.key});
|
||||
@ -12,10 +15,17 @@ class SideNavigationBar extends StatefulWidget {
|
||||
|
||||
class _SideNavigationBarState extends State<SideNavigationBar> {
|
||||
bool _isExpanded = false;
|
||||
NavigationItem _selectedNavigationItem = NavigationItem.basicConfiguration;
|
||||
PackageInfo? _packageInfo;
|
||||
|
||||
double get width => _isExpanded ? 360 : 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),
|
||||
@ -78,7 +88,8 @@ class _SideNavigationBarState extends State<SideNavigationBar> {
|
||||
_navigationButton(NavigationItem.about),
|
||||
const Spacer(),
|
||||
_animatedText(
|
||||
text: 'Version 1.0.0',
|
||||
text: 'Version ${_packageInfo?.version ?? ''}',
|
||||
padding: EdgeInsets.zero,
|
||||
expandedKey: const ValueKey('expandedVersion'),
|
||||
collapsedKey: const ValueKey('collapsedVersion'),
|
||||
alignment: Alignment.bottomCenter,
|
||||
@ -116,7 +127,7 @@ class _SideNavigationBarState extends State<SideNavigationBar> {
|
||||
child: leading,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
Flexible(
|
||||
child: Text(
|
||||
text,
|
||||
key: expandedKey,
|
||||
@ -134,14 +145,15 @@ class _SideNavigationBarState extends State<SideNavigationBar> {
|
||||
);
|
||||
|
||||
Widget _navigationButton(NavigationItem navigationItem) {
|
||||
final isSelected = _selectedNavigationItem == navigationItem;
|
||||
final selectedNavigationItem = context.watch<NavigationBloc>().state;
|
||||
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),
|
||||
onTap: () => context.read<NavigationBloc>().add(NavigationChangedEvent(navigationItem)),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
@ -161,14 +173,30 @@ class _SideNavigationBarState extends State<SideNavigationBar> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
extension _NavigationItemContent on NavigationItem {
|
||||
String get title {
|
||||
switch (this) {
|
||||
case NavigationItem.basicConfiguration:
|
||||
return Strings.tabBasicConfiguration;
|
||||
case NavigationItem.modConfiguration:
|
||||
return Strings.tabModConfiguration;
|
||||
case NavigationItem.serverProperties:
|
||||
return Strings.tabServerProperties;
|
||||
case NavigationItem.about:
|
||||
return Strings.tabAbout;
|
||||
}
|
||||
}
|
||||
|
||||
final IconData iconData;
|
||||
final String title;
|
||||
|
||||
const NavigationItem({required this.iconData, required this.title});
|
||||
IconData get iconData {
|
||||
switch (this) {
|
||||
case NavigationItem.basicConfiguration:
|
||||
return Icons.dashboard_rounded;
|
||||
case NavigationItem.modConfiguration:
|
||||
return Icons.extension;
|
||||
case NavigationItem.serverProperties:
|
||||
return Icons.settings;
|
||||
case NavigationItem.about:
|
||||
return Icons.info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ abstract class Strings {
|
||||
static const buttonBrowse = '瀏覽';
|
||||
static const tabBasicConfiguration = '基本設定';
|
||||
static const tabModConfiguration = '模組設定';
|
||||
static const tabServerPropertiesConfiguration = '伺服器選項';
|
||||
static const tabServerProperties = '伺服器選項';
|
||||
static const tabAbout = '關於與說明';
|
||||
static const tabInstallationProgress = '安裝進度';
|
||||
static const tooltipEulaInfo = '點擊查看 EULA 條款';
|
||||
|
16
pubspec.lock
16
pubspec.lock
@ -232,6 +232,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+1
|
||||
version: "6.0.0-pre.1"
|
||||
|
||||
environment:
|
||||
sdk: ">=3.6.0 <4.0.0"
|
||||
@ -39,6 +39,7 @@ dependencies:
|
||||
flutter_bloc: ^9.1.1
|
||||
gap: ^3.0.1
|
||||
http: ^1.4.0
|
||||
package_info_plus: ^8.3.0
|
||||
path: ^1.9.1
|
||||
url_launcher: ^6.3.1
|
||||
window_manager: ^0.5.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user