192 lines
6.6 KiB
Dart
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);
|
|
}
|
|
}
|