правки лаунчера
This commit is contained in:
@@ -29,17 +29,20 @@ class HomeScreenBloc extends Bloc<HomeScreenEvent, HomeScreenState> {
|
||||
_preferencesRepository = preferencesRepository,
|
||||
_session = session,
|
||||
_manifest = manifest,
|
||||
super(HomeScreenState(
|
||||
model: HomeScreenModel.initial().copyWith(
|
||||
isAuthenticated: true,
|
||||
remoteBuildHash: manifest.buildHash,
|
||||
super(
|
||||
HomeScreenState(
|
||||
model: HomeScreenModel.initial().copyWith(
|
||||
isAuthenticated: true,
|
||||
remoteBuildHash: manifest.buildHash,
|
||||
),
|
||||
),
|
||||
)) {
|
||||
) {
|
||||
on<HomeScreenLoad>(_onHomeScreenLoad);
|
||||
on<HomeScreenSyncRequested>(_onHomeScreenSyncRequested);
|
||||
on<HomeScreenOutputDirRequested>(_onHomeScreenOutputDirRequested);
|
||||
on<HomeScreenPauseRequested>(_onHomeScreenPauseRequested);
|
||||
on<HomeScreenPlayRequested>(_onHomeScreenPlayRequested);
|
||||
on<HomeScreenLogoutRequested>(_onHomeScreenLogoutRequested);
|
||||
on<HomeScreenSyncStatusChanged>(_onHomeScreenSyncStatusChanged);
|
||||
on<HomeScreenSyncFailed>(_onHomeScreenSyncFailed);
|
||||
|
||||
@@ -220,6 +223,34 @@ class HomeScreenBloc extends Bloc<HomeScreenEvent, HomeScreenState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onHomeScreenLogoutRequested(
|
||||
HomeScreenLogoutRequested event,
|
||||
Emitter<HomeScreenState> emit,
|
||||
) async {
|
||||
_pauseRequested = true;
|
||||
await _syncSubscription?.cancel();
|
||||
_syncSubscription = null;
|
||||
await _preferencesRepository.clearLauncherSession();
|
||||
|
||||
emit(
|
||||
HomeScreenState(
|
||||
model: state.model.copyWith(
|
||||
phase: HomeScreenPhase.idle,
|
||||
isAuthenticated: false,
|
||||
progress: const DownloadProgress.initial(),
|
||||
statusText:
|
||||
'Сессия завершена. Войдите снова.',
|
||||
currentPath: null,
|
||||
errorMessage: null,
|
||||
localBuildHash: null,
|
||||
remoteBuildHash: null,
|
||||
processedFiles: 0,
|
||||
totalFiles: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onHomeScreenSyncStatusChanged(
|
||||
HomeScreenSyncStatusChanged event,
|
||||
Emitter<HomeScreenState> emit,
|
||||
|
||||
@@ -13,6 +13,8 @@ final class HomeScreenPauseRequested extends HomeScreenEvent {}
|
||||
|
||||
final class HomeScreenPlayRequested extends HomeScreenEvent {}
|
||||
|
||||
final class HomeScreenLogoutRequested extends HomeScreenEvent {}
|
||||
|
||||
final class HomeScreenSyncStatusChanged extends HomeScreenEvent {
|
||||
final ClientSyncStatus status;
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:moonwell_launcher/app/home_screen/bloc/home_screen_bloc.dart';
|
||||
import 'package:moonwell_launcher/app/home_screen/home_screen_content.dart';
|
||||
import 'package:moonwell_launcher/app/login_screen/login_screen.dart';
|
||||
import 'package:moonwell_launcher/app/theme/mw_theme.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/application/client_sync_use_case.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/game_installation_service.dart';
|
||||
@@ -28,35 +30,50 @@ class HomeScreen extends StatelessWidget {
|
||||
session: session,
|
||||
manifest: manifest,
|
||||
),
|
||||
child: Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
// Background image
|
||||
Positioned.fill(
|
||||
child: Image.asset('assets/background.png', fit: BoxFit.cover),
|
||||
child: BlocListener<HomeScreenBloc, HomeScreenState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.model.isAuthenticated && !current.model.isAuthenticated,
|
||||
listener: (context, state) async {
|
||||
await Navigator.of(context).pushReplacement(
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (_, __, ___) => const LoginScreen(),
|
||||
transitionsBuilder: (_, animation, __, child) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 250),
|
||||
),
|
||||
// Gradient overlay
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
MWColors.abyss.withAlpha(180),
|
||||
MWColors.abyss.withAlpha(220),
|
||||
MWColors.abyss.withAlpha(240),
|
||||
],
|
||||
stops: const [0.0, 0.5, 1.0],
|
||||
);
|
||||
},
|
||||
child: Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
// Background image
|
||||
Positioned.fill(
|
||||
child: Image.asset('assets/background.png', fit: BoxFit.cover),
|
||||
),
|
||||
// Gradient overlay
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
MWColors.abyss.withAlpha(180),
|
||||
MWColors.abyss.withAlpha(220),
|
||||
MWColors.abyss.withAlpha(240),
|
||||
],
|
||||
stops: const [0.0, 0.5, 1.0],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Main content
|
||||
const Positioned.fill(child: HomeScreenContent()),
|
||||
// Custom title bar
|
||||
const Positioned(top: 0, left: 0, right: 0, child: _TitleBar()),
|
||||
],
|
||||
// Main content
|
||||
const Positioned.fill(child: HomeScreenContent()),
|
||||
// Custom title bar
|
||||
const Positioned(top: 0, left: 0, right: 0, child: _TitleBar()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -95,7 +112,9 @@ class _TitleBar extends StatelessWidget {
|
||||
),
|
||||
_TitleBarButton(
|
||||
icon: Icons.close,
|
||||
onTap: () => SystemNavigator.pop(),
|
||||
onTap: () {
|
||||
unawaited(windowManager.destroy());
|
||||
},
|
||||
color: MWColors.error,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -25,10 +25,7 @@ class HomeScreenContent extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Left panel — News
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: _NewsPanel(cs: cs),
|
||||
),
|
||||
Expanded(flex: 3, child: _NewsPanel(cs: cs)),
|
||||
// Right panel — Status & Controls
|
||||
SizedBox(
|
||||
width: 320,
|
||||
@@ -72,9 +69,9 @@ class _NewsPanel extends StatelessWidget {
|
||||
),
|
||||
Text(
|
||||
'Новости',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: cs.onSurface,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(color: cs.onSurface),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// News list
|
||||
@@ -83,11 +80,7 @@ class _NewsPanel extends StatelessWidget {
|
||||
shaderCallback: (bounds) => LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.white,
|
||||
Colors.white,
|
||||
Colors.white.withAlpha(0),
|
||||
],
|
||||
colors: [Colors.white, Colors.white, Colors.white.withAlpha(0)],
|
||||
stops: const [0.0, 0.85, 1.0],
|
||||
).createShader(bounds),
|
||||
blendMode: BlendMode.dstIn,
|
||||
@@ -183,6 +176,25 @@ class _RightPanel extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton.icon(
|
||||
onPressed: () => context.read<HomeScreenBloc>().add(
|
||||
HomeScreenLogoutRequested(),
|
||||
),
|
||||
icon: const Icon(Icons.logout_rounded, size: 16),
|
||||
label: const Text('Выйти'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: cs.onSurfaceVariant,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 8,
|
||||
),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Spacer(),
|
||||
// Folder selector
|
||||
GestureDetector(
|
||||
@@ -211,7 +223,11 @@ class _RightPanel extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(Icons.edit, size: 14, color: cs.onSurfaceVariant.withAlpha(120)),
|
||||
Icon(
|
||||
Icons.edit,
|
||||
size: 14,
|
||||
color: cs.onSurfaceVariant.withAlpha(120),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -230,7 +246,10 @@ class _RightPanel extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
model.statusText,
|
||||
style: TextStyle(color: cs.onSurface.withAlpha(180), fontSize: 13),
|
||||
style: TextStyle(
|
||||
color: cs.onSurface.withAlpha(180),
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
if (model.currentPath != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
@@ -302,10 +321,7 @@ class _BottomBar extends StatelessWidget {
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
MWColors.abyss.withAlpha(0),
|
||||
MWColors.abyss.withAlpha(230),
|
||||
],
|
||||
colors: [MWColors.abyss.withAlpha(0), MWColors.abyss.withAlpha(230)],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:moonwell_launcher/app/home_screen/home_screen.dart';
|
||||
import 'package:moonwell_launcher/app/theme/mw_theme.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/launcher_api_client.dart';
|
||||
import 'package:moonwell_launcher/features/preferences/domain/repositories/preferences_repository.dart';
|
||||
import 'package:moonwell_launcher/service_container.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
@@ -42,8 +44,10 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
|
||||
try {
|
||||
final api = getIt<LauncherApiClient>();
|
||||
final preferencesRepository = getIt<PreferencesRepository>();
|
||||
final session = await api.login(username: username, password: password);
|
||||
final manifest = await api.fetchManifest(session.accessToken);
|
||||
await preferencesRepository.setLauncherSession(session);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -222,7 +226,9 @@ class _TitleBar extends StatelessWidget {
|
||||
),
|
||||
_TitleBarButton(
|
||||
icon: Icons.close,
|
||||
onTap: () => SystemNavigator.pop(),
|
||||
onTap: () {
|
||||
unawaited(windowManager.destroy());
|
||||
},
|
||||
color: MWColors.error,
|
||||
),
|
||||
],
|
||||
|
||||
+105
-1
@@ -1,6 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:moonwell_launcher/app/home_screen/home_screen.dart';
|
||||
import 'package:moonwell_launcher/app/login_screen/login_screen.dart';
|
||||
import 'package:moonwell_launcher/app/theme/mw_theme.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/application/restore_launcher_session_use_case.dart';
|
||||
import 'package:moonwell_launcher/features/launcher/data/launcher_api_client.dart';
|
||||
import 'package:moonwell_launcher/features/preferences/domain/repositories/preferences_repository.dart';
|
||||
import 'package:moonwell_launcher/service_container.dart';
|
||||
|
||||
class MoonWellApp extends StatelessWidget {
|
||||
const MoonWellApp({super.key});
|
||||
@@ -9,8 +14,107 @@ class MoonWellApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'MoonWell',
|
||||
home: const LoginScreen(),
|
||||
home: const _LauncherBootstrapScreen(),
|
||||
theme: moonWellTheme(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LauncherBootstrapScreen extends StatefulWidget {
|
||||
const _LauncherBootstrapScreen();
|
||||
|
||||
@override
|
||||
State<_LauncherBootstrapScreen> createState() =>
|
||||
_LauncherBootstrapScreenState();
|
||||
}
|
||||
|
||||
class _LauncherBootstrapScreenState extends State<_LauncherBootstrapScreen> {
|
||||
late final Future<RestoreLauncherSessionResult> _bootstrapFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bootstrapFuture = RestoreLauncherSessionUseCase(
|
||||
launcherApiClient: getIt<LauncherApiClient>(),
|
||||
preferencesRepository: getIt<PreferencesRepository>(),
|
||||
)();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<RestoreLauncherSessionResult>(
|
||||
future: _bootstrapFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState != ConnectionState.done) {
|
||||
return const _LauncherBootstrapLoadingScreen();
|
||||
}
|
||||
|
||||
final result =
|
||||
snapshot.data ??
|
||||
const RestoreLauncherSessionResult.unauthenticated();
|
||||
if (result.isAuthenticated) {
|
||||
return HomeScreen(
|
||||
session: result.session!,
|
||||
manifest: result.manifest!,
|
||||
);
|
||||
}
|
||||
|
||||
return const LoginScreen();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LauncherBootstrapLoadingScreen extends StatelessWidget {
|
||||
const _LauncherBootstrapLoadingScreen();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset('assets/background.png', fit: BoxFit.cover),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: RadialGradient(
|
||||
center: Alignment.center,
|
||||
radius: 1.0,
|
||||
colors: [
|
||||
MWColors.abyss.withAlpha(200),
|
||||
MWColors.abyss.withAlpha(240),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/logo.png',
|
||||
height: 160,
|
||||
filterQuality: FilterQuality.high,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
color: cs.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user