Files
moonwell-launcher/lib/app/home_screen/home_screen_content.dart
T

192 lines
6.6 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:moonwell_launcher/app/design_system/mw_launcher_components.dart';
import 'package:moonwell_launcher/app/design_system/mw_shells.dart';
import 'package:moonwell_launcher/app/home_screen/bloc/home_screen_bloc.dart';
import 'package:moonwell_launcher/app/home_screen/bloc/home_screen_model.dart';
import 'package:moonwell_launcher/app/mw_app.dart';
import 'package:moonwell_launcher/features/launcher/domain/entities/client_sync_status.dart';
import 'package:window_manager/window_manager.dart';
class HomeScreenContent extends StatelessWidget {
const HomeScreenContent({super.key});
@override
Widget build(BuildContext context) {
final model = context.watch<HomeScreenBloc>().state.model;
return MwLauncherShell(
news: _buildNews(model),
syncActions: _buildSyncActions(context, model),
statusBar: _buildStatusBar(model),
account: MwAccountAffordance(
onSettings: () => _showSettings(context, model),
onLogout: () =>
context.read<HomeScreenBloc>().add(HomeScreenLogoutRequested()),
),
onDrag: windowManager.startDragging,
onDoubleTap: () => unawaited(_toggleMaximized()),
onMinimize: windowManager.minimize,
onMaximizeRestore: () => unawaited(_toggleMaximized()),
onClose: () => unawaited(windowManager.destroy()),
);
}
Widget _buildNews(HomeScreenModel model) {
final state = model.isLoadingNews
? MwNewsListState.loading
: model.newsItems.isNotEmpty
? MwNewsListState.populated
: model.newsErrorMessage != null
? MwNewsListState.failure
: MwNewsListState.empty;
return MwNewsList(
state: state,
error: model.newsErrorMessage,
items: [
for (final item in model.newsItems)
MwNewsItemData(
title: item.title,
body: item.body,
createdAt: item.createdAt,
imageUrl: item.imageUrl,
),
],
);
}
Widget _buildSyncActions(BuildContext context, HomeScreenModel model) {
final presentation = _syncPresentation(model);
final progress = model.progress.total > 0
? (model.progress.downloaded / model.progress.total).clamp(0.0, 1.0)
: null;
return MwSyncActionArea(
state: presentation,
status: model.statusText,
currentPath: model.currentPath,
error: model.errorMessage,
progress: progress,
onPrimary: () {
final bloc = context.read<HomeScreenBloc>();
if (model.phase == HomeScreenPhase.readyToPlay && model.canPlay) {
bloc.add(HomeScreenPlayRequested());
} else {
bloc.add(HomeScreenSyncRequested());
}
},
onPause: model.isSyncing
? () => context.read<HomeScreenBloc>().add(HomeScreenPauseRequested())
: null,
);
}
MwSyncPresentation _syncPresentation(HomeScreenModel model) {
if (model.phase == HomeScreenPhase.paused) {
return MwSyncPresentation.paused;
}
if (model.phase == HomeScreenPhase.failure) {
return MwSyncPresentation.failure;
}
if (model.phase == HomeScreenPhase.readyToPlay) {
return model.statusText.startsWith('Игра запущена')
? MwSyncPresentation.launched
: MwSyncPresentation.ready;
}
if (model.phase != HomeScreenPhase.syncing) {
return MwSyncPresentation.install;
}
return switch (model.syncStage) {
ClientSyncStage.downloadingFiles => MwSyncPresentation.downloading,
ClientSyncStage.verifyingInstallation => MwSyncPresentation.verifying,
ClientSyncStage.completed => MwSyncPresentation.ready,
_ => MwSyncPresentation.scanning,
};
}
Widget _buildStatusBar(HomeScreenModel model) {
return MwLauncherStatusBar(
status: model.outputPath?.toFilePath() ?? 'Папка установки не выбрана',
fileCount: model.totalFiles > 0
? '${model.processedFiles}/${model.totalFiles}'
: null,
speed: model.progress.speed > 0
? _formatSpeed(model.progress.speed)
: null,
eta: model.progress.eta > Duration.zero
? _formatDuration(model.progress.eta)
: null,
buildHash: _shortHash(model.localBuildHash ?? model.remoteBuildHash),
);
}
Future<void> _showSettings(
BuildContext context,
HomeScreenModel model,
) async {
final bloc = context.read<HomeScreenBloc>();
final themeController = LauncherThemeScope.of(context);
await showDialog<void>(
context: context,
builder: (dialogContext) => Dialog(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 460, maxHeight: 620),
child: AnimatedBuilder(
animation: themeController,
builder: (context, _) => SingleChildScrollView(
child: MwSettingsPanel(
installationPath: model.outputPath?.toFilePath(),
themeVariant: themeController.variant,
syncActive: model.isSyncing,
onChooseDirectory: () {
Navigator.of(dialogContext).pop();
bloc.add(HomeScreenOutputDirRequested());
},
onThemeChanged: (variant) {
unawaited(themeController.setVariant(variant));
},
onVerify: () {
Navigator.of(dialogContext).pop();
bloc.add(HomeScreenSyncRequested());
},
onLogout: () {
Navigator.of(dialogContext).pop();
bloc.add(HomeScreenLogoutRequested());
},
),
),
),
),
),
);
}
Future<void> _toggleMaximized() async {
if (await windowManager.isMaximized()) {
await windowManager.unmaximize();
} else {
await windowManager.maximize();
}
}
String _formatSpeed(int bytesPerSecond) {
const megabyte = 1024 * 1024;
if (bytesPerSecond >= megabyte) {
return '${(bytesPerSecond / megabyte).toStringAsFixed(1)} MB/s';
}
return '${(bytesPerSecond / 1024).toStringAsFixed(0)} KB/s';
}
String _formatDuration(Duration duration) {
final hours = duration.inHours.toString().padLeft(2, '0');
final minutes = (duration.inMinutes % 60).toString().padLeft(2, '0');
final seconds = (duration.inSeconds % 60).toString().padLeft(2, '0');
return '$hours:$minutes:$seconds';
}
String? _shortHash(String? hash) {
if (hash == null || hash.isEmpty) return null;
return hash.length <= 8 ? hash : hash.substring(0, 8);
}
}