From 9207c4789d2f0a22703ecffb6bce9dc6285b5a92 Mon Sep 17 00:00:00 2001 From: Jacob Moura Date: Mon, 22 Jan 2024 18:53:53 -0300 Subject: [PATCH] update retroarch core selector --- .../(public)/config/edit_platform_page.dart | 1 + .../config/widgets/player_select.dart | 19 +-- lib/app/core/widgets/searchable_dropdown.dart | 161 ++++++++++++++++++ .../interactor/actions/platform_action.dart | 24 +++ pubspec.lock | 2 +- pubspec.yaml | 2 +- .../interactor/actions/platform_action.dart | 37 ++++ 7 files changed, 231 insertions(+), 15 deletions(-) create mode 100644 lib/app/core/widgets/searchable_dropdown.dart diff --git a/lib/app/(public)/config/edit_platform_page.dart b/lib/app/(public)/config/edit_platform_page.dart index 28e1521..3e12211 100644 --- a/lib/app/(public)/config/edit_platform_page.dart +++ b/lib/app/(public)/config/edit_platform_page.dart @@ -169,6 +169,7 @@ class _EditPlatformPageState extends State { decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Folder', + suffixIcon: Icon(Icons.folder), ), initialValue: beautifyPath(platform.folder), readOnly: true, diff --git a/lib/app/(public)/config/widgets/player_select.dart b/lib/app/(public)/config/widgets/player_select.dart index 2358331..be6239a 100644 --- a/lib/app/(public)/config/widgets/player_select.dart +++ b/lib/app/(public)/config/widgets/player_select.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:yuno/app/core/widgets/searchable_dropdown.dart'; import 'package:yuno/app/interactor/models/embeds/player.dart'; import '../../../core/constants/retroarch_cores.dart'; @@ -113,23 +114,15 @@ class PlayerSelect extends StatelessWidget { children: [ SizedBox( width: 300, - child: DropdownButtonFormField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Retroarch Core', - ), - value: player?.extra, - onChanged: (String? newValue) { + child: SearchableDropdown( + onSearchTextChanged: (newValue) { if (newValue != null) { onChanged(player?.copyWith(extra: newValue)); } }, - items: retroarchCores.map((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), + label: 'Retroarch Core', + item: player?.extra, + items: retroarchCores, ), ), IconButton( diff --git a/lib/app/core/widgets/searchable_dropdown.dart b/lib/app/core/widgets/searchable_dropdown.dart new file mode 100644 index 0000000..378c165 --- /dev/null +++ b/lib/app/core/widgets/searchable_dropdown.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:routefly/routefly.dart'; + +class SearchableDropdown extends StatefulWidget { + final void Function(String? searchText) onSearchTextChanged; + final List items; + final String? label; + final String? item; + const SearchableDropdown({ + super.key, + this.item, + this.label, + required this.onSearchTextChanged, + required this.items, + }); + + @override + State createState() => _SearchableDropdownState(); +} + +class _SearchableDropdownState extends State { + final _searchController = TextEditingController(); + + @override + void initState() { + super.initState(); + _searchController.text = widget.item ?? ''; + } + + @override + void didUpdateWidget(covariant SearchableDropdown oldWidget) { + super.didUpdateWidget(oldWidget); + _searchController.text = widget.item ?? ''; + } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return TextField( + controller: _searchController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: widget.label, + suffixIcon: const Icon(Icons.arrow_drop_down), + ), + readOnly: true, + onTap: () async { + final item = await Navigator.push( + context, + PageRouteBuilder( + barrierDismissible: true, + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) { + return Center( + child: SizedBox( + width: 370, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: kToolbarHeight, + ), + child: _SeachItems( + items: widget.items, + label: widget.label, + ), + ), + ), + ); + }, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + ), + ); + + if(item is String){ + widget.onSearchTextChanged(item); + _searchController.text = item; + } + + }, + ); + } +} + +class _SeachItems extends StatefulWidget { + final List items; + final String? label; + + const _SeachItems({super.key, required this.items, this.label}); + + @override + State<_SeachItems> createState() => _SeachItemsState(); +} + +class _SeachItemsState extends State<_SeachItems> { + var filteredItems = []; + @override + void initState() { + super.initState(); + filteredItems = widget.items; + } + + void filter(String? value) { + if (value == null || value.isEmpty) { + setState(() { + filteredItems = widget.items; + }); + return; + } + + setState(() { + filteredItems = widget.items + .where((item) => item.toLowerCase().contains(value.toLowerCase())) + .toList(); + }); + } + + @override + Widget build(BuildContext context) { + return Material( + elevation: 17, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: widget.label, + ), + onChanged: filter, + ), + Expanded( + child: Scrollbar( + thumbVisibility: true, + child: ListView.builder( + itemCount: filteredItems.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(filteredItems[index]), + onTap: () { + Navigator.pop(context, filteredItems[index]); + }, + ); + }, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/app/interactor/actions/platform_action.dart b/lib/app/interactor/actions/platform_action.dart index 327f4f1..9bc0418 100644 --- a/lib/app/interactor/actions/platform_action.dart +++ b/lib/app/interactor/actions/platform_action.dart @@ -73,6 +73,12 @@ Future syncPlatform(PlatformModel platform) async { final repository = injector(); + final folderGames = await _getGames(platform); + final currentGames = platform.games; + final games = syncGames(currentGames, folderGames); + + platform = platform.copyWith(games: games); + for (var i = 0; i < platform.games.length; i++) { if (platform.games[i].isSynced) continue; if (platform.games[i].image.isNotEmpty) { @@ -95,6 +101,24 @@ Future syncPlatform(PlatformModel platform) async { platformSyncState(); } +List syncGames(List currentGames, List folderGames) { + final games = []; + + for (var i = 0; i < folderGames.length; i++) { + final folderGame = folderGames[i]; + final currentGame = currentGames.firstWhere( + (game) => game.path == folderGame.path, + orElse: () => folderGame, + ); + games.add(currentGame); + } + + games.removeWhere((game) { + return folderGames.every((folderGame) => folderGame.path != game.path); + }); + return games; +} + Future getDominatingColor(String imagePath) async { final imageFile = File(imagePath); if (!imageFile.existsSync()) { diff --git a/pubspec.lock b/pubspec.lock index 9e5d58c..383fed7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -818,7 +818,7 @@ packages: routefly: dependency: "direct main" description: - path: "D:\\Projects\\routefly" + path: "/Users/jacob/Projects/routefly" relative: false source: path version: "1.0.8" diff --git a/pubspec.yaml b/pubspec.yaml index bff9fc9..1552efb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.0.3+11 +version: 0.0.5+13 environment: sdk: '>=3.2.4 <4.0.0' diff --git a/test/app/interactor/actions/platform_action.dart b/test/app/interactor/actions/platform_action.dart index 5928e68..b5f3e85 100644 --- a/test/app/interactor/actions/platform_action.dart +++ b/test/app/interactor/actions/platform_action.dart @@ -1,5 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:yuno/app/interactor/actions/platform_action.dart'; +import 'package:yuno/app/interactor/models/embeds/game.dart'; void main() { test('Uri to path', () { @@ -14,4 +15,40 @@ void main() { final megaman = cleanName('Mega Man Zero 3.zip'); expect(megaman, 'Mega Man Zero 3'); }); + + test('Sync games add', () { + final currentGames = [ + Game(name: 'game 1', description: 'aaa', image: '', path: '/path1',), + Game(name: 'game 2', description: 'aaa', image: '', path: '/path2',), + ]; + final folderGames = [ + Game(name: 'game 1', description: '', image: '', path: '/path1',), + Game(name: 'game 2', description: '', image: '', path: '/path2',), + Game(name: 'game 3', description: '', image: '', path: '/path3',), + ]; + + final games = syncGames(currentGames, folderGames); + expect(games.length, 3); + expect(games[0].description, 'aaa'); + expect(games[1].description, 'aaa'); + + }); + + test('Sync games remove', () { + final currentGames = [ + Game(name: 'game 1', description: 'aaa', image: '', path: '/path1',), + Game(name: 'game 2', description: 'aaa', image: '', path: '/path2',), + ]; + final folderGames = [ + Game(name: 'game 1', description: '', image: '', path: '/path1',), + Game(name: 'game 3', description: '', image: '', path: '/path3',), + ]; + + final games = syncGames(currentGames, folderGames); + expect(games.length, 2); + expect(games[0].description, 'aaa'); + expect(games[1].description, ''); + expect(games[1].name, 'game 3'); + + }); }