MCSI-2 Browse installing directory #19

Merged
squid merged 5 commits from MCSI-2_browse_installing_path into main 2025-07-10 22:38:48 +08:00
21 changed files with 221 additions and 194 deletions
Showing only changes of commit 20f902f0ad - Show all commits

View File

@ -0,0 +1,7 @@
import 'dart:typed_data';
import 'package:minecraft_server_installer/main/application/repository/installation_repository.dart';
abstract interface class InstallationApiService {
Future<Uint8List> fetchRemoteFile(Uri url, {DownloadProgressCallback? onProgressChanged});
}

View File

@ -0,0 +1,5 @@
import 'dart:typed_data';
abstract interface class InstallationFileStorage {
Future<void> saveFile(Uint8List fileBytes, String path);
}

View File

@ -0,0 +1,16 @@
import 'package:minecraft_server_installer/main/adapter/gateway/installation_api_service.dart';
import 'package:minecraft_server_installer/main/adapter/gateway/installation_file_storage.dart';
import 'package:minecraft_server_installer/main/application/repository/installation_repository.dart';
class InstallationRepositoryImpl implements InstallationRepository {
final InstallationApiService _apiService;
final InstallationFileStorage _fileStorage;
InstallationRepositoryImpl(this._apiService, this._fileStorage);
@override
Future<void> downloadServerFile(Uri url, String path, {DownloadProgressCallback? onProgressChanged}) async {
final fileBytes = await _apiService.fetchRemoteFile(url, onProgressChanged: onProgressChanged);
await _fileStorage.saveFile(fileBytes, path);
}
}

View File

