diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 746e8d8..afc9dc8 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,14 +1,23 @@ - + + + + + + + + android:name="android.permission.WRITE_EXTERNAL_STORAGE" + android:maxSdkVersion="29" /> + { initialValue: beautifyPath(platform.folder), readOnly: true, onTap: () async { - final String? selectedDirectory = - await getDirectoryPath(); + final selectedDirectory = await getDirectoryPath(); if (selectedDirectory != null) { setState(() { platform = platform.copyWith( - folder: Directory.fromUri( - Uri.directory(selectedDirectory)), + folder:selectedDirectory, ); }); } @@ -326,7 +324,12 @@ class _EditPlatformPageState extends State { if (platform.id != -1) FloatingActionButton( heroTag: 'delete', - onPressed: () {}, + onPressed: () { + deletePlatform(platform); + if (context.mounted) { + Routefly.pop(context); + } + }, child: const Icon(Icons.delete), ), if (platform.id != -1) const Gap(17), @@ -338,9 +341,9 @@ class _EditPlatformPageState extends State { } else { createPlatform(platform); } - // if (context.mounted) { - // Routefly.pop(context); - // } + if (context.mounted) { + Routefly.pop(context); + } }, child: const Icon(Icons.save), ), @@ -349,8 +352,8 @@ class _EditPlatformPageState extends State { ); } - String beautifyPath(Directory dir) { - final path = convertContentUriToFilePath(dir.path); + String beautifyPath(String dir) { + final path = convertContentUriToFilePath(dir); return path.replaceAll('/storage/emulated/0', ''); } } diff --git a/lib/app/(public)/home_page.dart b/lib/app/(public)/home_page.dart index 227aef2..b4decd7 100644 --- a/lib/app/(public)/home_page.dart +++ b/lib/app/(public)/home_page.dart @@ -375,11 +375,8 @@ class _HomePageState extends State { transitionAnimation: widget.transitionAnimation, selected: selectedItemIndex == index, onTap: () { - if (index == selectedItemIndex) { - openGame(); - } else { - handlerSelect(index); - } + handlerSelect(index); + openGame(); }, index: index, gamesLength: games.length, diff --git a/lib/app/data/repositories/apps/android_apps_repository.dart b/lib/app/data/repositories/apps/android_apps_repository.dart index d181add..077874f 100644 --- a/lib/app/data/repositories/apps/android_apps_repository.dart +++ b/lib/app/data/repositories/apps/android_apps_repository.dart @@ -46,6 +46,8 @@ class AndroidAppsRepository implements AppsRepository { action: intent.action, package: intent.package, componentName: intent.componentName, + data: intent.data, + type: intent.type, arguments: intent.arguments, flags: [ flag.Flag.FLAG_ACTIVITY_NEW_TASK, diff --git a/lib/app/data/repositories/isar/adapters/platform_adapter.dart b/lib/app/data/repositories/isar/adapters/platform_adapter.dart index 64c1fe1..5b31713 100644 --- a/lib/app/data/repositories/isar/adapters/platform_adapter.dart +++ b/lib/app/data/repositories/isar/adapters/platform_adapter.dart @@ -20,7 +20,7 @@ abstract class PlatformAdapter { data.category = model.category.id; data.games = model.games.map((e) => gameFromModel(e)).toList(); - data.folder = model.folder.path; + data.folder = model.folder; data.lastUpdate = DateTime.now(); data.playerPackageId = model.player?.app.package; data.playerExtra = model.player?.extra; @@ -45,7 +45,7 @@ abstract class PlatformAdapter { static PlatformModel platformFromData(PlatformData model) { return PlatformModel( id: model.id, - folder: Directory.fromUri(Uri.directory(model.folder)), + folder: model.folder, lastUpdate: model.lastUpdate, category: categorieState.firstWhere((e) => e.id == model.category), player: model.playerPackageId == null diff --git a/lib/app/interactor/actions/platform_action.dart b/lib/app/interactor/actions/platform_action.dart index 39bc97c..2589379 100644 --- a/lib/app/interactor/actions/platform_action.dart +++ b/lib/app/interactor/actions/platform_action.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:media_store_plus/media_store_plus.dart'; import 'package:yuno/app/interactor/models/platform_model.dart'; import 'package:yuno/app/interactor/repositories/platform_repository.dart'; import 'package:yuno/injector.dart'; @@ -55,22 +56,29 @@ Future> _getGames(PlatformModel platform) async { return platform.games; } + + final games = []; - final directory = Directory(convertContentUriToFilePath(platform.folder.path)); - final files = directory // - .listSync() - .whereType() - .where(platform.category.checkFileExtension) + final media = MediaStore(); + + final documents = await media.getDocumentTree(uriString: platform.folder); + + if(documents == null) { + return []; + } + + + final files = documents // + .children + .where((doc){ + return platform.category.checkFileExtension(doc.name ?? ''); + }) .toList(); for (var file in files) { - final name = file.path.split('/').last; games.add(Game( - name: name, - path: addFileInUri( - platform.folder.path, - name, - ), + name: file.name ?? '', + path: file.uriString ?? '', description: '', image: '', )); @@ -79,6 +87,11 @@ Future> _getGames(PlatformModel platform) async { return games; } +String cleanName(String name) { + final index = name.indexOf(RegExp(r'[.(\[]')) - 1; + return name.substring(0 , index <= 0 ? name.length : index).trim(); +} + Future updatePlatform(PlatformModel platform) async { final repository = injector(); await repository.updatePlatform(platform); @@ -93,10 +106,6 @@ Future deletePlatform(PlatformModel platform) async { Future syncPlatform(PlatformModel platform) async {} -String addFileInUri(String path, String file) { - String encoded = Uri.encodeComponent('/$file'); - return '$path$encoded'; -} String convertContentUriToFilePath(String contentUri) { Uri uri = Uri.parse(contentUri); diff --git a/lib/app/interactor/actions/player_action.dart b/lib/app/interactor/actions/player_action.dart index c7f3c09..57c779e 100644 --- a/lib/app/interactor/actions/player_action.dart +++ b/lib/app/interactor/actions/player_action.dart @@ -1,46 +1,61 @@ import 'package:yuno/app/interactor/models/embeds/player.dart'; import '../models/embeds/game.dart'; +import 'platform_action.dart'; + +typedef IntentFunction = PlayerIntent Function(Player, Game); PlayerIntent? getAppIntent(Game game, Player player) { - return _defaultAppIntent[player.app.package]?.parse(player, game); + return _defaultAppIntent[player.app.package]?.call(player, game); } -final _defaultAppIntent = { - 'xyz.aethersx2.android': PlayerIntent( - action: 'android.intent.action.VIEW', - package: 'xyz.aethersx2.android', - componentName: 'xyz.aethersx2.android.EmulationActivity', - arguments: { - 'bootPath': r'${fileGame}', - }, - ), - 'com.retroarch.aarch64': PlayerIntent( - action: 'android.intent.action.MAIN', - package: 'com.retroarch.aarch64', - componentName: 'com.retroarch.browser.retroactivity.RetroActivityFuture', - arguments: { - 'ROM': r'${fileGame}', - 'LIBRETRO': - r'/data/data/com.retroarch.aarch64/cores/${extra}_libretro_android.so', - }, - ), - 'com.yuzu.android': PlayerIntent( - action: 'android.nfc.action.TECH_DISCOVERED', - package: 'com.yuzu.android', - componentName: 'com.yuzu.android.EmulationActivity', - data: r'${fileGame}', - ), - 'org.yuzu.yuzu_emu_ea': PlayerIntent( - action: 'android.nfc.action.TECH_DISCOVERED', - package: 'org.yuzu.yuzu_emu_ea', - componentName: 'com.yuzu.android.EmulationActivity', - data: r'${fileGame}', - ), - 'switch.skyline.emu': PlayerIntent( - action: 'android.intent.action.VIEW', - package: 'switch.skyline.emu', - componentName: 'switch.skyline.emu.EmulationActivity', - data: r'${fileGame}', - ), +final _defaultAppIntent = { + 'xyz.aethersx2.android': (p, g) { + return PlayerIntent( + action: 'android.intent.action.VIEW', + package: 'xyz.aethersx2.android', + componentName: 'xyz.aethersx2.android.EmulationActivity', + arguments: { + 'bootPath': g.path, + }, + ); + }, + 'com.retroarch.aarch64': (p, g) { + return PlayerIntent( + action: 'android.intent.action.MAIN', + package: 'com.retroarch.aarch64', + componentName: 'com.retroarch.browser.retroactivity.RetroActivityFuture', + arguments: { + 'ROM': convertContentUriToFilePath(g.path), + 'LIBRETRO': + '/data/data/com.retroarch.aarch64/cores/${p.extra}_libretro_android.so', + }, + ); + }, + 'org.yuzu.yuzu_emu': (p, g) { + return PlayerIntent( + action: 'android.nfc.action.TECH_DISCOVERED', + package: 'org.yuzu.yuzu_emu', + type: 'application/octet-stream', + componentName: 'org.yuzu.yuzu_emu.activities.EmulationActivity', + data: g.path, + ); + }, + 'org.yuzu.yuzu_emu.ea': (p, g) { + return PlayerIntent( + action: 'android.nfc.action.TECH_DISCOVERED', + package: 'org.yuzu.yuzu_emu.ea', + type: 'application/octet-stream', + componentName: 'org.yuzu.yuzu_emu.activities.EmulationActivity', + data: g.path, + ); + }, + 'switch.skyline.emu': (p, g) { + return PlayerIntent( + action: 'android.intent.action.VIEW', + package: 'switch.skyline.emu', + componentName: 'switch.skyline.emu.EmulationActivity', + data: g.path, + ); + }, }; diff --git a/lib/app/interactor/atoms/game_atom.dart b/lib/app/interactor/atoms/game_atom.dart index 6be57c1..7e79b84 100644 --- a/lib/app/interactor/atoms/game_atom.dart +++ b/lib/app/interactor/atoms/game_atom.dart @@ -60,6 +60,7 @@ List get categoriesFoSelectState { final categorieState = [ GameCategory(name: 'Android', image: img.androidSVG, id: 'android'), GameCategory(name: 'Nintendo Switch', image: img.switchSVG, id: 'switch'), + GameCategory(name: 'Super Nintendo', image: img.switchSVG, id: 'snes'), GameCategory(name: 'Playstation 1', image: img.ps1SVG, id: 'ps1'), GameCategory(name: 'Playstation 2', image: img.ps2SVG, id: 'ps2'), GameCategory(name: 'Playstation Portable', image: img.pspSVG, id: 'psp'), diff --git a/lib/app/interactor/models/embeds/game_category.dart b/lib/app/interactor/models/embeds/game_category.dart index 2f2717e..787eb43 100644 --- a/lib/app/interactor/models/embeds/game_category.dart +++ b/lib/app/interactor/models/embeds/game_category.dart @@ -15,7 +15,7 @@ class GameCategory { this.extensions = const [], }); - bool checkFileExtension(File element) => true; + bool checkFileExtension(String name) => true; @override bool operator ==(covariant GameCategory other) { diff --git a/lib/app/interactor/models/embeds/player.dart b/lib/app/interactor/models/embeds/player.dart index 9a7b128..e7b9ebc 100644 --- a/lib/app/interactor/models/embeds/player.dart +++ b/lib/app/interactor/models/embeds/player.dart @@ -47,6 +47,7 @@ class PlayerIntent { final String package; final String? componentName; final String? data; + final String? type; final Map? arguments; PlayerIntent({ @@ -55,23 +56,37 @@ class PlayerIntent { this.componentName, this.data, this.arguments, + this.type, }); - PlayerIntent parse(Player player, Game game) { + PlayerIntent parse({String? fileGameUri, String? fileGamePath, String? extra,}) { if (arguments == null) { return this; } - var newArguments = replaceVariables(arguments, 'fileGame', game.path); - newArguments = replaceVariables( - newArguments, - 'extra', - player.extra ?? '', - ); + var newArguments = arguments; + var newData = data; + + + if(fileGameUri != null){ + newArguments = replaceMap(newArguments, 'fileGameUri', fileGameUri); + newData = replaceVariable(newData, 'fileGameUri', fileGameUri); + newData = replaceVariable(newData, 'fileGame', fileGameUri); + } + + if(fileGamePath != null){ + newArguments = replaceMap(newArguments, 'fileGamePath', fileGamePath); + } + + if(extra != null){ + newArguments = replaceMap(newArguments, 'extra', extra); + } + return PlayerIntent( action: action, package: package, componentName: componentName, - data: replaceVariable(data, 'fileGame', game.path), + type: type, + data: newData, arguments: newArguments, ); } @@ -84,7 +99,7 @@ class PlayerIntent { return value.replaceAll(regex, replaceValue); } - Map replaceVariables( + Map replaceMap( Map? map, String variable, String replaceValue) { if (map == null) { return {}; diff --git a/lib/app/interactor/models/platform_model.dart b/lib/app/interactor/models/platform_model.dart index 15f63af..95157f2 100644 --- a/lib/app/interactor/models/platform_model.dart +++ b/lib/app/interactor/models/platform_model.dart @@ -9,7 +9,7 @@ class PlatformModel { final int id; final GameCategory category; final Player? player; - final Directory folder; + final String folder; final List games; final DateTime lastUpdate; @@ -26,7 +26,7 @@ class PlatformModel { static PlatformModel defaultInstance() { return PlatformModel( id: -1, - folder: Directory(''), + folder:'', lastUpdate: DateTime.now(), games: [], category: GameCategory(name: '', image: '', id: ''), @@ -36,7 +36,7 @@ class PlatformModel { PlatformModel copyWith( {Player? player, String? name, - Directory? folder, + String? folder, DateTime? lastUpdate, GameCategory? category, String? playerArguments, diff --git a/pubspec.lock b/pubspec.lock index c624896..46fa92e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -301,10 +301,10 @@ packages: dependency: transitive description: name: file_selector_ios - sha256: "2f48db7e338b2255101c35c604b7ca5ab588dce032db7fc418a2fe5f28da63f8" + sha256: b015154e6d9fddbc4d08916794df170b44531798c8dd709a026df162d07ad81d url: "https://pub.dev" source: hosted - version: "0.5.1+7" + version: "0.5.1+8" file_selector_linux: dependency: transitive description: @@ -576,6 +576,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + media_store_plus: + dependency: "direct main" + description: + name: media_store_plus + sha256: "8ef2d1db47a6986c15d9d20bfb3689891bd0f8648d2da65da0343ac574dd071d" + url: "https://pub.dev" + source: hosted + version: "0.0.7" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a44dee2..8f0ff5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: installed_apps: ^1.4.0 permission_handler: ^11.1.0 path_provider: ^2.1.2 + media_store_plus: ^0.0.7 file_selector: ^1.0.2 android_intent_plus: ^4.0.3 soundpool: ^2.4.1