WIP: feat(ui): Migrate to new UI implementation

This commit is contained in:
2026-06-22 06:47:16 +04:00
parent 873aa8d7b5
commit bb4334f68d
109 changed files with 13499 additions and 1257 deletions
+65
View File
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:moonwell_launcher/app/theme/moonwell_design_system.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
import 'main.directories.g.dart';
void main() => runApp(const MoonWellWidgetbook());
@widgetbook.App()
class MoonWellWidgetbook extends StatelessWidget {
const MoonWellWidgetbook({super.key});
@override
Widget build(BuildContext context) {
return Widgetbook.material(
initialRoute: '/?path=compositions/mwlaunchershell/ready',
directories: directories,
addons: [
MaterialThemeAddon(
themes: [
WidgetbookTheme(
name: 'Forest',
data: MoonWellTheme.create(
MoonWellThemeVariant.forest,
fontPackage: 'moonwell_launcher',
),
),
WidgetbookTheme(
name: 'Temple',
data: MoonWellTheme.create(
MoonWellThemeVariant.temple,
fontPackage: 'moonwell_launcher',
),
),
],
),
ViewportAddon(const [
ViewportData(
name: 'Minimum desktop · 1024×640',
width: 1024,
height: 640,
pixelRatio: 1,
platform: TargetPlatform.windows,
),
ViewportData(
name: 'Target desktop · 1280×720',
width: 1280,
height: 720,
pixelRatio: 1,
platform: TargetPlatform.windows,
),
ViewportData(
name: 'Large desktop · 1600×900',
width: 1600,
height: 900,
pixelRatio: 1,
platform: TargetPlatform.windows,
),
]),
TextScaleAddon(min: 1, max: 2, divisions: 4, initialScale: 1),
],
);
}
}
+413
View File
@@ -0,0 +1,413 @@
// dart format width=80
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_import, prefer_relative_imports, directives_ordering
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// AppGenerator
// **************************************************************************
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:widgetbook/widgetbook.dart' as _widgetbook;
import 'package:widgetbook_workspace/stories/authentication_stories.dart'
as _widgetbook_workspace_stories_authentication_stories;
import 'package:widgetbook_workspace/stories/foundations_stories.dart'
as _widgetbook_workspace_stories_foundations_stories;
import 'package:widgetbook_workspace/stories/launcher_stories.dart'
as _widgetbook_workspace_stories_launcher_stories;
import 'package:widgetbook_workspace/stories/primitives_stories.dart'
as _widgetbook_workspace_stories_primitives_stories;
import 'package:widgetbook_workspace/stories/shell_stories.dart'
as _widgetbook_workspace_stories_shell_stories;
import 'package:widgetbook_workspace/stories/window_chrome_stories.dart'
as _widgetbook_workspace_stories_window_chrome_stories;
final directories = <_widgetbook.WidgetbookNode>[
_widgetbook.WidgetbookCategory(
name: 'Authentication',
children: [
_widgetbook.WidgetbookComponent(
name: 'MwAuthenticationForm',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'api_error',
builder: _widgetbook_workspace_stories_authentication_stories
.buildMwAuthenticationFormApiErrorUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'default',
builder: _widgetbook_workspace_stories_authentication_stories
.buildMwAuthenticationFormUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'loading',
builder: _widgetbook_workspace_stories_authentication_stories
.buildMwAuthenticationFormLoadingUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'long_russian_copy',
builder: _widgetbook_workspace_stories_authentication_stories
.buildMwAuthenticationFormLongRussianCopyUseCase,
),
],
),
],
),
_widgetbook.WidgetbookCategory(
name: 'Compositions',
children: [
_widgetbook.WidgetbookComponent(
name: 'MwLauncherShell',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'downloading',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLauncherShellDownloadingUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'empty_news',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLauncherShellEmptyNewsUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'failure',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLauncherShellFailureUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'paused',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLauncherShellPausedUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'ready',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLauncherShellReadyUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'verifying',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLauncherShellVerifyingUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwLoginShell',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'api_error',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLoginShellApiErrorUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'default',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLoginShellUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'restored_session_loading',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwLoginShellRestoredSessionLoadingUseCase,
),
],
),
],
),
_widgetbook.WidgetbookCategory(
name: 'Foundations',
children: [
_widgetbook.WidgetbookComponent(
name: 'ArtworkSlot',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'default',
builder: _widgetbook_workspace_stories_foundations_stories
.buildArtworkSlotUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'FoundationsGallery',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'default',
builder: _widgetbook_workspace_stories_foundations_stories
.buildFoundationsGalleryUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwArtworkBackground',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'default',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwArtworkBackgroundUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwBrandMark',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'default',
builder: _widgetbook_workspace_stories_shell_stories
.buildMwBrandMarkUseCase,
),
],
),
],
),
_widgetbook.WidgetbookCategory(
name: 'Launcher',
children: [
_widgetbook.WidgetbookComponent(
name: 'MwAccountAffordance',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'default',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwAccountAffordanceUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwGameRail',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'compact',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwGameRailCompactUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'full',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwGameRailFullUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwGameTile',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwGameTileInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwLauncherStatusBar',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'metadata',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwLauncherStatusBarMetadataUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwNewsItem',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'image_failure',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwNewsItemImageFailureUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'text_only',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwNewsItemTextOnlyUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwNewsList',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'empty',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwNewsListEmptyUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'failure',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwNewsListFailureUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'loading',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwNewsListLoadingUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'populated',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwNewsListPopulatedUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwSettingsPanel',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwSettingsPanelInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwSyncActionArea',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_launcher_stories
.buildMwSyncActionAreaInteractiveUseCase,
),
],
),
],
),
_widgetbook.WidgetbookCategory(
name: 'Primitives',
children: [
_widgetbook.WidgetbookComponent(
name: 'MwBadge',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwBadgeInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwButton',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'disabled',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwButtonDisabledUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwButtonInteractiveUseCase,
),
_widgetbook.WidgetbookUseCase(
name: 'loading',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwButtonLoadingUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwCheckbox',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwCheckboxInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwDisabledFeature',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'default',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwDisabledFeatureUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwIconButton',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwIconButtonInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwPanel',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwPanelInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwProgressBar',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwProgressBarInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwStateView',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwStateViewInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwStatusText',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwStatusTextInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwTabs',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwTabsInteractiveUseCase,
),
],
),
_widgetbook.WidgetbookComponent(
name: 'MwTextField',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_primitives_stories
.buildMwTextFieldInteractiveUseCase,
),
],
),
],
),
_widgetbook.WidgetbookCategory(
name: 'Window Chrome',
children: [
_widgetbook.WidgetbookComponent(
name: 'MwWindowChrome',
useCases: [
_widgetbook.WidgetbookUseCase(
name: 'interactive',
builder: _widgetbook_workspace_stories_window_chrome_stories
.buildMwWindowChromeInteractiveUseCase,
),
],
),
],
),
];
@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:moonwell_launcher/app/design_system/mw_authentication.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
@widgetbook.UseCase(
name: 'default',
type: MwAuthenticationForm,
path: '[Authentication]',
)
Widget buildMwAuthenticationFormUseCase(BuildContext context) {
return SizedBox(
width: 420,
child: MwAuthenticationForm(
usernameController: TextEditingController(
text: context.knobs.string(label: 'username', initialValue: ''),
),
passwordController: TextEditingController(
text: context.knobs.string(label: 'password', initialValue: ''),
),
onSubmit: () => debugPrint('Authentication form submitted'),
),
);
}
@widgetbook.UseCase(
name: 'loading',
type: MwAuthenticationForm,
path: '[Authentication]',
)
Widget buildMwAuthenticationFormLoadingUseCase(BuildContext context) {
return SizedBox(
width: 420,
child: MwAuthenticationForm(
usernameController: TextEditingController(text: 'moonwalker'),
passwordController: TextEditingController(text: 'password'),
loading: true,
onSubmit: () => debugPrint('Loading authentication form submitted'),
),
);
}
@widgetbook.UseCase(
name: 'api_error',
type: MwAuthenticationForm,
path: '[Authentication]',
)
Widget buildMwAuthenticationFormApiErrorUseCase(BuildContext context) {
return SizedBox(
width: 420,
child: MwAuthenticationForm(
usernameController: TextEditingController(text: 'moonwalker'),
passwordController: TextEditingController(),
error: context.knobs.string(
label: 'error',
initialValue: 'Неверный логин или пароль.',
),
onSubmit: () => debugPrint('Errored authentication form submitted'),
),
);
}
@widgetbook.UseCase(
name: 'long_russian_copy',
type: MwAuthenticationForm,
path: '[Authentication]',
)
Widget buildMwAuthenticationFormLongRussianCopyUseCase(BuildContext context) {
return SizedBox(
width: 420,
child: MwAuthenticationForm(
usernameController: TextEditingController(),
passwordController: TextEditingController(),
error:
'Не удалось связаться с сервером авторизации. Проверьте подключение '
'к интернету и повторите попытку через несколько минут.',
onSubmit: () => debugPrint('Long-copy authentication form submitted'),
),
);
}
@@ -0,0 +1,149 @@
import 'package:flutter/material.dart';
import 'package:moonwell_launcher/app/design_system/mw_primitives.dart';
import 'package:moonwell_launcher/app/design_system/mw_shells.dart';
import 'package:moonwell_launcher/app/theme/moonwell_design_system.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
class FoundationsGallery extends StatelessWidget {
const FoundationsGallery({super.key});
@override
Widget build(BuildContext context) {
final tokens = MoonWellDesignTokens.of(context);
final colors = <String, Color>{
'Background': tokens.background,
'Surface low': tokens.surfaceLow,
'Surface': tokens.surface,
'Surface high': tokens.surfaceHigh,
'Accent': tokens.accent,
'Accent muted': tokens.accentMuted,
'Success': tokens.success,
'Warning': tokens.warning,
'Error': Theme.of(context).colorScheme.error,
};
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Semantic colors',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
for (final entry in colors.entries)
SizedBox(
width: 144,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 72,
decoration: BoxDecoration(
color: entry.value,
border: Border.all(color: Colors.white24),
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(height: 6),
Text(entry.key),
MwStatusText(
text:
'#${entry.value.toARGB32().toRadixString(16).substring(2).toUpperCase()}',
monospace: true,
),
],
),
),
],
),
const SizedBox(height: 32),
Text('Typography', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Text(
'MoonWell',
style: TextStyle(
fontFamily: tokens.brandFontFamily,
fontSize: 32,
letterSpacing: 2,
),
),
Text(
'Лунный колодец зовёт героев Нордскола',
style: Theme.of(context).textTheme.headlineMedium,
),
const Text('Интерфейс, формы, навигация и рабочие статусы'),
MwStatusText(
text: 'build a93fd1c2 · 12.4 MB/s · 00:01:42',
monospace: true,
),
const SizedBox(height: 32),
Text(
'Surfaces & radii',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 12),
const Row(
children: [
Expanded(
child: MwPanel(level: MwPanelLevel.low, child: Text('Low')),
),
SizedBox(width: 12),
Expanded(child: MwPanel(child: Text('Regular'))),
SizedBox(width: 12),
Expanded(
child: MwPanel(level: MwPanelLevel.high, child: Text('High')),
),
],
),
],
),
);
}
}
@widgetbook.UseCase(
name: 'default',
type: FoundationsGallery,
path: '[Foundations]',
)
Widget buildFoundationsGalleryUseCase(BuildContext context) {
return const FoundationsGallery();
}
class ArtworkSlot extends StatelessWidget {
const ArtworkSlot({super.key});
@override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
Image.asset(
'assets/background.png',
package: 'moonwell_launcher',
fit: BoxFit.cover,
),
const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0x22000000), Color(0xEE000000)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
const Center(child: MwBrandMark(width: 240)),
],
);
}
}
@widgetbook.UseCase(name: 'default', type: ArtworkSlot, path: '[Foundations]')
Widget buildArtworkSlotUseCase(BuildContext context) {
return const ArtworkSlot();
}
@@ -0,0 +1,230 @@
import 'package:flutter/material.dart';
import 'package:moonwell_launcher/app/design_system/mw_launcher_components.dart';
import 'package:moonwell_launcher/core/moonwell_theme_variant.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
@widgetbook.UseCase(name: 'interactive', type: MwGameTile, path: '[Launcher]')
Widget buildMwGameTileInteractiveUseCase(BuildContext context) {
return SizedBox(
width: context.knobs.double.slider(
label: 'width',
initialValue: 240,
min: 72,
max: 360,
divisions: 36,
),
child: MwGameTile(
title: context.knobs.string(label: 'title', initialValue: 'MoonWell'),
subtitle: context.knobs.stringOrNull(
label: 'subtitle',
initialValue: 'Wrath of the Lich King',
),
icon: Icons.nights_stay_outlined,
selected: context.knobs.boolean(label: 'selected', initialValue: true),
enabled: context.knobs.boolean(label: 'enabled', initialValue: true),
compact: context.knobs.boolean(label: 'compact', initialValue: false),
onPressed: () => debugPrint('Game tile selected'),
),
);
}
@widgetbook.UseCase(name: 'full', type: MwGameRail, path: '[Launcher]')
Widget buildMwGameRailFullUseCase(BuildContext context) {
return const SizedBox(width: 240, height: 560, child: MwGameRail());
}
@widgetbook.UseCase(name: 'compact', type: MwGameRail, path: '[Launcher]')
Widget buildMwGameRailCompactUseCase(BuildContext context) {
return const SizedBox(
width: 72,
height: 560,
child: MwGameRail(compact: true),
);
}
@widgetbook.UseCase(name: 'text_only', type: MwNewsItem, path: '[Launcher]')
Widget buildMwNewsItemTextOnlyUseCase(BuildContext context) {
return SizedBox(
width: 620,
child: MwNewsItem(
item: MwNewsItemData(
title: context.knobs.string(
label: 'title',
initialValue: 'Добро пожаловать в MoonWell',
),
body: context.knobs.string(
label: 'body',
initialValue: 'Приключение в Нордсколе начинается здесь.',
),
createdAt: DateTime(2026, 6, 22),
),
),
);
}
@widgetbook.UseCase(name: 'image_failure', type: MwNewsItem, path: '[Launcher]')
Widget buildMwNewsItemImageFailureUseCase(BuildContext context) {
return const SizedBox(
width: 620,
child: MwNewsItem(
item: MwNewsItemData(
title: 'Обновление игрового мира',
body: 'Изображение недоступно, но публикация остаётся читаемой.',
imageUrl: 'https://invalid.moonwell.local/news.jpg',
),
),
);
}
@widgetbook.UseCase(name: 'loading', type: MwNewsList, path: '[Launcher]')
Widget buildMwNewsListLoadingUseCase(BuildContext context) {
return const MwNewsList(state: MwNewsListState.loading);
}
@widgetbook.UseCase(name: 'empty', type: MwNewsList, path: '[Launcher]')
Widget buildMwNewsListEmptyUseCase(BuildContext context) {
return const MwNewsList(state: MwNewsListState.empty);
}
@widgetbook.UseCase(name: 'failure', type: MwNewsList, path: '[Launcher]')
Widget buildMwNewsListFailureUseCase(BuildContext context) {
return MwNewsList(
state: MwNewsListState.failure,
error: context.knobs.string(
label: 'error',
initialValue: 'Сервер новостей временно недоступен.',
),
onRetry: () => debugPrint('News reload requested'),
);
}
@widgetbook.UseCase(name: 'populated', type: MwNewsList, path: '[Launcher]')
Widget buildMwNewsListPopulatedUseCase(BuildContext context) {
final count = context.knobs.int.slider(
label: 'itemCount',
initialValue: 8,
min: 1,
max: 50,
divisions: 49,
);
return MwNewsList(
state: MwNewsListState.populated,
items: List.generate(
count,
(index) => MwNewsItemData(
title: 'Новость ${index + 1}',
body:
'Реалистичный текст публикации о событиях и обновлениях MoonWell.',
createdAt: DateTime(2026, 6, 22).subtract(Duration(days: index)),
),
),
);
}
@widgetbook.UseCase(
name: 'interactive',
type: MwSyncActionArea,
path: '[Launcher]',
)
Widget buildMwSyncActionAreaInteractiveUseCase(BuildContext context) {
final state = context.knobs.object.dropdown<MwSyncPresentation>(
label: 'state',
initialOption: MwSyncPresentation.downloading,
options: MwSyncPresentation.values,
labelBuilder: (value) => value.name,
);
return SizedBox(
width: 420,
child: MwSyncActionArea(
state: state,
status: context.knobs.string(
label: 'status',
initialValue: 'Загружаем обновление клиента…',
),
progress: context.knobs.double.slider(
label: 'progress',
initialValue: .46,
min: 0,
max: 1,
divisions: 100,
),
currentPath: context.knobs.stringOrNull(
label: 'currentPath',
initialValue: r'Data\ruRU\patch-ruRU.MPQ',
),
error: state == MwSyncPresentation.failure
? context.knobs.string(
label: 'error',
initialValue: 'Проверка SHA-256 не пройдена.',
)
: null,
onPrimary: () => debugPrint('Primary sync action requested: $state'),
onPause: state == MwSyncPresentation.downloading
? () => debugPrint('Sync pause requested')
: null,
),
);
}
@widgetbook.UseCase(
name: 'metadata',
type: MwLauncherStatusBar,
path: '[Launcher]',
)
Widget buildMwLauncherStatusBarMetadataUseCase(BuildContext context) {
return MwLauncherStatusBar(
status: context.knobs.string(
label: 'status',
initialValue: 'Загрузка игровых файлов',
),
fileCount: 'Файлы 124/480',
speed: '12.4 MB/s',
eta: 'ETA 00:01:42',
buildHash: 'a93fd1c2',
);
}
@widgetbook.UseCase(
name: 'default',
type: MwAccountAffordance,
path: '[Launcher]',
)
Widget buildMwAccountAffordanceUseCase(BuildContext context) {
return MwAccountAffordance(
onSettings: () => debugPrint('Settings requested'),
onLogout: () => debugPrint('Logout requested'),
);
}
@widgetbook.UseCase(
name: 'interactive',
type: MwSettingsPanel,
path: '[Launcher]',
)
Widget buildMwSettingsPanelInteractiveUseCase(BuildContext context) {
final variant = context.knobs.object.dropdown<MoonWellThemeVariant>(
label: 'themeVariant',
initialOption: MoonWellThemeVariant.forest,
options: MoonWellThemeVariant.values,
labelBuilder: (value) => value.name,
);
return SizedBox(
width: 420,
child: MwSettingsPanel(
installationPath: context.knobs.stringOrNull(
label: 'installationPath',
initialValue: r'C:\Games\MoonWell',
),
themeVariant: variant,
syncActive: context.knobs.boolean(
label: 'syncActive',
initialValue: false,
),
onChooseDirectory: () => debugPrint('Directory selection requested'),
onThemeChanged: (value) => debugPrint('Theme changed: ${value.name}'),
onVerify: () => debugPrint('Manual verification requested'),
onLogout: () => debugPrint('Logout requested'),
),
);
}
@@ -0,0 +1,258 @@
import 'package:flutter/material.dart';
import 'package:moonwell_launcher/app/design_system/mw_primitives.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
@widgetbook.UseCase(name: 'interactive', type: MwButton, path: '[Primitives]')
Widget buildMwButtonInteractiveUseCase(BuildContext context) {
final variant = context.knobs.object.dropdown<MwButtonVariant>(
label: 'variant',
initialOption: MwButtonVariant.primary,
options: MwButtonVariant.values,
labelBuilder: (value) => value.name,
);
return MwButton(
label: context.knobs.string(label: 'label', initialValue: 'Играть'),
icon: context.knobs.boolean(label: 'showIcon', initialValue: true)
? Icons.play_arrow
: null,
variant: variant,
loading: context.knobs.boolean(label: 'loading', initialValue: false),
expand: context.knobs.boolean(label: 'expand', initialValue: false),
onPressed: context.knobs.boolean(label: 'enabled', initialValue: true)
? () => debugPrint('MwButton pressed')
: null,
);
}
@widgetbook.UseCase(name: 'loading', type: MwButton, path: '[Primitives]')
Widget buildMwButtonLoadingUseCase(BuildContext context) {
return MwButton(
label: context.knobs.string(label: 'label', initialValue: 'Загрузка'),
loading: true,
onPressed: () => debugPrint('Loading MwButton pressed'),
);
}
@widgetbook.UseCase(name: 'disabled', type: MwButton, path: '[Primitives]')
Widget buildMwButtonDisabledUseCase(BuildContext context) {
return MwButton(
label: context.knobs.string(label: 'label', initialValue: 'Недоступно'),
onPressed: null,
);
}
@widgetbook.UseCase(
name: 'interactive',
type: MwIconButton,
path: '[Primitives]',
)
Widget buildMwIconButtonInteractiveUseCase(BuildContext context) {
return MwIconButton(
icon: Icons.settings,
tooltip: context.knobs.string(label: 'tooltip', initialValue: 'Настройки'),
selected: context.knobs.boolean(label: 'selected', initialValue: false),
destructive: context.knobs.boolean(
label: 'destructive',
initialValue: false,
),
onPressed: context.knobs.boolean(label: 'enabled', initialValue: true)
? () => debugPrint('MwIconButton pressed')
: null,
);
}
@widgetbook.UseCase(
name: 'interactive',
type: MwTextField,
path: '[Primitives]',
)
Widget buildMwTextFieldInteractiveUseCase(BuildContext context) {
return SizedBox(
width: 380,
child: MwTextField(
label: context.knobs.string(label: 'label', initialValue: 'Логин'),
hint: context.knobs.stringOrNull(
label: 'hint',
initialValue: 'Введите логин',
),
errorText: context.knobs.stringOrNull(
label: 'errorText',
initialValue: null,
),
enabled: context.knobs.boolean(label: 'enabled', initialValue: true),
obscureText: context.knobs.boolean(
label: 'obscureText',
initialValue: false,
),
prefixIcon: context.knobs.boolean(label: 'showIcon', initialValue: true)
? Icons.person_outline
: null,
onSubmitted: (value) => debugPrint('MwTextField submitted: $value'),
),
);
}
@widgetbook.UseCase(name: 'interactive', type: MwCheckbox, path: '[Primitives]')
Widget buildMwCheckboxInteractiveUseCase(BuildContext context) {
return MwCheckbox(
label: context.knobs.string(label: 'label', initialValue: 'Запомнить меня'),
value: context.knobs.boolean(label: 'value', initialValue: false),
onChanged: context.knobs.boolean(label: 'enabled', initialValue: true)
? (value) => debugPrint('MwCheckbox changed: $value')
: null,
);
}
@widgetbook.UseCase(name: 'interactive', type: MwTabs, path: '[Primitives]')
Widget buildMwTabsInteractiveUseCase(BuildContext context) {
return MwTabs(
labels: const ['Новости', 'Обновления', 'Очень длинный раздел'],
selectedIndex: context.knobs.int.slider(
label: 'selectedIndex',
initialValue: 0,
min: 0,
max: 2,
divisions: 2,
),
disabledIndices:
context.knobs.boolean(label: 'disableFutureTabs', initialValue: true)
? const {1, 2}
: const {},
onSelected: (index) => debugPrint('MwTabs selected: $index'),
);
}
@widgetbook.UseCase(name: 'interactive', type: MwBadge, path: '[Primitives]')
Widget buildMwBadgeInteractiveUseCase(BuildContext context) {
return MwBadge(
label: context.knobs.string(label: 'label', initialValue: 'Готово'),
tone: context.knobs.object.dropdown<MwBadgeTone>(
label: 'tone',
initialOption: MwBadgeTone.success,
options: MwBadgeTone.values,
labelBuilder: (value) => value.name,
),
);
}
@widgetbook.UseCase(name: 'interactive', type: MwPanel, path: '[Primitives]')
Widget buildMwPanelInteractiveUseCase(BuildContext context) {
return SizedBox(
width: 420,
child: MwPanel(
level: context.knobs.object.dropdown<MwPanelLevel>(
label: 'level',
initialOption: MwPanelLevel.regular,
options: MwPanelLevel.values,
labelBuilder: (value) => value.name,
),
child: Text(
context.knobs.string(
label: 'content',
initialValue: 'Содержимое панели MoonWell',
),
),
),
);
}
@widgetbook.UseCase(
name: 'interactive',
type: MwProgressBar,
path: '[Primitives]',
)
Widget buildMwProgressBarInteractiveUseCase(BuildContext context) {
return SizedBox(
width: 440,
child: MwProgressBar(
value: context.knobs.boolean(label: 'indeterminate', initialValue: false)
? null
: context.knobs.double.slider(
label: 'value',
initialValue: .46,
min: 0,
max: 1,
divisions: 100,
),
label: context.knobs.stringOrNull(
label: 'label',
initialValue: 'Загрузка файлов',
),
state: context.knobs.object.dropdown<MwProgressState>(
label: 'state',
initialOption: MwProgressState.active,
options: MwProgressState.values,
labelBuilder: (value) => value.name,
),
),
);
}
@widgetbook.UseCase(
name: 'interactive',
type: MwStatusText,
path: '[Primitives]',
)
Widget buildMwStatusTextInteractiveUseCase(BuildContext context) {
return MwStatusText(
text: context.knobs.string(
label: 'text',
initialValue: 'Проверяем локальные файлы…',
),
tone: context.knobs.object.dropdown<MwStatusTone>(
label: 'tone',
initialOption: MwStatusTone.operational,
options: MwStatusTone.values,
labelBuilder: (value) => value.name,
),
monospace: context.knobs.boolean(label: 'monospace', initialValue: false),
);
}
@widgetbook.UseCase(
name: 'interactive',
type: MwStateView,
path: '[Primitives]',
)
Widget buildMwStateViewInteractiveUseCase(BuildContext context) {
final kind = context.knobs.object.dropdown<MwStateKind>(
label: 'kind',
initialOption: MwStateKind.empty,
options: MwStateKind.values,
labelBuilder: (value) => value.name,
);
return MwStateView(
kind: kind,
title: context.knobs.string(
label: 'title',
initialValue: 'Новостей пока нет',
),
description: context.knobs.stringOrNull(
label: 'description',
initialValue: 'Новые публикации появятся здесь.',
),
onRetry: kind == MwStateKind.error
? () => debugPrint('MwStateView retry pressed')
: null,
);
}
@widgetbook.UseCase(
name: 'default',
type: MwDisabledFeature,
path: '[Primitives]',
)
Widget buildMwDisabledFeatureUseCase(BuildContext context) {
return MwDisabledFeature(
message: context.knobs.string(
label: 'message',
initialValue: 'Регистрация появится позже',
),
child: const MwButton(
label: 'Регистрация · скоро',
onPressed: null,
variant: MwButtonVariant.secondary,
),
);
}
+246
View File
@@ -0,0 +1,246 @@
import 'package:flutter/material.dart';
import 'package:moonwell_launcher/app/design_system/mw_authentication.dart';
import 'package:moonwell_launcher/app/design_system/mw_launcher_components.dart';
import 'package:moonwell_launcher/app/design_system/mw_shells.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
VoidCallback _windowCallback(String action) {
return () => debugPrint('Window action requested: $action');
}
@widgetbook.UseCase(name: 'default', type: MwBrandMark, path: '[Foundations]')
Widget buildMwBrandMarkUseCase(BuildContext context) {
return MwBrandMark(
width: context.knobs.double.slider(
label: 'width',
initialValue: 220,
min: 96,
max: 360,
divisions: 33,
),
);
}
@widgetbook.UseCase(
name: 'default',
type: MwArtworkBackground,
path: '[Foundations]',
)
Widget buildMwArtworkBackgroundUseCase(BuildContext context) {
return const MwArtworkBackground(
assetPackage: 'moonwell_launcher',
child: Center(child: Text('Safe content area')),
);
}
@widgetbook.UseCase(name: 'default', type: MwLoginShell, path: '[Compositions]')
Widget buildMwLoginShellUseCase(BuildContext context) {
return MwLoginShell(
assetPackage: 'moonwell_launcher',
form: MwAuthenticationForm(
usernameController: TextEditingController(),
passwordController: TextEditingController(),
onSubmit: () => debugPrint('Login submitted'),
),
onDrag: _windowCallback('drag'),
onDoubleTap: _windowCallback('double_tap'),
onMinimize: _windowCallback('minimize'),
onMaximizeRestore: _windowCallback('maximize_restore'),
onClose: _windowCallback('close'),
);
}
@widgetbook.UseCase(
name: 'api_error',
type: MwLoginShell,
path: '[Compositions]',
)
Widget buildMwLoginShellApiErrorUseCase(BuildContext context) {
return MwLoginShell(
assetPackage: 'moonwell_launcher',
form: MwAuthenticationForm(
usernameController: TextEditingController(text: 'moonwalker'),
passwordController: TextEditingController(),
error: context.knobs.string(
label: 'error',
initialValue: 'Неверный логин или пароль.',
),
onSubmit: () => debugPrint('Login submitted after API error'),
),
onDrag: _windowCallback('drag'),
onDoubleTap: _windowCallback('double_tap'),
onMinimize: _windowCallback('minimize'),
onMaximizeRestore: _windowCallback('maximize_restore'),
onClose: _windowCallback('close'),
);
}
@widgetbook.UseCase(
name: 'restored_session_loading',
type: MwLoginShell,
path: '[Compositions]',
)
Widget buildMwLoginShellRestoredSessionLoadingUseCase(BuildContext context) {
return MwLoginShell(
assetPackage: 'moonwell_launcher',
form: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Восстанавливаем защищённую сессию…'),
],
),
),
onDrag: _windowCallback('drag'),
onDoubleTap: _windowCallback('double_tap'),
onMinimize: _windowCallback('minimize'),
onMaximizeRestore: _windowCallback('maximize_restore'),
onClose: _windowCallback('close'),
);
}
MwLauncherShell _launcherShell({
required MwNewsListState newsState,
required MwSyncPresentation syncState,
required String status,
double? progress,
String? error,
}) {
return MwLauncherShell(
assetPackage: 'moonwell_launcher',
news: MwNewsList(
state: newsState,
error: newsState == MwNewsListState.failure
? 'Сервер новостей временно недоступен.'
: null,
items: newsState == MwNewsListState.populated
? List.generate(
8,
(index) => MwNewsItemData(
title: 'Новость ${index + 1}',
body: 'События игрового мира и важные обновления MoonWell.',
createdAt: DateTime(2026, 6, 22 - index),
),
)
: const [],
onRetry: () => debugPrint('News reload requested'),
),
syncActions: MwSyncActionArea(
state: syncState,
status: status,
progress: progress,
currentPath: syncState == MwSyncPresentation.downloading
? r'Data\ruRU\patch-ruRU.MPQ'
: null,
error: error,
onPrimary: () => debugPrint('Primary sync action requested'),
onPause: syncState == MwSyncPresentation.downloading
? () => debugPrint('Sync pause requested')
: null,
),
statusBar: const MwLauncherStatusBar(
status: 'MoonWell Launcher · локальная установка',
fileCount: '124/480',
buildHash: 'a93fd1c2',
),
account: MwAccountAffordance(
onSettings: () => debugPrint('Settings requested'),
onLogout: () => debugPrint('Logout requested'),
),
onDrag: _windowCallback('drag'),
onDoubleTap: _windowCallback('double_tap'),
onMinimize: _windowCallback('minimize'),
onMaximizeRestore: _windowCallback('maximize_restore'),
onClose: _windowCallback('close'),
);
}
@widgetbook.UseCase(
name: 'ready',
type: MwLauncherShell,
path: '[Compositions]',
)
Widget buildMwLauncherShellReadyUseCase(BuildContext context) {
return _launcherShell(
newsState: MwNewsListState.populated,
syncState: MwSyncPresentation.ready,
status: 'Клиент готов к запуску.',
);
}
@widgetbook.UseCase(
name: 'downloading',
type: MwLauncherShell,
path: '[Compositions]',
)
Widget buildMwLauncherShellDownloadingUseCase(BuildContext context) {
return _launcherShell(
newsState: MwNewsListState.populated,
syncState: MwSyncPresentation.downloading,
status: 'Загружаем обновление клиента…',
progress: context.knobs.double.slider(
label: 'progress',
initialValue: .46,
min: 0,
max: 1,
divisions: 100,
),
);
}
@widgetbook.UseCase(
name: 'paused',
type: MwLauncherShell,
path: '[Compositions]',
)
Widget buildMwLauncherShellPausedUseCase(BuildContext context) {
return _launcherShell(
newsState: MwNewsListState.populated,
syncState: MwSyncPresentation.paused,
status: 'Обновление приостановлено.',
progress: .46,
);
}
@widgetbook.UseCase(
name: 'verifying',
type: MwLauncherShell,
path: '[Compositions]',
)
Widget buildMwLauncherShellVerifyingUseCase(BuildContext context) {
return _launcherShell(
newsState: MwNewsListState.populated,
syncState: MwSyncPresentation.verifying,
status: 'Проверяем целостность установки…',
);
}
@widgetbook.UseCase(
name: 'failure',
type: MwLauncherShell,
path: '[Compositions]',
)
Widget buildMwLauncherShellFailureUseCase(BuildContext context) {
return _launcherShell(
newsState: MwNewsListState.failure,
syncState: MwSyncPresentation.failure,
status: 'Не удалось завершить обновление.',
error: 'Проверка SHA-256 не пройдена.',
);
}
@widgetbook.UseCase(
name: 'empty_news',
type: MwLauncherShell,
path: '[Compositions]',
)
Widget buildMwLauncherShellEmptyNewsUseCase(BuildContext context) {
return _launcherShell(
newsState: MwNewsListState.empty,
syncState: MwSyncPresentation.ready,
status: 'Клиент готов к запуску.',
);
}
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:moonwell_launcher/app/design_system/mw_window_chrome.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
@widgetbook.UseCase(
name: 'interactive',
type: MwWindowChrome,
path: '[Window Chrome]',
)
Widget buildMwWindowChromeInteractiveUseCase(BuildContext context) {
return MwWindowChrome(
onDrag: () => debugPrint('Window drag requested'),
onDoubleTap: () => debugPrint('Window maximize/restore requested'),
onMinimize: () => debugPrint('Window minimize requested'),
onMaximizeRestore: () => debugPrint('Window maximize/restore requested'),
onClose: () => debugPrint('Window close requested'),
showMaximize: context.knobs.boolean(label: 'Show maximize button'),
);
}