448 lines
13 KiB
Dart
448 lines
13 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:moonwell_launcher/app/design_system/mw_launcher_components.dart';
|
|
import 'package:moonwell_launcher/app/design_system/mw_window_chrome.dart';
|
|
import 'package:moonwell_launcher/app/theme/moonwell_design_system.dart';
|
|
|
|
const _ink = Color(0xFF06091A);
|
|
const _ivory = Color(0xFFECE4D0);
|
|
|
|
class MwBrandMark extends StatelessWidget {
|
|
const MwBrandMark({super.key, this.width = 220, this.compact = false});
|
|
|
|
final double width;
|
|
final bool compact;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final accent = MoonWellDesignTokens.of(context).accent;
|
|
return Semantics(
|
|
image: true,
|
|
label: 'MoonWell',
|
|
child: SizedBox(
|
|
width: width,
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_MoonMark(size: compact ? 28 : 40, color: accent),
|
|
const SizedBox(width: 12),
|
|
Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'MOONWELL',
|
|
style: TextStyle(
|
|
fontFamily: 'Cinzel',
|
|
color: _ivory,
|
|
fontSize: compact ? 15 : 22,
|
|
height: 1,
|
|
letterSpacing: compact ? 4 : 6,
|
|
),
|
|
),
|
|
if (!compact) ...[
|
|
const SizedBox(height: 5),
|
|
Text(
|
|
'LUNAR · LAUNCHER',
|
|
style: TextStyle(
|
|
fontFamily: 'JetBrains Mono',
|
|
color: accent,
|
|
fontSize: 9,
|
|
letterSpacing: 2.6,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MoonMark extends StatelessWidget {
|
|
const _MoonMark({required this.size, required this.color});
|
|
|
|
final double size;
|
|
final Color color;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SizedBox.square(
|
|
dimension: size,
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(color: color.withAlpha(90)),
|
|
boxShadow: [BoxShadow(color: color.withAlpha(70), blurRadius: 16)],
|
|
),
|
|
child: Icon(Icons.nightlight_round, color: color, size: size * .72),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MwArtworkBackground extends StatelessWidget {
|
|
const MwArtworkBackground({
|
|
super.key,
|
|
required this.child,
|
|
this.assetPackage,
|
|
});
|
|
|
|
final Widget child;
|
|
final String? assetPackage;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final accent = MoonWellDesignTokens.of(context).accent;
|
|
return Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
const ColoredBox(color: _ink),
|
|
// Image.asset(
|
|
// 'assets/background.png',
|
|
// package: assetPackage,
|
|
// fit: BoxFit.cover,
|
|
// alignment: Alignment.center,
|
|
// errorBuilder: (_, _, _) => const SizedBox.shrink(),
|
|
// ),
|
|
DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.centerLeft,
|
|
end: Alignment.centerRight,
|
|
colors: [
|
|
_ink.withAlpha(235),
|
|
_ink.withAlpha(85),
|
|
_ink.withAlpha(165),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
gradient: RadialGradient(
|
|
center: const Alignment(.35, -.5),
|
|
radius: .8,
|
|
colors: [accent.withAlpha(42), Colors.transparent],
|
|
),
|
|
),
|
|
),
|
|
child,
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class MwLoginShell extends StatelessWidget {
|
|
const MwLoginShell({
|
|
super.key,
|
|
required this.form,
|
|
required this.onDrag,
|
|
required this.onDoubleTap,
|
|
required this.onMinimize,
|
|
required this.onMaximizeRestore,
|
|
required this.onClose,
|
|
this.assetPackage,
|
|
});
|
|
|
|
final Widget form;
|
|
final VoidCallback onDrag;
|
|
final VoidCallback onDoubleTap;
|
|
final VoidCallback onMinimize;
|
|
final VoidCallback onMaximizeRestore;
|
|
final VoidCallback onClose;
|
|
final String? assetPackage;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: MwArtworkBackground(
|
|
assetPackage: assetPackage,
|
|
child: Stack(
|
|
children: [
|
|
Positioned.fill(
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(64, 80, 64, 58),
|
|
child: Align(
|
|
alignment: Alignment.bottomLeft,
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 500),
|
|
child: const Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'✦',
|
|
style: TextStyle(
|
|
color: Color(0xFF7FB8D4),
|
|
fontSize: 20,
|
|
),
|
|
),
|
|
SizedBox(height: 12),
|
|
Text(
|
|
'«Звёздный свет проведёт тебя сквозь самые '
|
|
'тёмные ночи — если ты готов идти туда, '
|
|
'куда он указывает.»',
|
|
style: TextStyle(
|
|
fontFamily: 'Cormorant Garamond',
|
|
fontStyle: FontStyle.italic,
|
|
color: Color(0xC6ECE4D0),
|
|
fontSize: 22,
|
|
height: 1.5,
|
|
),
|
|
),
|
|
SizedBox(height: 18),
|
|
Text(
|
|
'— МАЛФУРИОН ЯРОСТЬ БУРИ',
|
|
style: TextStyle(
|
|
fontFamily: 'JetBrains Mono',
|
|
color: Color(0x80ECE4D0),
|
|
fontSize: 10,
|
|
letterSpacing: 2,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
width: 480,
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xF20A0D1F),
|
|
border: Border(
|
|
left: BorderSide(color: Color(0x247FB8D4)),
|
|
),
|
|
),
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.fromLTRB(56, 64, 56, 58),
|
|
child: form,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Positioned(
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
child: MwWindowChrome(
|
|
onDrag: onDrag,
|
|
onDoubleTap: onDoubleTap,
|
|
onMinimize: onMinimize,
|
|
onMaximizeRestore: onMaximizeRestore,
|
|
onClose: onClose,
|
|
showMaximize: false,
|
|
),
|
|
),
|
|
const Positioned(
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
height: 26,
|
|
child: _LoginStatusBar(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _LoginStatusBar extends StatelessWidget {
|
|
const _LoginStatusBar();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xD904060E),
|
|
border: Border(top: BorderSide(color: Color(0x247FB8D4))),
|
|
),
|
|
child: const Row(
|
|
children: [
|
|
Text('Версия лаунчера 1.0.0'),
|
|
SizedBox(width: 24),
|
|
Text('·'),
|
|
SizedBox(width: 24),
|
|
Text('Соединение защищено TLS'),
|
|
Spacer(),
|
|
Text('© 2026 MoonWell'),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MwLauncherShell extends StatelessWidget {
|
|
const MwLauncherShell({
|
|
super.key,
|
|
required this.news,
|
|
required this.syncActions,
|
|
required this.statusBar,
|
|
required this.account,
|
|
required this.onDrag,
|
|
required this.onDoubleTap,
|
|
required this.onMinimize,
|
|
required this.onMaximizeRestore,
|
|
required this.onClose,
|
|
this.assetPackage,
|
|
});
|
|
|
|
final Widget news;
|
|
final Widget syncActions;
|
|
final Widget statusBar;
|
|
final Widget account;
|
|
final VoidCallback onDrag;
|
|
final VoidCallback onDoubleTap;
|
|
final VoidCallback onMinimize;
|
|
final VoidCallback onMaximizeRestore;
|
|
final VoidCallback onClose;
|
|
final String? assetPackage;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: MwArtworkBackground(
|
|
assetPackage: assetPackage,
|
|
child: Stack(
|
|
children: [
|
|
const Positioned(
|
|
left: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
width: 240,
|
|
child: MwGameRail(),
|
|
),
|
|
Positioned(
|
|
left: 272,
|
|
top: 24,
|
|
child: const MwBrandMark(width: 245),
|
|
),
|
|
Positioned(right: 28, top: 48, child: account),
|
|
const Positioned(left: 272, top: 160, child: _HeroCopy()),
|
|
Positioned(
|
|
right: 24,
|
|
top: 110,
|
|
bottom: 130,
|
|
width: 360,
|
|
child: news,
|
|
),
|
|
Positioned(
|
|
left: 240,
|
|
right: 0,
|
|
bottom: 26,
|
|
height: 110,
|
|
child: syncActions,
|
|
),
|
|
Positioned(
|
|
left: 240,
|
|
right: 0,
|
|
bottom: 0,
|
|
height: 26,
|
|
child: statusBar,
|
|
),
|
|
Positioned(
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
child: MwWindowChrome(
|
|
onDrag: onDrag,
|
|
onDoubleTap: onDoubleTap,
|
|
onMinimize: onMinimize,
|
|
onMaximizeRestore: onMaximizeRestore,
|
|
onClose: onClose,
|
|
showMaximize: false,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _HeroCopy extends StatelessWidget {
|
|
const _HeroCopy();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final accent = MoonWellDesignTokens.of(context).accent;
|
|
return SizedBox(
|
|
width: 520,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: accent),
|
|
borderRadius: BorderRadius.circular(99),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 6,
|
|
height: 6,
|
|
decoration: BoxDecoration(
|
|
color: accent,
|
|
shape: BoxShape.circle,
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Text(
|
|
'OPEN BETA',
|
|
style: TextStyle(
|
|
fontFamily: 'JetBrains Mono',
|
|
color: accent,
|
|
fontSize: 11,
|
|
letterSpacing: 2.4,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 18),
|
|
const Text(
|
|
'Свет колодца',
|
|
style: TextStyle(
|
|
fontFamily: 'Cinzel',
|
|
color: _ivory,
|
|
fontSize: 50,
|
|
height: 1.05,
|
|
letterSpacing: 1.5,
|
|
),
|
|
),
|
|
Text(
|
|
'зовёт тебя домой.',
|
|
style: TextStyle(
|
|
fontFamily: 'Cormorant Garamond',
|
|
fontStyle: FontStyle.italic,
|
|
color: accent,
|
|
fontSize: 54,
|
|
height: 1.05,
|
|
),
|
|
),
|
|
const SizedBox(height: 14),
|
|
const Text(
|
|
'Wrath of the Lich King · приватный сервер',
|
|
style: TextStyle(
|
|
fontFamily: 'Cormorant Garamond',
|
|
fontStyle: FontStyle.italic,
|
|
color: Color(0xC6ECE4D0),
|
|
fontSize: 18,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|