diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 2a1d193..f0a530b 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,17 +1,11 @@ import 'dart:convert'; -import 'dart:io'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; +import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:json_schema/json_schema.dart'; -import 'package:provider/provider.dart'; class SettingsView extends StatefulWidget { const SettingsView({super.key}); @@ -19,14 +13,6 @@ class SettingsView extends StatefulWidget { @override State createState() => _SettingsViewState(); - /// Helper method to read file content from either bytes or path - static Future _readFileContent(PlatformFile file) async { - if (file.bytes != null) return utf8.decode(file.bytes!); - if (file.path != null) return await File(file.path!).readAsString(); - - throw Exception('Die Datei hat keinen lesbaren Inhalt'); - } - static Future validateJsonSchema(String jsonString) async { final String schemaString; @@ -84,33 +70,30 @@ class _SettingsViewState extends State { ), ), ), - SettingsListTile( title: 'Export data', icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { - final String json = await _getAppDataAsJson(context); - await exportData(json, 'export'); + final String json = + await DataTransferService.getAppDataAsJson(context); + await DataTransferService.exportData( + json, + 'exported_data', + ); }, ), SettingsListTile( title: 'Import data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => importData(context), + onPressed: () => DataTransferService.importData(context), ), SettingsListTile( title: 'Delete all data', icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => deleteAllData(context), - ), - SettingsListTile( - title: 'Add Sample Data', - icon: Icons.upload_outlined, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () => addSampleData(context), + onPressed: () => DataTransferService.deleteAllData(context), ), ], ), @@ -118,129 +101,4 @@ class _SettingsViewState extends State { ), ); } - - Future deleteAllData(BuildContext context) async { - final db = Provider.of(context, listen: false); - await db.gameDao.deleteAllGames(); - await db.groupDao.deleteAllGroups(); - await db.playerDao.deleteAllPlayers(); - print('[deleteAllData] All data deleted'); - } - - Future addSampleData(BuildContext context) async { - final db = Provider.of(context, listen: false); - - final player1 = Player(name: 'Alice'); - final player2 = Player(name: 'Bob'); - final group = Group(name: 'Friends', members: [player1, player2]); - final game = Game(name: 'Sample Game', group: group, winner: 'Alice'); - - await db.playerDao.addPlayer(player: player1); - await db.playerDao.addPlayer(player: player2); - await db.groupDao.addGroup(group: group); - await db.gameDao.addGame(game: game); - } - - Future _getAppDataAsJson(BuildContext context) async { - final db = Provider.of(context, listen: false); - final games = await db.gameDao.getAllGames(); - final groups = await db.groupDao.getAllGroups(); - final players = await db.playerDao.getAllPlayers(); - - // Construct a JSON representation of the data - final Map jsonMap = { - 'games': games.map((game) => game.toJson()).toList(), - 'groups': groups.map((group) => group.toJson()).toList(), - 'players': players.map((player) => player.toJson()).toList(), - }; - - return json.encode(jsonMap); - } - - Future exportData(String jsonString, String fileName) async { - try { - final bytes = Uint8List.fromList(utf8.encode(jsonString)); - await FilePicker.platform.saveFile( - fileName: '$fileName.json', - bytes: bytes, - ); - return true; - } catch (e, stack) { - print('[exportData] $e'); - print(stack); - return false; - } - } - - Future importData(BuildContext context) async { - final db = Provider.of(context, listen: false); - - final path = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['json'], - ); - - if (path == null) { - print('[importData] No file selected'); - return; - } - - try { - final jsonString = await SettingsView._readFileContent(path.files.single); - - // Checks if the JSON String is in the gameList format - if (await SettingsView.validateJsonSchema(jsonString)) { - final Map jsonData = - json.decode(jsonString) as Map; - print('[importData] : $jsonData'); - final List? gamesJson = jsonData['games'] as List?; - final List? groupsJson = jsonData['groups'] as List?; - final List? playersJson = - jsonData['players'] as List?; - - final List importedGames = - gamesJson - ?.map((g) => Game.fromJson(g as Map)) - .toList() ?? - []; - final List importedGroups = - groupsJson - ?.map((g) => Group.fromJson(g as Map)) - .toList() ?? - []; - final List importedPlayers = - playersJson - ?.map((p) => Player.fromJson(p as Map)) - .toList() ?? - []; - - for (Player player in importedPlayers) { - await db.playerDao.addPlayer(player: player); - } - - for (Group group in importedGroups) { - await db.groupDao.addGroup(group: group); - } - - for (Game game in importedGames) { - await db.gameDao.addGame(game: game); - } - } else { - print('[importData] Invalid JSON schema'); - return; - } - print('[importData] Data imported successfully'); - return; - } on FormatException catch (e, stack) { - print('[importData] FormatException'); - print('[importData] $e'); - print(stack); - return; - } on Exception catch (e, stack) { - print('[importData] Exception'); - print('[importData] $e'); - print(stack); - return; - } - } } diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart new file mode 100644 index 0000000..839c0e0 --- /dev/null +++ b/lib/services/data_transfer_service.dart @@ -0,0 +1,135 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; +import 'package:provider/provider.dart'; + +class DataTransferService { + /// Deletes all data from the database. + static Future deleteAllData(BuildContext context) async { + final db = Provider.of(context, listen: false); + await db.gameDao.deleteAllGames(); + await db.groupDao.deleteAllGroups(); + await db.playerDao.deleteAllPlayers(); + print('[deleteAllData] All data deleted'); + } + + static Future getAppDataAsJson(BuildContext context) async { + final db = Provider.of(context, listen: false); + final games = await db.gameDao.getAllGames(); + final groups = await db.groupDao.getAllGroups(); + final players = await db.playerDao.getAllPlayers(); + + // Construct a JSON representation of the data + final Map jsonMap = { + 'games': games.map((game) => game.toJson()).toList(), + 'groups': groups.map((group) => group.toJson()).toList(), + 'players': players.map((player) => player.toJson()).toList(), + }; + + return json.encode(jsonMap); + } + + /// Exports the given JSON string to a file with the specified name. + static Future exportData(String jsonString, String fileName) async { + try { + final bytes = Uint8List.fromList(utf8.encode(jsonString)); + await FilePicker.platform.saveFile( + fileName: '$fileName.json', + bytes: bytes, + ); + return true; + } catch (e, stack) { + print('[exportData] $e'); + print(stack); + return false; + } + } + + /// Imports data from a selected JSON file into the database. + static Future importData(BuildContext context) async { + final db = Provider.of(context, listen: false); + + final path = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['json'], + ); + + if (path == null) { + print('[importData] No file selected'); + return; + } + + try { + final jsonString = await _readFileContent(path.files.single); + + if (await SettingsView.validateJsonSchema(jsonString)) { + final Map jsonData = + json.decode(jsonString) as Map; + + final List? gamesJson = jsonData['games'] as List?; + final List? groupsJson = jsonData['groups'] as List?; + final List? playersJson = + jsonData['players'] as List?; + + final List importedGames = + gamesJson + ?.map((g) => Game.fromJson(g as Map)) + .toList() ?? + []; + final List importedGroups = + groupsJson + ?.map((g) => Group.fromJson(g as Map)) + .toList() ?? + []; + final List importedPlayers = + playersJson + ?.map((p) => Player.fromJson(p as Map)) + .toList() ?? + []; + + for (Player player in importedPlayers) { + await db.playerDao.addPlayer(player: player); + } + + for (Group group in importedGroups) { + await db.groupDao.addGroup(group: group); + } + + for (Game game in importedGames) { + await db.gameDao.addGame(game: game); + } + } else { + print('[importData] Invalid JSON schema'); + return; + } + print('[importData] Data imported successfully'); + return; + } on FormatException catch (e, stack) { + print('[importData] FormatException'); + print('[importData] $e'); + print(stack); + return; + } on Exception catch (e, stack) { + print('[importData] Exception'); + print('[importData] $e'); + print(stack); + return; + } + } + + /// Helper method to read file content from either bytes or path + static Future _readFileContent(PlatformFile file) async { + if (file.bytes != null) return utf8.decode(file.bytes!); + if (file.path != null) return await File(file.path!).readAsString(); + + throw Exception('Die Datei hat keinen lesbaren Inhalt'); + } +}