-
-
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>