@ -1,9 +1,46 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:minecraft_server_installer/main/adapter/presentation/installation_state.dart'; import 'package:minecraft_server_installer/main/adapter/presentation/installation_state.dart';
import 'package:minecraft_server_installer/main/adapter/presentation/progress_view_model.dart';
import 'package:minecraft_server_installer/main/application/use_case/download_file_use_case.dart';
import 'package:minecraft_server_installer/main/constants.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart'; import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart';
import 'package:path/path.dart' as path;
class InstallationBloc extends Bloc<InstallationEvent, InstallationState> { class InstallationBloc extends Bloc<InstallationEvent, InstallationState> {
InstallationBloc() : super(const InstallationState.empty()) { InstallationBloc(DownloadFileUseCase downloadFileUseCase) : super(const InstallationState.empty()) {
on<InstallationStartedEvent>((_, emit) async {
if (!state.canStartToInstall) {
return;
}
final gameVersion = state.gameVersion!;
final savePath = state.savePath!;
emit(state.copyWith(isLocked: true, downloadProgress: const ProgressViewModel.start()));
await downloadFileUseCase(
gameVersion.url,
path.join(savePath, Constants.serverFileName),
onProgressChanged: (progressValue) => add(_InstallationProgressValueChangedEvent(progressValue)),
);
emit(state.copyWith(isLocked: false, downloadProgress: const ProgressViewModel.complete()));
});
on<_InstallationProgressValueChangedEvent>((event, emit) {
ProgressViewModel newProgress;
if (event.progressValue < 0) {
newProgress = state.downloadProgress.copyWith(value: 0.0);
} else if (event.progressValue > 1) {
newProgress = state.downloadProgress.copyWith(value: 1.0);
} else {
newProgress = state.downloadProgress.copyWith(value: event.progressValue);
}
emit(state.copyWith(downloadProgress: newProgress));
});
on<InstallationConfigurationUpdatedEvent>((event, emit) { on<InstallationConfigurationUpdatedEvent>((event, emit) {
final newState = state.copyWith( final newState = state.copyWith(
gameVersion: event.gameVersion, gameVersion: event.gameVersion,
@ -16,6 +53,14 @@ class InstallationBloc extends Bloc<InstallationEvent, InstallationState> {
sealed class InstallationEvent {} sealed class InstallationEvent {}
class InstallationStartedEvent extends InstallationEvent {}
class _InstallationProgressValueChangedEvent extends InstallationEvent {
final double progressValue;
_InstallationProgressValueChangedEvent(this.progressValue);
}
class InstallationConfigurationUpdatedEvent extends InstallationEvent { class InstallationConfigurationUpdatedEvent extends InstallationEvent {
final GameVersionViewModel? gameVersion; final GameVersionViewModel? gameVersion;
final String? savePath; final String? savePath;

View File

@ -1,39 +1,52 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:minecraft_server_installer/main/adapter/presentation/progress_view_model.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart'; import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart';
class InstallationState with EquatableMixin { class InstallationState with EquatableMixin {
final GameVersionViewModel? gameVersion; final GameVersionViewModel? gameVersion;
final String? savePath; final String? savePath;
final ProgressViewModel downloadProgress;
final bool isLocked;
const InstallationState({ const InstallationState({
this.gameVersion, required this.gameVersion,
this.savePath, required this.savePath,
required this.downloadProgress,
required this.isLocked,
}); });
const InstallationState.empty() const InstallationState.empty()
: this( : this(
gameVersion: null, gameVersion: null,
savePath: null, savePath: null,
downloadProgress: const ProgressViewModel.zero(),
isLocked: false,
); );
@override @override
List<Object?> get props => [ List<Object?> get props => [
gameVersion, gameVersion,
savePath, savePath,
downloadProgress,
isLocked,
]; ];
InstallationState copyWith({ InstallationState copyWith({
GameVersionViewModel? gameVersion, GameVersionViewModel? gameVersion,
String? savePath, String? savePath,
ProgressViewModel? downloadProgress,
bool? isLocked,
}) => }) =>
InstallationState( InstallationState(
gameVersion: gameVersion ?? this.gameVersion, gameVersion: gameVersion ?? this.gameVersion,
savePath: savePath ?? this.savePath, savePath: savePath ?? this.savePath,
downloadProgress: downloadProgress ?? this.downloadProgress,
isLocked: isLocked ?? this.isLocked,
); );
bool get isGameVersionSelected => gameVersion != null; bool get isGameVersionSelected => gameVersion != null;
bool get isSavePathSelected => savePath != null && savePath!.isNotEmpty; bool get isSavePathSelected => savePath != null && savePath!.isNotEmpty;
bool get canStartToInstall => isGameVersionSelected && isSavePathSelected; bool get canStartToInstall => isGameVersionSelected && isSavePathSelected && !isLocked;
} }

View File

@ -0,0 +1,33 @@
import 'package:equatable/equatable.dart';
class ProgressViewModel with EquatableMixin {
/// The value should between 0.0 and 1.0.
final double value;
final bool isInProgress;
const ProgressViewModel({
required this.value,
required this.isInProgress,
});
const ProgressViewModel.zero() : this(value: 0.0, isInProgress: false);
const ProgressViewModel.start() : this(value: 0.0, isInProgress: true);
const ProgressViewModel.complete() : this(value: 1.0, isInProgress: false);
@override
List<Object?> get props => [
value,
isInProgress,
];
ProgressViewModel copyWith({
double? value,
bool? isInProgress,
}) =>
ProgressViewModel(
value: value ?? this.value,
isInProgress: isInProgress ?? this.isInProgress,
);
}

View File

@ -0,0 +1,5 @@
typedef DownloadProgressCallback = void Function(double progress);
abstract interface class InstallationRepository {
Future<void> downloadServerFile(Uri url, String path, {DownloadProgressCallback? onProgressChanged});
}

View File

@ -0,0 +1,10 @@
import 'package:minecraft_server_installer/main/application/repository/installation_repository.dart';
class DownloadFileUseCase {
final InstallationRepository _installationRepository;
DownloadFileUseCase(this._installationRepository);
Future<void> call(Uri url, String path, {DownloadProgressCallback? onProgressChanged}) =>
_installationRepository.downloadServerFile(url, path, onProgressChanged: onProgressChanged);
}

View File

@ -0,0 +1,40 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'package:minecraft_server_installer/main/adapter/gateway/installation_api_service.dart';
import 'package:minecraft_server_installer/main/application/repository/installation_repository.dart';
class InstallationApiServiceImpl implements InstallationApiService {
@override
Future<Uint8List> fetchRemoteFile(Uri url, {DownloadProgressCallback? onProgressChanged}) async {
final client = http.Client();
final request = http.Request('GET', url);
final response = await client.send(request);
final contentLength = response.contentLength;
final completer = Completer<Uint8List>();
final bytes = <int>[];
var receivedBytes = 0;
response.stream.listen(
(chunk) {
bytes.addAll(chunk);
receivedBytes += chunk.length;
if (onProgressChanged != null && contentLength != null) {
onProgressChanged(receivedBytes / contentLength);
}
},
onDone: () {
if (onProgressChanged != null) {
onProgressChanged(1);
}
completer.complete(Uint8List.fromList(bytes));
},
onError: completer.completeError,
cancelOnError: true,
);
return completer.future;
}
}

View File

@ -0,0 +1,18 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:minecraft_server_installer/main/adapter/gateway/installation_file_storage.dart';
class InstallationFileStorageImpl implements InstallationFileStorage {
@override
Future<void> saveFile(Uint8List fileBytes, String path) async {
final file = File(path);
if (!await file.parent.exists()) {
await file.parent.create(recursive: true);
}
await file.create();
await file.writeAsBytes(fileBytes, flush: true);
}
}

View File

@ -2,11 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:minecraft_server_installer/main/adapter/presentation/installation_bloc.dart'; import 'package:minecraft_server_installer/main/adapter/presentation/installation_bloc.dart';
import 'package:minecraft_server_installer/main/adapter/presentation/installation_state.dart';
import 'package:minecraft_server_installer/main/framework/ui/path_browsing_field.dart'; import 'package:minecraft_server_installer/main/framework/ui/path_browsing_field.dart';
import 'package:minecraft_server_installer/main/framework/ui/strings.dart'; import 'package:minecraft_server_installer/main/framework/ui/strings.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/vanilla_bloc.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart'; import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/vanilla_state.dart';
import 'package:minecraft_server_installer/vanilla/framework/ui/game_version_dropdown.dart'; import 'package:minecraft_server_installer/vanilla/framework/ui/game_version_dropdown.dart';
class BasicConfigurationTab extends StatefulWidget { class BasicConfigurationTab extends StatefulWidget {
@ -30,16 +29,17 @@ class _BasicConfigurationTabState extends State<BasicConfigurationTab> {
], ],
); );
Widget get _bottomControl => BlocConsumer<VanillaBloc, VanillaState>( Widget get _bottomControl => BlocConsumer<InstallationBloc, InstallationState>(
listener: (_, __) {}, listener: (_, __) {},
builder: (_, state) => Row( builder: (_, state) => Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
if (state.isDownloading) Expanded(child: LinearProgressIndicator(value: state.downloadProgress)), if (state.downloadProgress.isInProgress)
Expanded(child: LinearProgressIndicator(value: state.downloadProgress.value)),
const Gap(32), const Gap(32),
ElevatedButton.icon( ElevatedButton.icon(
style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))), style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))),
onPressed: context.watch<InstallationBloc>().state.isGameVersionSelected ? _downloadServerFile : null, onPressed: context.watch<InstallationBloc>().state.canStartToInstall ? _downloadServerFile : null,
icon: const Icon(Icons.download), icon: const Icon(Icons.download),
label: const Padding( label: const Padding(
padding: EdgeInsets.symmetric(vertical: 12), padding: EdgeInsets.symmetric(vertical: 12),
@ -51,7 +51,6 @@ class _BasicConfigurationTabState extends State<BasicConfigurationTab> {
); );
void _downloadServerFile() { void _downloadServerFile() {
final savePath = context.read<InstallationBloc>().state.savePath; context.read<InstallationBloc>().add((InstallationStartedEvent()));
context.read<VanillaBloc>().add(VanillaServerFileDownloadedEvent(savePath!));
} }
} }

View File

@ -1,14 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:minecraft_server_installer/main/adapter/gateway/installation_repository_impl.dart';
import 'package:minecraft_server_installer/main/adapter/presentation/installation_bloc.dart'; import 'package:minecraft_server_installer/main/adapter/presentation/installation_bloc.dart';
import 'package:minecraft_server_installer/main/adapter/presentation/installation_state.dart';
import 'package:minecraft_server_installer/main/application/use_case/download_file_use_case.dart';
import 'package:minecraft_server_installer/main/framework/api/installation_api_service_impl.dart';
import 'package:minecraft_server_installer/main/framework/storage/installation_file_storage_impl.dart';
import 'package:minecraft_server_installer/main/framework/ui/basic_configuration_tab.dart'; import 'package:minecraft_server_installer/main/framework/ui/basic_configuration_tab.dart';
import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_repository_impl.dart'; import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_repository_impl.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/vanilla_bloc.dart'; import 'package:minecraft_server_installer/vanilla/adapter/presentation/vanilla_bloc.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/vanilla_state.dart';
import 'package:minecraft_server_installer/vanilla/application/use_case/download_server_file_use_case.dart';
import 'package:minecraft_server_installer/vanilla/application/use_case/get_game_version_list_use_case.dart'; import 'package:minecraft_server_installer/vanilla/application/use_case/get_game_version_list_use_case.dart';
import 'package:minecraft_server_installer/vanilla/framework/api/vanilla_api_service_impl.dart'; import 'package:minecraft_server_installer/vanilla/framework/api/vanilla_api_service_impl.dart';
import 'package:minecraft_server_installer/vanilla/framework/storage/vanilla_file_storage_impl.dart';
class MinecraftServerInstaller extends StatelessWidget { class MinecraftServerInstaller extends StatelessWidget {
const MinecraftServerInstaller({super.key}); const MinecraftServerInstaller({super.key});
@ -18,27 +20,28 @@ class MinecraftServerInstaller extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final gameVersionApiService = VanillaApiServiceImpl(); final installationApiService = InstallationApiServiceImpl();
final gameVersionFileStorage = VanillaFileStorageImpl(); final installationFileStorage = InstallationFileStorageImpl();
final gameVersionRepository = VanillaRepositoryImpl(gameVersionApiService, gameVersionFileStorage); final installationRepository = InstallationRepositoryImpl(installationApiService, installationFileStorage);
final getGameVersionListUseCase = GetGameVersionListUseCase(gameVersionRepository);
final downloadServerFileUseCase = DownloadServerFileUseCase(gameVersionRepository);
final installationBloc = InstallationBloc(); final gameVersionApiService = VanillaApiServiceImpl();
final gameVersionRepository = VanillaRepositoryImpl(gameVersionApiService);
final downloadFileUseCase = DownloadFileUseCase(installationRepository);
final getGameVersionListUseCase = GetGameVersionListUseCase(gameVersionRepository);
return MaterialApp( return MaterialApp(
title: 'Minecraft Server Installer', title: 'Minecraft Server Installer',
theme: ThemeData.light().copyWith(colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue.shade900)), theme: ThemeData.light().copyWith(colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue.shade900)),
home: MultiBlocProvider( home: MultiBlocProvider(
providers: [ providers: [
BlocProvider.value(value: installationBloc), BlocProvider(create: (_) => InstallationBloc(downloadFileUseCase)),
BlocProvider<VanillaBloc>( BlocProvider<VanillaBloc>(
create: (_) => VanillaBloc(installationBloc, getGameVersionListUseCase, downloadServerFileUseCase) create: (_) => VanillaBloc(getGameVersionListUseCase)..add(VanillaGameVersionListLoadedEvent()),
..add(VanillaGameVersionListLoadedEvent()),
), ),
], ],
child: Scaffold( child: Scaffold(
body: BlocConsumer<VanillaBloc, VanillaState>( body: BlocConsumer<InstallationBloc, InstallationState>(
listener: (_, __) {}, listener: (_, __) {},
builder: (_, state) { builder: (_, state) {
if (state.isLocked) { if (state.isLocked) {

View File

@ -1,10 +1,5 @@
import 'dart:typed_data';
import 'package:minecraft_server_installer/vanilla/application/use_case/download_server_file_use_case.dart';
import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart'; import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart';
abstract interface class VanillaApiService { abstract interface class VanillaApiService {
Future<List<GameVersion>> fetchGameVersionList(); Future<List<GameVersion>> fetchGameVersionList();
Future<Uint8List> fetchServerFile(Uri url, {DownloadProgressCallback? onProgressChanged});
} }

View File

@ -1,5 +0,0 @@
import 'dart:typed_data';
abstract interface class VanillaFileStorage {
Future<void> saveFile(Uint8List fileBytes, String savePath);
}

View File

@ -1,28 +1,12 @@
import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_api_service.dart'; import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_api_service.dart';
import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_file_storage.dart';
import 'package:minecraft_server_installer/vanilla/application/repository/vanilla_repository.dart'; import 'package:minecraft_server_installer/vanilla/application/repository/vanilla_repository.dart';
import 'package:minecraft_server_installer/vanilla/application/use_case/download_server_file_use_case.dart';
import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart'; import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart';
class VanillaRepositoryImpl implements VanillaRepository { class VanillaRepositoryImpl implements VanillaRepository {
final VanillaApiService _gameVersionApiService; final VanillaApiService _gameVersionApiService;
final VanillaFileStorage _gameVersionFileStorage;
VanillaRepositoryImpl(this._gameVersionApiService, this._gameVersionFileStorage); VanillaRepositoryImpl(this._gameVersionApiService);
@override @override
Future<List<GameVersion>> getGameVersionList() => _gameVersionApiService.fetchGameVersionList(); Future<List<GameVersion>> getGameVersionList() => _gameVersionApiService.fetchGameVersionList();
@override
Future<void> downloadServerFile(
GameVersion version,
String savePath, {
DownloadProgressCallback? onProgressChanged,
}) async {
final fileBytes = await _gameVersionApiService.fetchServerFile(
version.url,
onProgressChanged: onProgressChanged,
);
await _gameVersionFileStorage.saveFile(fileBytes, savePath);
}
} }

View File

@ -1,25 +1,13 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:minecraft_server_installer/main/adapter/presentation/installation_bloc.dart';
import 'package:minecraft_server_installer/main/constants.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart'; import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/vanilla_state.dart'; import 'package:minecraft_server_installer/vanilla/adapter/presentation/vanilla_state.dart';
import 'package:minecraft_server_installer/vanilla/application/use_case/download_server_file_use_case.dart';
import 'package:minecraft_server_installer/vanilla/application/use_case/get_game_version_list_use_case.dart'; import 'package:minecraft_server_installer/vanilla/application/use_case/get_game_version_list_use_case.dart';
import 'package:path/path.dart' as path;
class VanillaBloc extends Bloc<VanillaEvent, VanillaState> { class VanillaBloc extends Bloc<VanillaEvent, VanillaState> {
final InstallationBloc _installationBloc; VanillaBloc(GetGameVersionListUseCase getGameVersionListUseCase) : super(const VanillaState.empty()) {
final GetGameVersionListUseCase _getGameVersionListUseCase;
final DownloadServerFileUseCase _downloadServerFileUseCase;
VanillaBloc(
this._installationBloc,
this._getGameVersionListUseCase,
this._downloadServerFileUseCase,
) : super(const VanillaState.empty()) {
on<VanillaGameVersionListLoadedEvent>((_, emit) async { on<VanillaGameVersionListLoadedEvent>((_, emit) async {
try { try {
final gameVersions = await _getGameVersionListUseCase(); final gameVersions = await getGameVersionListUseCase();
emit( emit(
const VanillaState.empty().copyWith( const VanillaState.empty().copyWith(
gameVersions: gameVersions.map((entity) => GameVersionViewModel.fromEntity(entity)).toList(), gameVersions: gameVersions.map((entity) => GameVersionViewModel.fromEntity(entity)).toList(),
@ -29,31 +17,6 @@ class VanillaBloc extends Bloc<VanillaEvent, VanillaState> {
emit(const VanillaState.empty()); emit(const VanillaState.empty());
} }
}); });
on<VanillaServerFileDownloadedEvent>((event, emit) async {
final gameVersion = _installationBloc.state.gameVersion;
if (gameVersion == null) {
return;
}
emit(state.copyWith(isLocked: true));
await _downloadServerFileUseCase(
gameVersion.toEntity(),
path.join(event.savePath, Constants.serverFileName),
onProgressChanged: (progress) => add(_VanillaDownloadProgressChangedEvent(progress)),
);
emit(state.copyWith(isLocked: false));
});
on<_VanillaDownloadProgressChangedEvent>((event, emit) {
if (event.progress < 0) {
emit(state.copyWith(downloadProgress: 0));
} else if (event.progress > 1) {
emit(state.copyWith(downloadProgress: 1));
} else {
emit(state.copyWith(downloadProgress: event.progress));
}
});
} }
} }
@ -66,15 +29,3 @@ class VanillaGameVersionSelectedEvent extends VanillaEvent {
VanillaGameVersionSelectedEvent(this.gameVersion); VanillaGameVersionSelectedEvent(this.gameVersion);
} }
class VanillaServerFileDownloadedEvent extends VanillaEvent {
final String savePath;
VanillaServerFileDownloadedEvent(this.savePath);
}
class _VanillaDownloadProgressChangedEvent extends VanillaEvent {
final double progress;
_VanillaDownloadProgressChangedEvent(this.progress);
}

View File

@ -2,32 +2,22 @@ import 'package:equatable/equatable.dart';
import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart'; import 'package:minecraft_server_installer/vanilla/adapter/presentation/game_version_view_model.dart';
class VanillaState with EquatableMixin { class VanillaState with EquatableMixin {
final bool isLocked;
final double downloadProgress;
final List<GameVersionViewModel> gameVersions; final List<GameVersionViewModel> gameVersions;
const VanillaState({ const VanillaState({
required this.isLocked,
required this.downloadProgress,
required this.gameVersions, required this.gameVersions,
}); });
const VanillaState.empty() const VanillaState.empty()
: this( : this(
isLocked: false,
downloadProgress: 0,
gameVersions: const [], gameVersions: const [],
); );
@override @override
List<Object?> get props => [ List<Object?> get props => [
isLocked,
downloadProgress,
gameVersions, gameVersions,
]; ];
bool get isDownloading => downloadProgress > 0 && downloadProgress < 1;
VanillaState copyWith({ VanillaState copyWith({
bool? isLocked, bool? isLocked,
double? downloadProgress, double? downloadProgress,
@ -35,8 +25,6 @@ class VanillaState with EquatableMixin {
GameVersionViewModel? selectedGameVersion, GameVersionViewModel? selectedGameVersion,
}) => }) =>
VanillaState( VanillaState(
isLocked: isLocked ?? this.isLocked,
downloadProgress: downloadProgress ?? this.downloadProgress,
gameVersions: gameVersions ?? this.gameVersions, gameVersions: gameVersions ?? this.gameVersions,
); );
} }

View File

@ -1,12 +1,5 @@
import 'package:minecraft_server_installer/vanilla/application/use_case/download_server_file_use_case.dart';
import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart'; import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart';
abstract interface class VanillaRepository { abstract interface class VanillaRepository {
Future<List<GameVersion>> getGameVersionList(); Future<List<GameVersion>> getGameVersionList();
Future<void> downloadServerFile(
GameVersion version,
String savePath, {
DownloadProgressCallback? onProgressChanged,
});
} }

View File

@ -1,21 +0,0 @@
import 'package:minecraft_server_installer/vanilla/application/repository/vanilla_repository.dart';
import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart';
typedef DownloadProgressCallback = void Function(double progress);
class DownloadServerFileUseCase {
final VanillaRepository _gameVersionRepository;
DownloadServerFileUseCase(this._gameVersionRepository);
Future<void> call(
GameVersion version,
String savePath, {
DownloadProgressCallback? onProgressChanged,
}) =>
_gameVersionRepository.downloadServerFile(
version,
savePath,
onProgressChanged: onProgressChanged,
);
}

View File

@ -1,10 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:minecraft_server_installer/main/constants.dart'; import 'package:minecraft_server_installer/main/constants.dart';
import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_api_service.dart'; import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_api_service.dart';
import 'package:minecraft_server_installer/vanilla/application/use_case/download_server_file_use_case.dart';
import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart'; import 'package:minecraft_server_installer/vanilla/domain/entity/game_version.dart';
class VanillaApiServiceImpl implements VanillaApiService { class VanillaApiServiceImpl implements VanillaApiService {
@ -22,36 +20,4 @@ class VanillaApiServiceImpl implements VanillaApiService {
return gameVersionList; return gameVersionList;
} }
@override
Future<Uint8List> fetchServerFile(Uri url, {DownloadProgressCallback? onProgressChanged}) async {
final client = http.Client();
final request = http.Request('GET', url);
final response = await client.send(request);
final contentLength = response.contentLength;
final completer = Completer<Uint8List>();
final bytes = <int>[];
var receivedBytes = 0;
response.stream.listen(
(chunk) {
bytes.addAll(chunk);
receivedBytes += chunk.length;
if (onProgressChanged != null && contentLength != null) {
onProgressChanged(receivedBytes / contentLength);
}
},
onDone: () {
if (onProgressChanged != null) {
onProgressChanged(1);
}
completer.complete(Uint8List.fromList(bytes));
},
onError: completer.completeError,
cancelOnError: true,
);
return completer.future;
}
} }

View File

@ -1,18 +0,0 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:minecraft_server_installer/vanilla/adapter/gateway/vanilla_file_storage.dart';
class VanillaFileStorageImpl implements VanillaFileStorage {
@override
Future<void> saveFile(Uint8List fileBytes, String savePath) async {
final file = File(savePath);
if (!await file.parent.exists()) {
await file.parent.create(recursive: true);
}
await file.create();
await file.writeAsBytes(fileBytes, flush: true);
}
}