-
-
Notifications
You must be signed in to change notification settings - Fork 14
commit 3f0775a
abduznik edited this page May 23, 2026
·
1 revision
Commit: 3f0775a3f4bb213b48e6b1577171f5eb6e4f3621
Author: Yan
Date: 2026年03月28日
- Add macOS target to emulator registry (PPSSPP, RetroArch)
- Add macos_url, macos_executable, macos_supported_platforms fields
- Add macosExecutable getter to EmulatorStrategy base class
- Add Platform.isMacOS branch to strategy registry filter
- Add Platform.isMacOS branch to emulator download service
- Add .dmg extraction via hdiutil attach/detach
- Add macOS zip extraction via system unzip (preserves symlinks/perms)
- Auto codesign and remove quarantine after extraction
- Fix RetroArch launch to use platform-correct paths and separators
- Fix RetroArch core map to use .dylib on macOS/Linux
- Fix RetroArch core download URL for macOS (arm64 buildbot)
- Fix directory service to use getApplicationSupportDirectory on macOS
- Fix emulator detection to use macos_executable on macOS
- Fix ROM download state persistence across tab switches
- Fix setState after dispose in emulator download listener
- Disable app sandbox in DebugProfile for dev builds
Why: Adds a new feature or capability to the application.
.gitignore | 1 +
ios/Flutter/Debug.xcconfig | 1 +
ios/Flutter/Release.xcconfig | 1 +
ios/Podfile | 43 +++++++++
lib/core/emulator/emulator_download_service.dart | 10 +-
lib/core/emulator/emulator_registry_data.dart | 13 ++-
lib/core/emulator/emulator_strategy.dart | 2 +
lib/core/emulator/strategies/ppsspp_strategy.dart | 4 +
.../emulator/strategies/retroarch_strategy.dart | 87 ++++++++++-------
lib/core/emulator/strategy_registry.dart | 1 +
lib/core/extraction/extraction_service.dart | 100 ++++++++++++++++++--
lib/core/storage/directory_service.dart | 63 +++++++++----
lib/ui/screens/library_screen.dart | 24 +++++
lib/ui/screens/settings_emulators_section.dart | 25 +++--
lib/ui/screens/settings_screen.dart | 10 +-
macos/Flutter/Flutter-Debug.xcconfig | 1 +
macos/Flutter/Flutter-Release.xcconfig | 1 +
macos/Podfile | 42 +++++++++
macos/Podfile.lock | 42 +++++++++
macos/Runner.xcodeproj/project.pbxproj | 103 ++++++++++++++++++++-
macos/Runner.xcworkspace/contents.xcworkspacedata | 3 +
macos/Runner/DebugProfile.entitlements | 18 ++--
macos/Runner/Release.entitlements | 16 +++-
23 files changed, 528 insertions(+), 83 deletions(-)
.gitignoreios/Flutter/Debug.xcconfigios/Flutter/Release.xcconfigios/Podfilelib/core/emulator/emulator_download_service.dartlib/core/emulator/emulator_registry_data.dartlib/core/emulator/emulator_strategy.dartlib/core/emulator/strategies/ppsspp_strategy.dartlib/core/emulator/strategies/retroarch_strategy.dartlib/core/emulator/strategy_registry.dartlib/core/extraction/extraction_service.dartlib/core/storage/directory_service.dartlib/ui/screens/library_screen.dartlib/ui/screens/settings_emulators_section.dartlib/ui/screens/settings_screen.dartmacos/Flutter/Flutter-Debug.xcconfigmacos/Flutter/Flutter-Release.xcconfigmacos/Podfilemacos/Podfile.lockmacos/Runner.xcodeproj/project.pbxprojmacos/Runner.xcworkspace/contents.xcworkspacedatamacos/Runner/DebugProfile.entitlementsmacos/Runner/Release.entitlements
diff --git a/.gitignore b/.gitignore index ea0b2ab..69a85da 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ app.*.map.json /android/app/profile /android/app/release .aider* +Documents/ diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/core/emulator/emulator_download_service.dart b/lib/core/emulator/emulator_download_service.dart index 968a204..990da4b 100644 --- a/lib/core/emulator/emulator_download_service.dart +++ b/lib/core/emulator/emulator_download_service.dart @@ -62,9 +62,13 @@ class EmulatorDownloadService { return; } } else { - downloadUrl = Platform.isWindows - ? definition['windows_url'] as String? - : definition['linux_url'] as String?; + if (Platform.isWindows) { + downloadUrl = definition['windows_url'] as String?; + } else if (Platform.isMacOS) { + downloadUrl = definition['macos_url'] as String?; + } else { + downloadUrl = definition['linux_url'] as String?; + } } if (downloadUrl == null) { diff --git a/lib/core/emulator/emulator_registry_data.dart b/lib/core/emulator/emulator_registry_data.dart index 54ba544..3766fbe 100644 --- a/lib/core/emulator/emulator_registry_data.dart +++ b/lib/core/emulator/emulator_registry_data.dart @@ -3,16 +3,19 @@ const List<Map<String, dynamic>> kEmulatorDefinitions = [ 'id': 'retroarch', 'name': 'RetroArch', 'type': 'direct', - 'windows_url': 'https://buildbot.libretro.com/stable/1.19.1/windows/x86_64/RetroArch.7z', + 'windows_url': 'https://buildbot.libretro.com/stable/2.0/windows/x86_64/RetroArch_x64.zip', 'windows_executable': 'RetroArch.exe', + 'linux_url': 'https://buildbot.libretro.com/stable/2.0/linux/x86_64/RetroArch.zip', 'linux_executable': 'retroarch', + 'macos_url': 'https://buildbot.libretro.com/stable/1.22.2/apple/osx/universal/RetroArch_Metal.dmg', + 'macos_executable': 'RetroArch.app/Contents/MacOS/RetroArch', 'platform_slugs': [ 'gba', 'gbc', 'gb', 'nes', 'snes', 'n64', 'nds', 'psx', 'ps1', 'playstation', 'psp', 'dc', 'dreamcast', 'segacd', 'saturn', 'megadrive', 'genesis', 'md', 'gamegear', 'atari2600', 'atari7800', 'lynx', 'neogeo', 'arcade', 'mame', 'pcengine', 'wonderswan', 'virtualboy', 'msx', 'dos' ], - 'supported_platforms': ['windows', 'linux'], + 'supported_platforms': ['windows', 'linux', 'macos'], }, { 'id': 'dolphin', @@ -145,13 +148,15 @@ const List<Map<String, dynamic>> kEmulatorDefinitions = [ { 'id': 'ppsspp', 'name': 'PPSSPP (PSP)', - 'type': 'github', + 'type': 'direct', 'github_repo': 'hrydgard/ppsspp', 'github_asset_required': ['Windows', 'x64', '.zip'], 'github_asset_excluded': ['debug', 'symbols', 'VR'], 'windows_executable': 'PPSSPPWindows64.exe', 'linux_executable': 'PPSSPP', - 'supported_platforms': ['windows', 'linux'], + 'macos_url': 'https://github.com/hrydgard/ppsspp/releases/download/v1.20.3/PPSSPPSDL-macOS-v1.20.3.zip', + 'macos_executable': 'PPSSPPSDL.app/Contents/MacOS/PPSSPPSDL', + 'supported_platforms': ['windows', 'linux', 'macos'], 'platform_slugs': ['psp', 'playstation-portable'], }, { diff --git a/lib/core/emulator/emulator_strategy.dart b/lib/core/emulator/emulator_strategy.dart index b0e70f9..5648e3c 100644 --- a/lib/core/emulator/emulator_strategy.dart +++ b/lib/core/emulator/emulator_strategy.dart @@ -8,11 +8,13 @@ abstract class EmulatorStrategy { List<String> get supportedSlugs; String get windowsExecutable; String get linuxExecutable; + String get macosExecutable => windowsExecutable; bool get supportsSaveSync; String getExecutableForPlatform() { if (io.Platform.isWindows) return windowsExecutable; if (io.Platform.isLinux) return linuxExecutable; + if (io.Platform.isMacOS) return macosExecutable; return windowsExecutable; } diff --git a/lib/core/emulator/strategies/ppsspp_strategy.dart b/lib/core/emulator/strategies/ppsspp_strategy.dart index 394f752..183d10f 100644 --- a/lib/core/emulator/strategies/ppsspp_strategy.dart +++ b/lib/core/emulator/strategies/ppsspp_strategy.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:io' as io; import 'package:freegosy/core/emulator/emulator_strategy.dart'; import 'package:freegosy/core/romm/romm_models.dart'; import 'package:freegosy/core/storage/directory_service.dart'; @@ -23,6 +24,9 @@ class PPSSPPStrategy extends EmulatorStrategy { @override String get linuxExecutable => 'PPSSPP'; + @override + String get macosExecutable => 'PPSSPPSDL.app/Contents/MacOS/PPSSPPSDL'; + @override bool get supportsSaveSync => false; diff --git a/lib/core/emulator/strategies/retroarch_strategy.dart b/lib/core/emulator/strategies/retroarch_strategy.dart index dbff3b6..204db24 100644 --- a/lib/core/emulator/strategies/retroarch_strategy.dart +++ b/lib/core/emulator/strategies/retroarch_strategy.dart @@ -1,4 +1,5 @@ -import 'dart:io'; +import 'dart:io' as io show Platform, File, Directory; +import 'dart:io' show Process, ProcessStartMode; import 'package:dio/dio.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; @@ -51,10 +52,13 @@ class RetroArchStrategy extends EmulatorStrategy { @override String get linuxExecutable => 'retroarch'; + @override + String get macosExecutable => 'RetroArch.app/Contents/MacOS/RetroArch'; + @override bool get supportsSaveSync => true; - static const Map<String, String> _coreMap = { + static const Map<String, String> _coreMapWindows = { 'gba': 'mgba_libretro.dll', 'gbc': 'mgba_libretro.dll', 'gb': 'mgba_libretro.dll', @@ -66,9 +70,22 @@ class RetroArchStrategy extends EmulatorStrategy { 'dc': 'flycast_libretro.dll', }; + static const Map<String, String> _coreMapUnix = { + 'gba': 'mgba_libretro.dylib', + 'gbc': 'mgba_libretro.dylib', + 'gb': 'mgba_libretro.dylib', + 'snes': 'snes9x_libretro.dylib', + 'nds': 'desmume2015_libretro.dylib', + 'n64': 'mupen64plus_next_libretro.dylib', + 'psx': 'pcsx_rearmed_libretro.dylib', + 'psp': 'ppsspp_libretro.dylib', + 'dc': 'flycast_libretro.dylib', + }; + String? _getCoreForSlug(String? slug) { if (slug == null) return null; - return _coreMap[slug.toLowerCase()]; + final map = io.Platform.isWindows ? _coreMapWindows : _coreMapUnix; + return map[slug.toLowerCase()]; } @override @@ -79,33 +96,28 @@ class RetroArchStrategy extends EmulatorStrategy { throw Exception('$name not found. Please download it first.'); } - final normalizedExe = exePath.replaceAll('/', r'\'); - final normalizedRom = romPath.replaceAll('/', r'\'); final coreName = _getCoreForSlug(game.platformSlug); + final sep = io.Platform.isWindows ? r'\' : '/'; if (coreName == null) { - await Process.start( - normalizedExe, - [normalizedRom], - mode: ProcessStartMode.detached, - ); + await Process.start(exePath, [romPath], mode: ProcessStartMode.detached); return; } - final exeDir = File(normalizedExe).parent.path; - final corePath = '$exeDir\\cores\\$coreName'; + final exeDir = io.File(exePath).parent.path; + final corePath = '$exeDir${sep}cores$sep$coreName'; - if (!await File(corePath).exists()) { + if (!await io.File(corePath).exists()) { throw MissingRetroArchCoreException( coreName: coreName, corePath: corePath, - exePath: normalizedExe, + exePath: exePath, ); } await Process.start( - normalizedExe, - ['-L', corePath, normalizedRom], + exePath, + ['-L', corePath, romPath], mode: ProcessStartMode.detached, ); } @@ -118,54 +130,61 @@ class RetroArchStrategy extends EmulatorStrategy { throw Exception('$name not found. Please download it first.'); } - final normalizedExe = exePath.replaceAll('/', r'\'); - final normalizedRom = romPath.replaceAll('/', r'\'); final coreName = _getCoreForSlug(game.platformSlug); + final sep = io.Platform.isWindows ? r'\' : '/'; if (coreName == null) { - return await Process.start( - normalizedExe, - [normalizedRom], - mode: ProcessStartMode.normal, - ); + return await Process.start(exePath, [romPath], mode: ProcessStartMode.normal); } - final exeDir = File(normalizedExe).parent.path; - final corePath = '$exeDir\\cores\\$coreName'; + final exeDir = io.File(exePath).parent.path; + final corePath = '$exeDir${sep}cores$sep$coreName'; - if (!await File(corePath).exists()) { + if (!await io.File(corePath).exists()) { throw MissingRetroArchCoreException( coreName: coreName, corePath: corePath, - exePath: normalizedExe, + exePath: exePath, ); } return await Process.start( - normalizedExe, - ['-L', corePath, normalizedRom], + exePath, + ['-L', corePath, romPath], mode: ProcessStartMode.normal, ); } Future<void> downloadCore(String coreName, String coresDir, Dio dio) async { - final url = 'https://buildbot.libretro.com/nightly/windows/x86_64/latest/$coreName.zip'; + final String url; + final String ext; + if (io.Platform.isWindows) { + ext = 'dll'; + url = 'https://buildbot.libretro.com/nightly/windows/x86_64/latest/$coreName.zip'; + } else if (io.Platform.isMacOS) { + ext = 'dylib'; + url = 'https://buildbot.libretro.com/nightly/apple/osx/arm64/latest/$coreName.zip'; + } else { + ext = 'so'; + url = 'https://buildbot.libretro.com/nightly/linux/x86_64/latest/$coreName.zip'; + } + final tempDir = await getTemporaryDirectory(); final zipPath = p.join(tempDir.path, '$coreName.zip'); try { await dio.download(url, zipPath); - final bytes = await File(zipPath).readAsBytes(); + final bytes = await io.File(zipPath).readAsBytes(); final archive = ZipDecoder().decodeBytes(bytes); for (final entry in archive) { - if (entry.isFile && entry.name.endsWith('.dll')) { - final outFile = File('$coresDir\\${entry.name}'); + if (entry.isFile && entry.name.endsWith('.$ext')) { + final outFile = io.File(p.join(coresDir, entry.name)); await outFile.parent.create(recursive: true); await outFile.writeAsBytes(entry.content as List<int>); } } } finally { - final f = File(zipPath); + final f = io.File(zipPath); if (await f.exists()) await f.delete(); } } diff --git a/lib/core/emulator/strategy_registry.dart b/lib/core/emulator/strategy_registry.dart index b60d6cf..a9859a8 100644 --- a/lib/core/emulator/strategy_registry.dart +++ b/lib/core/emulator/strategy_registry.dart @@ -52,6 +52,7 @@ class StrategyRegistry { final supported = List<String>.from(definition['supported_platforms'] ?? []); if (Platform.isWindows && supported.contains('windows')) return true; if (Platform.isLinux && supported.contains('linux')) return true; + if (Platform.isMacOS && supported.contains('macos')) return true; return false; }).toList(); } diff --git a/lib/core/extraction/extraction_service.dart b/lib/core/extraction/extraction_service.dart index 5880ed9..8c85d1a 100644 --- a/lib/core/extraction/extraction_service.dart +++ b/lib/core/extraction/extraction_service.dart @@ -18,9 +18,73 @@ class ExtractionService { Future<void> extract(String archivePath, String destDir) async { final pathLower = archivePath.toLowerCase(); + if (pathLower.endsWith('.dmg')) { + final mountResult = await Process.run( + 'hdiutil', + ['attach', archivePath, '-nobrowse', '-readonly'], + ); + if (mountResult.exitCode != 0) { + throw Exception('Failed to mount DMG: ${mountResult.stderr}'); + } + + String? mountPoint; + for (final line in mountResult.stdout.toString().split('\n')) { + if (line.contains('/Volumes/')) { + mountPoint = line.trim().split('\t').last.trim(); + break; + } + } + if (mountPoint == null) { + throw Exception('Could not determine DMG mount point'); + } + + try { + final volume = Directory(mountPoint); + await for (final entity in volume.list()) { + if (entity is Directory && entity.path.endsWith('.app')) { + final appName = entity.uri.pathSegments + .where((s) => s.isNotEmpty) + .last; + final dest = Directory('$destDir/$appName'); + await _copyDirectory(entity, dest); + break; + } + } + } finally { + await Process.run('hdiutil', ['detach', mountPoint, '-force']); + } + return; + } + if (pathLower.endsWith('.zip')) { - final fileBytes = await File(archivePath).readAsBytes(); - await compute(_extractZipIsolate, [fileBytes, destDir]); + if (Platform.isMacOS || Platform.isLinux) { + final result = await Process.run( + 'unzip', + ['-o', archivePath, '-d', destDir], + runInShell: false, + ); + if (result.exitCode != 0) { + throw Exception('unzip failed: ${result.stderr}'); + } + await Process.run( + 'find', + [destDir, '-name', '*.app', '-exec', 'chmod', '-R', '+x', '{}', ';'], + runInShell: false, + ); + // Remove quarantine and self-sign all .app bundles so macOS allows launch + final findResult = await Process.run( + 'find', [destDir, '-name', '*.app', '-maxdepth', '3'], + runInShell: false, + ); + for (final appPath in findResult.stdout.toString().trim().split('\n')) { + if (appPath.isEmpty) continue; + await Process.run('xattr', ['-rd', 'com.apple.quarantine', appPath]); + await Process.run('codesign', ['--force', '--deep', '--sign', '-', appPath]); + } + } else { + final fileBytes = await File(archivePath).readAsBytes(); + await compute(_extractZipIsolate, [fileBytes, destDir]); + } } else if (pathLower.endsWith('.7z')) { final sevenZipExe = await directoryService.resolveSevenZipPath(); if (sevenZipExe == null) { @@ -35,14 +99,12 @@ class ExtractionService { throw Exception('7z extraction failed: ${result.stderr}'); } } else if (pathLower.endsWith('.exe')) { - // Self-extracting archive var result = await Process.run( archivePath, ['-o$destDir', '-y'], runInShell: false, ); if (result.exitCode != 0) { - // Try as a plain self-extractor with no arguments result = await Process.run( archivePath, [], @@ -50,7 +112,6 @@ class ExtractionService { ); } } else { - // Try ZIP magic bytes (PK = 0x50 0x4B) bool isZip = false; try { final raf = await File(archivePath).open(); @@ -60,11 +121,36 @@ class ExtractionService { } catch (_) {} if (isZip) { - final fileBytes = await File(archivePath).readAsBytes(); - await compute(_extractZipIsolate, [fileBytes, destDir]); + if (Platform.isMacOS || Platform.isLinux) { + final result = await Process.run( + 'unzip', + ['-o', archivePath, '-d', destDir], + runInShell: false, + ); + if (result.exitCode != 0) { + throw Exception('unzip failed: ${result.stderr}'); + } + } else { + final fileBytes = await File(archivePath).readAsBytes(); + await compute(_extractZipIsolate, [fileBytes, destDir]); + } } else { throw Exception('Unsupported archive format: $archivePath'); } } } + + Future<void> _copyDirectory(Directory source, Directory dest) async { + await dest.create(recursive: true); + await for (final entity in source.list(recursive: false)) { + final name = entity.uri.pathSegments + .where((s) => s.isNotEmpty) + .last; + if (entity is Directory) { + await _copyDirectory(entity, Directory('${dest.path}/$name')); + } else if (entity is File) { + await entity.copy('${dest.path}/$name'); + } + } + } } diff --git a/lib/core/storage/directory_service.dart b/lib/core/storage/directory_service.dart index e71e873..a6badb0 100644 --- a/lib/core/storage/directory_service.dart +++ b/lib/core/storage/directory_service.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:freegosy/core/romm/romm_models.dart'; @@ -42,8 +43,18 @@ class DirectoryService { Future<void> initialize() async { final prefs = await SharedPreferences.getInstance(); - romsRootPath = prefs.getString(_romsRootPathKey) ?? _defaultRomsPath; - emulatorsRootPath = prefs.getString(_emulatorsRootPathKey) ?? _defaultEmulatorsPath; + final String defaultBase; + if (defaultTargetPlatform == TargetPlatform.macOS || + defaultTargetPlatform == TargetPlatform.linux) { + final appSupport = await getApplicationSupportDirectory(); + defaultBase = appSupport.path; + } else { + final docsDir = await getApplicationDocumentsDirectory(); + defaultBase = docsDir.path; + } + + romsRootPath = prefs.getString(_romsRootPathKey) ?? '$defaultBase/ROMs'; + emulatorsRootPath = prefs.getString(_emulatorsRootPathKey) ?? '$defaultBase/Emulators'; await _ensureDirectoryExists(romsRootPath); await _ensureDirectoryExists(emulatorsRootPath); await loadEmulatorPathOverrides(); @@ -219,32 +230,50 @@ class DirectoryService { return '$emulatorDir/$executableName'; } - Future<bool> isEmulatorInstalled(String emulatorId, String executableName) async { - final dir = Directory(await getEmulatorDirectory(emulatorId)); - if (!await dir.exists()) return false; - if (await File('${dir.path}/$executableName').exists()) return true; - await for (final entity in dir.list()) { - if (entity is Directory) { - if (await File('${entity.path}/$executableName').exists()) return true; + Future<String?> findEmulatorExecutable(String emulatorId, String executableName) async { + final emulatorDir = await getEmulatorDirectory(emulatorId); + final dir = Directory(emulatorDir); + if (!await dir.exists()) { + return null; + } + + final direct = File('$emulatorDir/$executableName'); + if (await direct.exists()) { + return direct.path; + } + + if (executableName.contains('/')) { + await for (final entity in dir.list()) { + if (entity is Directory) { + final sub = File('${entity.path}/$executableName'); + if (await sub.exists()) { + return sub.path; + } + } } + return null; } - return false; - } - Future<String?> findEmulatorExecutable(String emulatorId, String executableName) async { - final dir = Directory(await getEmulatorDirectory(emulatorId)); - if (!await dir.exists()) return null; - final direct = File('${dir.path}/$executableName'); - if (await direct.exists()) return direct.path; await for (final entity in dir.list()) { + if (entity is File && entity.path.endsWith('/$executableName')) { + return entity.path; + } if (entity is Directory) { final sub = File('${entity.path}/$executableName'); - if (await sub.exists()) return sub.path; + if (await sub.exists()) { + return sub.path; + } } } + return null; } + Future<bool> isEmulatorInstalled(String emulatorId, String executableName) async { + final found = await findEmulatorExecutable(emulatorId, executableName); + return found != null; + } + Future<bool> isRomDownloaded(Game game) async { final found = await findExistingRomPath(game); return found != null; diff --git a/lib/ui/screens/library_screen.dart b/lib/ui/screens/library_screen.dart index 45fd81b..dadd284 100644 --- a/lib/ui/screens/library_screen.dart +++ b/lib/ui/screens/library_screen.dart @@ -41,6 +41,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> { _scrollController.addListener(_onScroll); WidgetsBinding.instance.addPostFrameCallback((_) { _focusNode.requestFocus(); + _refreshAllDownloadStates(); }); } @@ -76,6 +77,24 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> { platformId: ref.read(selectedPlatformIdProvider)?.toString(), search: ref.read(searchQueryProvider).isEmpty ? null : ref.read(searchQueryProvider), ); + await _refreshAllDownloadStates(); + } + + Future<void> _refreshAllDownloadStates() async { + if (ref.read(paginatedGamesProvider).games.isEmpty) return; + final dirService = ref.read(directoryServiceProvider).asData?.value; + if (dirService == null) return; + final games = ref.read(paginatedGamesProvider).games; + final results = await Future.wait( + games.map((g) async => MapEntry(g.id, await dirService.isRomDownloaded(g))), + ); + if (mounted) { + setState(() { + for (final entry in results) { + _downloadedStates[entry.key] = entry.value; + } + }); + } } void _startDownload(BuildContext context, WidgetRef ref, Game game) { @@ -525,6 +544,11 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> { )); } final game = paginatedState.games[index]; + if (_downloadedStates[game.id] == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _refreshAllDownloadStates(); + }); + } final dirService = directoryServiceAsync.asData?.value; final isWindowsGame = ['windows', 'pc', 'win'].contains(game.platformSlug?.toLowerCase() ?? ''); final coverUrl = ref.read(rommServiceProvider)?.resolveCoverUrl(game); diff --git a/lib/ui/screens/settings_emulators_section.dart b/lib/ui/screens/settings_emulators_section.dart index 4fc673a..b87ea52 100644 --- a/lib/ui/screens/settings_emulators_section.dart +++ b/lib/ui/screens/settings_emulators_section.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:file_picker/file_picker.dart'; @@ -78,15 +79,23 @@ Widget buildEmulatorsSection( ref.read(downloadProvider.notifier).startEmulatorDownload(emulatorId, emulatorName); // Listen for download completion and update state - ref.read(downloadProvider.notifier).stream.listen((downloads) { + StreamSubscription? sub; + sub = ref.read(downloadProvider.notifier).stream.listen((downloads) { final progress = downloads[emulatorId]; - if (progress != null && progress.isComplete) { // Use safeContext here - // Update the map directly. setState will re-render using the updated map. - emulatorInstallStates[emulatorId] = true; - setState(() {}); // Trigger parent state update - messenger.showSnackBar(SnackBar( - content: Text('$emulatorName downloaded.'), - )); + if (progress != null && (progress.isComplete || progress.error != null)) { + if (progress.isComplete) { + emulatorInstallStates[emulatorId] = true; + if (context.mounted) setState(() {}); + messenger.showSnackBar(SnackBar( + content: Text('$emulatorName downloaded successfully.'), + )); + } else if (progress.error != null) { + if (context.mounted) setState(() {}); + messenger.showSnackBar(SnackBar( + content: Text('Failed to download $emulatorName: ${progress.error}'), + )); + } + sub?.cancel(); } }); }, diff --git a/lib/ui/screens/settings_screen.dart b/lib/ui/screens/settings_screen.dart index f75081b..f41ca2d 100644 --- a/lib/ui/screens/settings_screen.dart +++ b/lib/ui/screens/settings_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:file_picker/file_picker.dart'; @@ -62,7 +63,14 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> { final states = <String, bool>{}; for (final def in kEmulatorDefinitions) { final id = def['id'] as String; - final exe = def['windows_executable'] as String; + final String exe; + if (defaultTargetPlatform == TargetPlatform.macOS) { + exe = (def['macos_executable'] as String?) ?? (def['windows_executable'] as String? ?? ''); + } else if (defaultTargetPlatform == TargetPlatform.linux) { + exe = (def['linux_executable'] as String?) ?? ''; + } else { + exe = (def['windows_executable'] as String?) ?? ''; + } if (exe.isEmpty) { states[id] = true; // Assume installed if no executable is defined continue; diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b..4b81f9b 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig index c2efd0b..5caa9d1 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..76836e7 --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,42 @@ +PODS: + - flutter_secure_storage_macos (6.1.3): + - FlutterMacOS + - FlutterMacOS (1.0.0) + - package_info_plus (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) + +EXTERNAL SOURCES: + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + FlutterMacOS: + :path: Flutter/ephemeral + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + sqflite_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin + +SPEC CHECKSUMS: + flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + package_info_plus: 122abb51244f66eead59ce7c9c200d6b53111779 + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index d11c248..18540de 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -21,12 +21,14 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 20FED99443D842721917B5EA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11BF9FD2E5D40BBC266BE01E /* Pods_Runner.framework */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + B4CD7DDD1DFB195BBB89E7B6 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C132C305A1AF3E6388C6B7C2 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,11 +62,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 11BF9FD2E5D40BBC266BE01E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; - 33CC10ED2044A3C60003C045 /* freegosy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "freegosy.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* freegosy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = freegosy.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; @@ -76,8 +79,15 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; + 3FB6F37F00E4A78247BF6B40 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; + 645F69065E0F4B8AB271256A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; + 7F03BA3B183B04F51E8CC981 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; + 81CF0E3069A0EAB2590738F9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; + C132C305A1AF3E6388C6B7C2 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CCAA1F871C96E6DDC1498078 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; }; + EEECA80AC0A0FD92A265CAB6 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B4CD7DDD1DFB195BBB89E7B6 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -92,6 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 20FED99443D842721917B5EA /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -125,6 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 7236E435FDF89CA91259A96E /* Pods */, ); sourceTree = "<group>"; }; @@ -172,9 +185,24 @@ path = Runner; sourceTree = "<group>"; }; + 7236E435FDF89CA91259A96E /* Pods */ = { + isa = PBXGroup; + children = ( + 7F03BA3B183B04F51E8CC981 /* Pods-Runner.debug.xcconfig */, + 3FB6F37F00E4A78247BF6B40 /* Pods-Runner.release.xcconfig */, + 81CF0E3069A0EAB2590738F9 /* Pods-Runner.profile.xcconfig */, + 645F69065E0F4B8AB271256A /* Pods-RunnerTests.debug.xcconfig */, + EEECA80AC0A0FD92A265CAB6 /* Pods-RunnerTests.release.xcconfig */, + CCAA1F871C96E6DDC1498078 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = "<group>"; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 11BF9FD2E5D40BBC266BE01E /* Pods_Runner.framework */, + C132C305A1AF3E6388C6B7C2 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = "<group>"; @@ -186,6 +214,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 1C4411D508300D115841BAD7 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -204,11 +233,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 5C47E23C8AFB35CF3ADC0D54 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + CBBB3DE13D8D81E5177BF4F9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -291,6 +322,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1C4411D508300D115841BAD7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -329,6 +382,45 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 5C47E23C8AFB35CF3ADC0D54 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CBBB3DE13D8D81E5177BF4F9 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -380,6 +472,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 645F69065E0F4B8AB271256A /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -394,6 +487,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EEECA80AC0A0FD92A265CAB6 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -408,6 +502,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = CCAA1F871C96E6DDC1498078 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -476,8 +571,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = FWFZZNYS2X; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -608,8 +705,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = FWFZZNYS2X; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -628,8 +727,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = FWFZZNYS2X; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ <FileRef location = "group:Runner.xcodeproj"> </FileRef> + <FileRef + location = "group:Pods/Pods.xcodeproj"> + </FileRef> </Workspace> diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index dddb8a3..775b708 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -2,11 +2,17 @@ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> - <key>com.apple.security.app-sandbox</key> - <true/> - <key>com.apple.security.cs.allow-jit</key> - <true/> - <key>com.apple.security.network.server</key> - <true/> + <key>com.apple.security.app-sandbox</key> + <false/> + <key>com.apple.security.cs.allow-jit</key> + <true/> + <key>com.apple.security.network.client</key> + <true/> + <key>com.apple.security.network.server</key> + <true/> + <key>keychain-access-groups</key> + <array> + <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string> + </array> </dict> </plist> diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 852fa1a..67ffb8f 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -2,7 +2,19 @@ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> - <key>com.apple.security.app-sandbox</key> - <true/> + <key>com.apple.security.app-sandbox</key> + <true/> + <key>com.apple.security.network.client</key> + <true/> + <key>com.apple.security.files.user-selected.read-write</key> + <true/> + <key>com.apple.security.files.downloads.read-write</key> + <true/> + <key>com.apple.security.temporary-exception.files.absolute-path.read-write</key> + <string>/</string> + <key>keychain-access-groups</key> + <array> + <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string> + </array> </dict> </plist>