diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 1dc6cf7..391a902 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/ios/Podfile b/ios/Podfile index 620e46e..c7a0964 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -39,5 +39,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' + end end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 10cbf8e..5a56e30 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,25 +6,25 @@ PODS: - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - - sqlite3 (3.51.1): - - sqlite3/common (= 3.51.1) - - sqlite3/common (3.51.1) - - sqlite3/dbstatvtab (3.51.1): + - sqlite3 (3.52.0): + - sqlite3/common (= 3.52.0) + - sqlite3/common (3.52.0) + - sqlite3/dbstatvtab (3.52.0): - sqlite3/common - - sqlite3/fts5 (3.51.1): + - sqlite3/fts5 (3.52.0): - sqlite3/common - - sqlite3/math (3.51.1): + - sqlite3/math (3.52.0): - sqlite3/common - - sqlite3/perf-threadsafe (3.51.1): + - sqlite3/perf-threadsafe (3.52.0): - sqlite3/common - - sqlite3/rtree (3.51.1): + - sqlite3/rtree (3.52.0): - sqlite3/common - - sqlite3/session (3.51.1): + - sqlite3/session (3.52.0): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - FlutterMacOS - - sqlite3 (~> 3.51.1) + - sqlite3 (~> 3.52.0) - sqlite3/dbstatvtab - sqlite3/fts5 - sqlite3/math @@ -56,9 +56,9 @@ SPEC CHECKSUMS: Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b - sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41 + sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921 + sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab -PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e +PODFILE CHECKSUM: 4b015915ec662986b54bf30ab778da63f7dda016 COCOAPODS: 1.16.2 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index b636303..c30b367 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,13 +1,16 @@ -import UIKit import Flutter +import UIKit @main -@objc class AppDelegate: FlutterAppDelegate { +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index f3ff95b..3e9f4f2 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,29 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,9 +66,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/lib/components/pokemon_image.dart b/lib/components/pokemon_image.dart new file mode 100644 index 0000000..848b804 --- /dev/null +++ b/lib/components/pokemon_image.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; + +class PokemonImage extends StatelessWidget { + final String imageUrl; + final String? fallbackUrl; + final BoxFit fit; + final double? width; + final double? height; + final Color? color; + final BlendMode? colorBlendMode; + + const PokemonImage({ + super.key, + required this.imageUrl, + this.fallbackUrl, + this.fit = BoxFit.contain, + this.width, + this.height, + this.color, + this.colorBlendMode, + }); + + @override + Widget build(BuildContext context) { + return Image.network( + imageUrl, + fit: fit, + width: width, + height: height, + color: color, + colorBlendMode: colorBlendMode, + errorBuilder: (context, error, stackTrace) { + // If the primary image fails and we have a fallback, try the fallback + if (fallbackUrl != null && fallbackUrl != imageUrl) { + return Image.network( + fallbackUrl!, + fit: fit, + width: width, + height: height, + color: color, + colorBlendMode: colorBlendMode, + errorBuilder: (context, error, stackTrace) { + // If the fallback also fails, show a placeholder + return _buildPlaceholder(); + }, + ); + } + // No fallback, show placeholder + return _buildPlaceholder(); + }, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! + : null, + ), + ); + }, + ); + } + + Widget _buildPlaceholder() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.help_outline, + size: (width ?? 40) * 0.5, + color: Colors.grey[400], + ), + if ((width ?? 100) > 60) + Text( + "Not Found", + style: TextStyle( + color: Colors.grey[600], + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } +} diff --git a/lib/components/pokemon_tile.dart b/lib/components/pokemon_tile.dart index e9dbeb3..e165173 100644 --- a/lib/components/pokemon_tile.dart +++ b/lib/components/pokemon_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../models/pokemon.dart'; +import 'pokemon_image.dart'; class PokemonTile extends StatelessWidget { const PokemonTile(this.pokemon, {Key? key}) : super(key: key); @@ -41,7 +42,7 @@ class PokemonTile extends StatelessWidget { borderRadius: BorderRadius.circular(4), ), child: pokemon.isCaught - ? Image.network(pokemon.imageUrl, fit: BoxFit.contain) + ? PokemonImage(imageUrl: pokemon.imageUrl, fit: BoxFit.contain) : const SizedBox.expand(), ), const SizedBox(width: 16), diff --git a/lib/pages/game_over_page.dart b/lib/pages/game_over_page.dart index f205685..c8c23bc 100644 --- a/lib/pages/game_over_page.dart +++ b/lib/pages/game_over_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../database/pokedex_database.dart'; +import '../components/pokemon_image.dart'; class GameOverPage extends StatefulWidget { const GameOverPage({Key? key}) : super(key: key); @@ -95,7 +96,10 @@ class _GameOverPageState extends State { if (pokemonImage.isNotEmpty) SizedBox( height: 140, - child: Image.network(pokemonImage, fit: BoxFit.contain), + child: PokemonImage( + imageUrl: pokemonImage, + fit: BoxFit.contain, + ), ), const SizedBox(height: 12), Text( diff --git a/lib/pages/guess_page.dart b/lib/pages/guess_page.dart index 8e19049..c3f0e1a 100644 --- a/lib/pages/guess_page.dart +++ b/lib/pages/guess_page.dart @@ -4,6 +4,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../models/pokemon.dart'; import '../database/pokedex_database.dart'; import 'main_page.dart'; +import '../components/pokemon_image.dart'; class GuessPage extends StatefulWidget { const GuessPage({Key? key}) : super(key: key); @@ -258,13 +259,17 @@ class _GuessPageState extends State { child: Padding( padding: const EdgeInsets.all(16.0), child: _isGuessed - ? Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain) - : ColorFiltered( - colorFilter: ColorFilter.mode( - _isShiny ? Colors.yellow[700]! : Colors.black, - BlendMode.srcIn - ), - child: Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain), + ? PokemonImage( + imageUrl: _isShiny ? _currentPokemon!.shinyImageUrl : _currentPokemon!.imageUrl, + fallbackUrl: _currentPokemon!.imageUrl, + fit: BoxFit.contain, + ) + : PokemonImage( + imageUrl: _isShiny ? _currentPokemon!.shinyImageUrl : _currentPokemon!.imageUrl, + fallbackUrl: _currentPokemon!.imageUrl, + fit: BoxFit.contain, + color: _isShiny ? Colors.yellow[700]! : Colors.black, + colorBlendMode: BlendMode.srcIn, ), ), ), diff --git a/lib/pages/pokemon_detail.dart b/lib/pages/pokemon_detail.dart index 5ff291d..8caea2d 100644 --- a/lib/pages/pokemon_detail.dart +++ b/lib/pages/pokemon_detail.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../models/pokemon.dart'; import '../components/pokemon_type.dart'; +import '../components/pokemon_image.dart'; class PokemonDetailPage extends StatefulWidget { const PokemonDetailPage({Key? key}) : super(key: key); @@ -126,7 +127,11 @@ class _PokemonDetailPageState extends State { height: 180, alignment: Alignment.center, color: const Color(0xFF81CCA5).withAlpha(153), // subtle green background behind sprite - child: Image.network(_isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl, fit: BoxFit.contain), + child: PokemonImage( + imageUrl: _isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl, + fallbackUrl: _isShiny ? pokemon.imageUrl : null, + fit: BoxFit.contain, + ), ), ), Container(