feat: Introduce PokemonImage component for robust image loading with fallback and shiny support, and update iOS platform to 15.0.
This commit is contained in:
parent
8cca5a64de
commit
4faf259aaa
@ -20,7 +20,5 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
|
||||||
<string>13.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# 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.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
@ -39,5 +39,8 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -6,25 +6,25 @@ PODS:
|
|||||||
- sqflite_darwin (0.0.4):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (3.51.1):
|
- sqlite3 (3.52.0):
|
||||||
- sqlite3/common (= 3.51.1)
|
- sqlite3/common (= 3.52.0)
|
||||||
- sqlite3/common (3.51.1)
|
- sqlite3/common (3.52.0)
|
||||||
- sqlite3/dbstatvtab (3.51.1):
|
- sqlite3/dbstatvtab (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/fts5 (3.51.1):
|
- sqlite3/fts5 (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/math (3.51.1):
|
- sqlite3/math (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/perf-threadsafe (3.51.1):
|
- sqlite3/perf-threadsafe (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/rtree (3.51.1):
|
- sqlite3/rtree (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3/session (3.51.1):
|
- sqlite3/session (3.52.0):
|
||||||
- sqlite3/common
|
- sqlite3/common
|
||||||
- sqlite3_flutter_libs (0.0.1):
|
- sqlite3_flutter_libs (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqlite3 (~> 3.51.1)
|
- sqlite3 (~> 3.52.0)
|
||||||
- sqlite3/dbstatvtab
|
- sqlite3/dbstatvtab
|
||||||
- sqlite3/fts5
|
- sqlite3/fts5
|
||||||
- sqlite3/math
|
- sqlite3/math
|
||||||
@ -56,9 +56,9 @@ SPEC CHECKSUMS:
|
|||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921
|
||||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab
|
||||||
|
|
||||||
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
|
PODFILE CHECKSUM: 4b015915ec662986b54bf30ab778da63f7dda016
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import UIKit
|
|
||||||
import Flutter
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
@ -24,6 +26,29 @@
|
|||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>FlutterSceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@ -41,9 +66,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
87
lib/components/pokemon_image.dart
Normal file
87
lib/components/pokemon_image.dart
Normal file
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../models/pokemon.dart';
|
import '../models/pokemon.dart';
|
||||||
|
import 'pokemon_image.dart';
|
||||||
|
|
||||||
class PokemonTile extends StatelessWidget {
|
class PokemonTile extends StatelessWidget {
|
||||||
const PokemonTile(this.pokemon, {Key? key}) : super(key: key);
|
const PokemonTile(this.pokemon, {Key? key}) : super(key: key);
|
||||||
@ -41,7 +42,7 @@ class PokemonTile extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
child: pokemon.isCaught
|
child: pokemon.isCaught
|
||||||
? Image.network(pokemon.imageUrl, fit: BoxFit.contain)
|
? PokemonImage(imageUrl: pokemon.imageUrl, fit: BoxFit.contain)
|
||||||
: const SizedBox.expand(),
|
: const SizedBox.expand(),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../database/pokedex_database.dart';
|
import '../database/pokedex_database.dart';
|
||||||
|
import '../components/pokemon_image.dart';
|
||||||
|
|
||||||
class GameOverPage extends StatefulWidget {
|
class GameOverPage extends StatefulWidget {
|
||||||
const GameOverPage({Key? key}) : super(key: key);
|
const GameOverPage({Key? key}) : super(key: key);
|
||||||
@ -95,7 +96,10 @@ class _GameOverPageState extends State<GameOverPage> {
|
|||||||
if (pokemonImage.isNotEmpty)
|
if (pokemonImage.isNotEmpty)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 140,
|
height: 140,
|
||||||
child: Image.network(pokemonImage, fit: BoxFit.contain),
|
child: PokemonImage(
|
||||||
|
imageUrl: pokemonImage,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
import '../models/pokemon.dart';
|
import '../models/pokemon.dart';
|
||||||
import '../database/pokedex_database.dart';
|
import '../database/pokedex_database.dart';
|
||||||
import 'main_page.dart';
|
import 'main_page.dart';
|
||||||
|
import '../components/pokemon_image.dart';
|
||||||
|
|
||||||
class GuessPage extends StatefulWidget {
|
class GuessPage extends StatefulWidget {
|
||||||
const GuessPage({Key? key}) : super(key: key);
|
const GuessPage({Key? key}) : super(key: key);
|
||||||
@ -258,13 +259,17 @@ class _GuessPageState extends State<GuessPage> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: _isGuessed
|
child: _isGuessed
|
||||||
? Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain)
|
? PokemonImage(
|
||||||
: ColorFiltered(
|
imageUrl: _isShiny ? _currentPokemon!.shinyImageUrl : _currentPokemon!.imageUrl,
|
||||||
colorFilter: ColorFilter.mode(
|
fallbackUrl: _currentPokemon!.imageUrl,
|
||||||
_isShiny ? Colors.yellow[700]! : Colors.black,
|
fit: BoxFit.contain,
|
||||||
BlendMode.srcIn
|
)
|
||||||
),
|
: PokemonImage(
|
||||||
child: Image.network(_currentPokemon!.imageUrl, fit: BoxFit.contain),
|
imageUrl: _isShiny ? _currentPokemon!.shinyImageUrl : _currentPokemon!.imageUrl,
|
||||||
|
fallbackUrl: _currentPokemon!.imageUrl,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
color: _isShiny ? Colors.yellow[700]! : Colors.black,
|
||||||
|
colorBlendMode: BlendMode.srcIn,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../models/pokemon.dart';
|
import '../models/pokemon.dart';
|
||||||
import '../components/pokemon_type.dart';
|
import '../components/pokemon_type.dart';
|
||||||
|
import '../components/pokemon_image.dart';
|
||||||
|
|
||||||
class PokemonDetailPage extends StatefulWidget {
|
class PokemonDetailPage extends StatefulWidget {
|
||||||
const PokemonDetailPage({Key? key}) : super(key: key);
|
const PokemonDetailPage({Key? key}) : super(key: key);
|
||||||
@ -126,7 +127,11 @@ class _PokemonDetailPageState extends State<PokemonDetailPage> {
|
|||||||
height: 180,
|
height: 180,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
color: const Color(0xFF81CCA5).withAlpha(153), // subtle green background behind sprite
|
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(
|
Container(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user