Compare commits
7 Commits
main
...
pokeguess-
| Author | SHA1 | Date | |
|---|---|---|---|
| 4faf259aaa | |||
| 8cca5a64de | |||
| d3d3ba3586 | |||
| 6592b35755 | |||
| 112d0136c9 | |||
| fbf37e6861 | |||
| 528cdcafef |
3
devtools_options.yaml
Normal file
3
devtools_options.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
32
docs/ARCHITECTURE.md
Normal file
32
docs/ARCHITECTURE.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Application Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The application follows a modular structure separated by responsibilities (models, pages, components, services).
|
||||
|
||||
## Layers
|
||||
|
||||
### 1. Data Layer
|
||||
|
||||
- **Models**: `Pokemon` class defines the data structure for a Pokemon, including serialization/deserialization logic.
|
||||
- **API**: `PokemonApi` handles communication with the Tyradex REST API using the `http` package.
|
||||
- **Database**: `PokedexDatabase` manages local persistence using SQLite (`sqflite`). It uses batch operations for performance during initial sync.
|
||||
|
||||
### 2. Business Logic & State
|
||||
|
||||
- **State Management**: Uses Flutter's `StatefulWidget` and `setState` for local page state.
|
||||
- **Reactivity**: `ValueNotifier` in the database layer notifies the UI when data changes (e.g., catching a Pokemon updates the list).
|
||||
- **Persistence**: `shared_preferences` is used for simple key-value storage like best scores.
|
||||
|
||||
### 3. UI Layer
|
||||
|
||||
- **Pages**: Top-level screens like `MainPage`, `PokemonListPage`, and `GuessPage`.
|
||||
- **Components**: Reusable UI elements like `PokemonTile`.
|
||||
- **Navigation**: Managed in `MainPage` using `IndexedStack` to preserve tab state across navigation.
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. At startup, the app checks the local database.
|
||||
2. If the database is missing generations, it fetches the full list from Tyradex API and performs a batch insert.
|
||||
3. User interactions (like a correct guess) update the local database.
|
||||
4. The database triggers a notification, causing relevant UI components to refresh their view.
|
||||
28
docs/README.md
Normal file
28
docs/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Pokeguess
|
||||
|
||||
## Description
|
||||
|
||||
Pokeguess is a Flutter mobile application that allows users to discover and collect Pokemon through a silhouette guessing game. The app fetch data from the Tyradex API and stores it locally for offline access.
|
||||
|
||||
## Features
|
||||
|
||||
- National Pokedex: Browse all 1025+ Pokemon from all generations.
|
||||
- Guess Game: Identify Pokemon by their silhouette.
|
||||
- Scoring System: Earn points for correct guesses, with bonuses for Shiny Pokemon. High scores are saved locally.
|
||||
- Collection: Track caught and seen Pokemon.
|
||||
- Search and Filter: Filter the collection by all or caught status and search by name.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Ensure Flutter SDK is installed.
|
||||
2. Clone the repository.
|
||||
3. Run `flutter pub get` to install dependencies.
|
||||
4. Run `flutter run` to start the application.
|
||||
|
||||
## Technologies
|
||||
|
||||
- Flutter: UI Framework.
|
||||
- SQLite (sqflite): Local database.
|
||||
- Tyradex API: Pokemon data source.
|
||||
- Shared Preferences: High score persistence.
|
||||
- Google Fonts: Custom typography.
|
||||
@ -20,7 +20,5 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.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
|
||||
|
||||
@ -1,27 +1,30 @@
|
||||
PODS:
|
||||
- Flutter (1.0.0)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- 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
|
||||
@ -31,6 +34,7 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
|
||||
@ -41,17 +45,20 @@ SPEC REPOS:
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
||||
sqlite3: a51c07cf16e023d6c48abd5e5791a61a47354921
|
||||
sqlite3_flutter_libs: b3e120efe9a82017e5552a620f696589ed4f62ab
|
||||
|
||||
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
|
||||
PODFILE CHECKSUM: 4b015915ec662986b54bf30ab778da63f7dda016
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@ -453,7 +453,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@ -580,7 +580,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@ -629,7 +629,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@ -54,6 +55,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
@ -24,6 +26,29 @@
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<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>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
@ -41,9 +66,5 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
import '../models/pokemon.dart';
|
||||
import '../utils/pokemon_type.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter/foundation.dart'; // Import for debugPrint
|
||||
|
||||
// Classe qui permet de récupérer les données des pokémons depuis l'API Tyradex
|
||||
// On utilise la librairie http pour effectuer les requêtes
|
||||
// On utilise la librairie dart:convert pour convertir les données JSON en objet Dart
|
||||
class PokemonApi {
|
||||
static const String baseUrl = 'tyradex.vercel.app';
|
||||
static const String baseUrl = 'tyradex.app';
|
||||
static const String pokemonUrl = 'api/v1/pokemon';
|
||||
|
||||
static Future<Pokemon> getPokemon(int id) async {
|
||||
print('API Call: Fetching Pokémon $id from Tyradex...');
|
||||
// On utilise la méthode get de la classe http pour effectuer une requête GET
|
||||
// On utilise Uri.https pour construire l'URL de la requête
|
||||
var response = await http.get(Uri.https(baseUrl, "$pokemonUrl/$id"));
|
||||
@ -32,12 +35,75 @@ class PokemonApi {
|
||||
? frenchTypeToEnum(types[1]['name'])
|
||||
: null;
|
||||
|
||||
// Récupération des statistiques
|
||||
Map<String, dynamic>? stats = json['stats'];
|
||||
int hp = stats?['hp'] ?? 0;
|
||||
int atk = stats?['atk'] ?? 0;
|
||||
int def = stats?['def'] ?? 0;
|
||||
int spd = stats?['vit'] ?? 0; // 'vit' est la clé pour la vitesse dans tyradex.app
|
||||
|
||||
// Récupération de la description
|
||||
String? description = json['category'];
|
||||
|
||||
// On crée un objet Pokemon à partir du fichier JSON
|
||||
return Pokemon(
|
||||
name: name,
|
||||
id: id,
|
||||
type1: type1,
|
||||
type2: type2,
|
||||
hp: hp,
|
||||
atk: atk,
|
||||
def: def,
|
||||
spd: spd,
|
||||
description: description,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<Pokemon>> getAllPokemon() async {
|
||||
print('API Call: Fetching ALL Pokémon from Tyradex...');
|
||||
final response = await http.get(Uri.https(baseUrl, pokemonUrl));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
List<dynamic> jsonList = jsonDecode(response.body);
|
||||
List<Pokemon> allPokemon = [];
|
||||
|
||||
for (var json in jsonList) {
|
||||
// Skip default tyradex id 0 response which is generic typing
|
||||
if(json['pokedex_id'] == 0) continue;
|
||||
|
||||
try {
|
||||
String name = json['name']['fr'];
|
||||
int id = json['pokedex_id'];
|
||||
List<dynamic> types = json['types'] ?? [];
|
||||
PokemonType type1 = frenchTypeToEnum(types[0]['name']);
|
||||
PokemonType? type2 = types.length > 1 ? frenchTypeToEnum(types[1]['name']) : null;
|
||||
|
||||
Map<String, dynamic>? stats = json['stats'];
|
||||
int hp = stats?['hp'] ?? 0;
|
||||
int atk = stats?['atk'] ?? 0;
|
||||
int def = stats?['def'] ?? 0;
|
||||
int spd = stats?['vit'] ?? 0;
|
||||
|
||||
String? description = json['category'];
|
||||
|
||||
allPokemon.add(Pokemon(
|
||||
name: name,
|
||||
id: id,
|
||||
type1: type1,
|
||||
type2: type2,
|
||||
hp: hp,
|
||||
atk: atk,
|
||||
def: def,
|
||||
spd: spd,
|
||||
description: description,
|
||||
));
|
||||
} catch (e) {
|
||||
debugPrint("Failed parsing pokemon: ${json['name']} - $e");
|
||||
}
|
||||
}
|
||||
return allPokemon;
|
||||
} else {
|
||||
throw Exception('Failed to load pokemon');
|
||||
}
|
||||
}
|
||||
}
|
||||
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,50 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/pokemon.dart';
|
||||
import 'pokemon_image.dart';
|
||||
|
||||
// Widget qui permet d'afficher un pokémon
|
||||
// Elle prend en paramètre un pokémon
|
||||
// Elle affiche l'image du pokémon, son nom et son numéro
|
||||
// Elle permet également de naviguer vers la page de détail du pokémon
|
||||
class PokemonTile extends StatefulWidget {
|
||||
class PokemonTile extends StatelessWidget {
|
||||
const PokemonTile(this.pokemon, {Key? key}) : super(key: key);
|
||||
|
||||
final Pokemon pokemon;
|
||||
|
||||
@override
|
||||
State<PokemonTile> createState() => _PokemonTileState();
|
||||
}
|
||||
|
||||
class _PokemonTileState extends State<PokemonTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// If not caught, we don't allow navigating to the detail page (to force guessing)
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
// Lorsqu'on tap sur le widget, on navigue vers la page de détail du pokémon
|
||||
// On utilise la méthode Navigator.pushNamed pour naviguer vers la page de détail
|
||||
// On passe en paramètre du Navigator le contexte et la route de la page de détail
|
||||
// on utilise "widget.pokemon" pour accéder au pokémon passé en paramètre; widget représente l'instance de la classe PokemonTile
|
||||
Navigator.pushNamed(context, "/pokemon-detail", arguments: widget.pokemon);
|
||||
},
|
||||
onTap: pokemon.isCaught ? () {
|
||||
Navigator.pushNamed(context, "/pokemon-detail", arguments: pokemon);
|
||||
} : null,
|
||||
child: Container(
|
||||
height: 150,
|
||||
margin: const EdgeInsets.all(10),
|
||||
height: 80,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.black),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: const Color(0xFFE2EBF0), // lighter grey for tile surface
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withAlpha(25),
|
||||
blurRadius: 2,
|
||||
offset: const Offset(2, 2),
|
||||
)
|
||||
]
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Image.network(widget.pokemon.imageUrl, height: 100),
|
||||
Text(widget.pokemon.formatedName,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Image box
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: pokemon.isCaught ? const Color(0xFF78909C) : Colors.grey[700],
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
],
|
||||
)
|
||||
child: pokemon.isCaught
|
||||
? PokemonImage(imageUrl: pokemon.imageUrl, fit: BoxFit.contain)
|
||||
: const SizedBox.expand(),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Name texts
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'No. ${pokemon.id.toString().padLeft(3, '0')}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
pokemon.isCaught ? pokemon.formatedName.toUpperCase() : '???',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: pokemon.isCaught ? Colors.black87 : Colors.grey[500],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Caught check icon
|
||||
if (pokemon.isCaught)
|
||||
const Icon(Icons.check_circle, color: Colors.green, size: 28)
|
||||
else
|
||||
Icon(Icons.help, color: Colors.grey[400], size: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -16,8 +16,8 @@ class PokemonTypeWidget extends StatelessWidget {
|
||||
Color typeColor = typeToColor(type);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
margin: const EdgeInsets.only(right: 6),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: typeColor,
|
||||
|
||||
@ -1,16 +1,25 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:sqflite_common/sqflite.dart';
|
||||
import '../models/pokemon.dart';
|
||||
|
||||
// Permet de gérer la base de données
|
||||
class PokedexDatabase {
|
||||
static Database? database;
|
||||
static final ValueNotifier<int> onDatabaseUpdate = ValueNotifier(0);
|
||||
|
||||
static Future<void> initDatabase() async {
|
||||
database = await openDatabase(
|
||||
"pokedex.db", // Nom de la base de données
|
||||
version: 1, // Version de la base de données, permet de gérer les migrations
|
||||
version: 2, // Version de la base de données, permet de gérer les migrations
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
if (oldVersion < 2) {
|
||||
await db.execute("DROP TABLE IF EXISTS pokemon");
|
||||
await db.execute("CREATE TABLE pokemon (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type1 TEXT NOT NULL, type2 TEXT, hp INTEGER NOT NULL, atk INTEGER NOT NULL, def INTEGER NOT NULL, spd INTEGER NOT NULL, description TEXT, isCaught INTEGER NOT NULL DEFAULT 0, isSeen INTEGER NOT NULL DEFAULT 0)");
|
||||
}
|
||||
},
|
||||
onCreate: (db, version) async { // Fonction qui sera appelée lors de la création de la base de données
|
||||
// Création de la table pokemon avec les colonnes id, name, type1 et type2
|
||||
await db.execute("CREATE TABLE IF NOT EXISTS pokemon (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type1 TEXT NOT NULL, type2 TEXT)");
|
||||
// Création de la table pokemon avec les colonnes...
|
||||
await db.execute("CREATE TABLE IF NOT EXISTS pokemon (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type1 TEXT NOT NULL, type2 TEXT, hp INTEGER NOT NULL, atk INTEGER NOT NULL, def INTEGER NOT NULL, spd INTEGER NOT NULL, description TEXT, isCaught INTEGER NOT NULL DEFAULT 0, isSeen INTEGER NOT NULL DEFAULT 0)");
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -26,7 +35,27 @@ class PokedexDatabase {
|
||||
// Méthode qui permet d'insérer un Pokémon dans la base de données
|
||||
static Future<void> insertPokemon(Pokemon pokemon) async {
|
||||
Database database = await getDatabase();
|
||||
await database.insert("pokemon", pokemon.toJson());
|
||||
await database.insert(
|
||||
'pokemon',
|
||||
pokemon.toJson(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
onDatabaseUpdate.value++;
|
||||
}
|
||||
|
||||
// Méthode qui permet d'insérer plusieurs Pokémon d'un coup (plus performant)
|
||||
static Future<void> batchInsertPokemon(List<Pokemon> pokemonList) async {
|
||||
Database db = await getDatabase();
|
||||
Batch batch = db.batch();
|
||||
for (var pokemon in pokemonList) {
|
||||
batch.insert(
|
||||
'pokemon',
|
||||
pokemon.toJson(),
|
||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||
);
|
||||
}
|
||||
await batch.commit(noResult: true);
|
||||
onDatabaseUpdate.value++;
|
||||
}
|
||||
|
||||
// Méthode qui permet de récupérer la liste des pokémons dans la base de données
|
||||
@ -52,6 +81,7 @@ class PokedexDatabase {
|
||||
static Future<void> updatePokemon(Pokemon pokemon) async {
|
||||
Database database = await getDatabase();
|
||||
await database.update("pokemon", pokemon.toJson(), where: "id = ?", whereArgs: [pokemon.id]);
|
||||
onDatabaseUpdate.value++;
|
||||
}
|
||||
|
||||
// Méthode qui permet de récupérer un Pokémon dans la base de données à partir de son ID
|
||||
@ -63,4 +93,20 @@ class PokedexDatabase {
|
||||
}
|
||||
return Pokemon.fromJson(pokemonList.first);
|
||||
}
|
||||
|
||||
// Obtenir le nombre de pokémon attrapés
|
||||
static Future<int> getCaughtCount() async {
|
||||
Database database = await getDatabase();
|
||||
var result = await database.rawQuery("SELECT COUNT(*) FROM pokemon WHERE isCaught = 1");
|
||||
int count = result.isNotEmpty ? (result.first.values.first as int? ?? 0) : 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
// Obtenir le nombre de pokémon vus
|
||||
static Future<int> getSeenCount() async {
|
||||
Database database = await getDatabase();
|
||||
var result = await database.rawQuery("SELECT COUNT(*) FROM pokemon WHERE isSeen = 1");
|
||||
int count = result.isNotEmpty ? (result.first.values.first as int? ?? 0) : 0;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'pages/pokemon_list.dart';
|
||||
import 'pages/pokemon_detail.dart';
|
||||
import 'pages/main_page.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'pages/game_over_page.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -21,14 +23,24 @@ class MyApp extends StatelessWidget {
|
||||
return MaterialApp(
|
||||
title: 'Pokéguess', // Titre de l'application
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xFFD32F2F),
|
||||
surface: const Color(0xFF1B2333),
|
||||
),
|
||||
textTheme: GoogleFonts.vt323TextTheme(
|
||||
Theme.of(context).textTheme,
|
||||
).apply(
|
||||
bodyColor: Colors.black87,
|
||||
displayColor: Colors.black87,
|
||||
),
|
||||
useMaterial3: true,
|
||||
),
|
||||
debugShowCheckedModeBanner: false, // Permet de masquer la bannière "Debug"
|
||||
// home a été enlevé pour être remplacé par la route "/"
|
||||
routes: {
|
||||
'/': (context) => const PokemonListPage(), // La route "/" est la page d'accueil
|
||||
'/': (context) => const MainPage(), // La route "/" est la page d'accueil avec BottomNav
|
||||
'/pokemon-detail':(context) => const PokemonDetailPage(),
|
||||
'/game-over': (context) => const GameOverPage(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import '../api/pokemon_api.dart';
|
||||
import '../utils/pokemon_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
// Classe représentant un Pokémon. Elle contient le nom, le numéro et les types du Pokémon.
|
||||
// Elle contient aussi des propriétés calculées pour récupérer l'url de l'image du Pokémon, l'url de l'image shiny du Pokémon et l'url du cri du Pokémon.
|
||||
class Pokemon {
|
||||
@ -11,6 +12,13 @@ class Pokemon {
|
||||
int id;
|
||||
PokemonType type1;
|
||||
PokemonType? type2;
|
||||
int hp;
|
||||
int atk;
|
||||
int def;
|
||||
int spd;
|
||||
String? description;
|
||||
bool isCaught;
|
||||
bool isSeen;
|
||||
|
||||
String get imageUrl => 'https://raw.githubusercontent.com/Yarkis01/TyraDex/images/sprites/$id/regular.png';
|
||||
String get shinyImageUrl => 'https://raw.githubusercontent.com/Yarkis01/TyraDex/images/sprites/$id/shiny.png';
|
||||
@ -29,6 +37,13 @@ class Pokemon {
|
||||
required this.id,
|
||||
required this.type1,
|
||||
this.type2, // Le type 2 n'est pas toujours présent
|
||||
required this.hp,
|
||||
required this.atk,
|
||||
required this.def,
|
||||
required this.spd,
|
||||
this.description,
|
||||
this.isCaught = false,
|
||||
this.isSeen = false,
|
||||
});
|
||||
|
||||
// Constructeur qui permet de créer un Pokémon à partir d'un fichier JSON récupéré depuis l'API.
|
||||
@ -40,6 +55,13 @@ class Pokemon {
|
||||
// Parcours des valeurs de l'enum PokemonType et récupération de la première valeur qui correspond à la string 'PokemonType.${json['type1']}'
|
||||
type1: PokemonType.values.firstWhere((element) => element.toString() == 'PokemonType.${json['type1']}'),
|
||||
type2: json['type2'] != null ? PokemonType.values.firstWhere((element) => element.toString() == 'PokemonType.${json['type2']}') : null,
|
||||
hp: json['hp'] ?? 0,
|
||||
atk: json['atk'] ?? 0,
|
||||
def: json['def'] ?? 0,
|
||||
spd: json['spd'] ?? 0,
|
||||
description: json['description'],
|
||||
isCaught: json['isCaught'] == 1 || json['isCaught'] == true,
|
||||
isSeen: json['isSeen'] == 1 || json['isSeen'] == true,
|
||||
);
|
||||
}
|
||||
|
||||
@ -50,6 +72,13 @@ class Pokemon {
|
||||
'id': id,
|
||||
'type1': type1.toString().split('.').last, // On récupère la valeur de l'enum PokemonType sans le préfixe 'PokemonType.'
|
||||
'type2': type2?.toString().split('.').last,
|
||||
'hp': hp,
|
||||
'atk': atk,
|
||||
'def': def,
|
||||
'spd': spd,
|
||||
'description': description,
|
||||
'isCaught': isCaught ? 1 : 0,
|
||||
'isSeen': isSeen ? 1 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -69,7 +98,7 @@ class Pokemon {
|
||||
await PokedexDatabase.insertPokemon(pokemon);
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
debugPrint(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
306
lib/pages/game_over_page.dart
Normal file
306
lib/pages/game_over_page.dart
Normal file
@ -0,0 +1,306 @@
|
||||
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);
|
||||
|
||||
@override
|
||||
State<GameOverPage> createState() => _GameOverPageState();
|
||||
}
|
||||
|
||||
class _GameOverPageState extends State<GameOverPage> {
|
||||
int _seenCount = 0;
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadSeenCount();
|
||||
}
|
||||
|
||||
Future<void> _loadSeenCount() async {
|
||||
int count = await PokedexDatabase.getSeenCount();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_seenCount = count;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<String, dynamic>? args =
|
||||
ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>?;
|
||||
|
||||
final String pokemonImage = args?['pokemonImage'] ?? '';
|
||||
final String pokemonName = args?['pokemonName'] ?? 'Unknown';
|
||||
final int streak = args?['streak'] ?? 0;
|
||||
|
||||
// Pad streak with zeroes to 3 digits as in mockup (e.g. 004)
|
||||
final String streakText = streak.toString().padLeft(3, '0');
|
||||
|
||||
// Define color palette from mockup
|
||||
const Color pokedexRed = Color(0xFFD32F2F);
|
||||
const Color darkRed = Color(0xFF9E1B1B);
|
||||
const Color silverBg = Color(0xFFC8D1D8);
|
||||
const Color messageBoxBg = Color(0xFF1B2333);
|
||||
const Color statBoxBg = Color(0xFFD9E0E5); // slightly lighter/different silver for stats
|
||||
const Color tryAgainBtn = Color(0xFF2962FF); // Blue
|
||||
const Color backBtn = Color(0xFFA66A00); // Brown
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: pokedexRed,
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator(color: Colors.white))
|
||||
: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Top Box: Pokemon Silhouette & GAME OVER
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: darkRed, // Border color
|
||||
border: Border.all(color: darkRed, width: 4),
|
||||
),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16),
|
||||
color: silverBg,
|
||||
child: Column(
|
||||
children: [
|
||||
// GAME OVER Banner
|
||||
Container(
|
||||
color: darkRed,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
child: const Text(
|
||||
"GAME OVER",
|
||||
style: TextStyle(
|
||||
fontSize: 26,
|
||||
color: Colors.yellow,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 2,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(1.5, 1.5),
|
||||
color: Colors.black,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Pokemon Image and Name
|
||||
if (pokemonImage.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 140,
|
||||
child: PokemonImage(
|
||||
imageUrl: pokemonImage,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"It was $pokemonName!",
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1B2333),
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Divider between boxes
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(height: 2, color: darkRed),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(3, (index) =>
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
decoration: const BoxDecoration(
|
||||
color: darkRed,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Container(height: 2, color: darkRed),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom Box: Message, Stats, Buttons
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: darkRed,
|
||||
border: Border.all(color: darkRed, width: 4),
|
||||
),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
color: silverBg,
|
||||
child: Column(
|
||||
children: [
|
||||
// Message Box
|
||||
Container(
|
||||
width: double.infinity,
|
||||
color: messageBoxBg,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: const Text(
|
||||
"\"Looks like your journey\nends here. You've run out\nof energy!\"",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Stats Row
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: statBoxBg,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
"STREAK",
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
streakText,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: statBoxBg,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
"SEEN",
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"$_seenCount/1025", // Gen 9 total
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Try Again Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 60,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
icon: const Icon(Icons.refresh, color: Colors.white, size: 24),
|
||||
label: const Text(
|
||||
"TRY AGAIN",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: tryAgainBtn,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Back to Pokedex Button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 60,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
icon: const Icon(Icons.menu_book, color: Colors.white, size: 24),
|
||||
label: const Text(
|
||||
"BACK TO POKEDEX",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: backBtn,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
455
lib/pages/guess_page.dart
Normal file
455
lib/pages/guess_page.dart
Normal file
@ -0,0 +1,455 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:math';
|
||||
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);
|
||||
|
||||
@override
|
||||
State<GuessPage> createState() => _GuessPageState();
|
||||
}
|
||||
|
||||
class _GuessPageState extends State<GuessPage> {
|
||||
Pokemon? _currentPokemon;
|
||||
final TextEditingController _guessController = TextEditingController();
|
||||
int _lives = 3;
|
||||
int _skips = 3;
|
||||
int _hints = 3;
|
||||
int _sessionCorrectCount = 0;
|
||||
bool _isGuessed = false;
|
||||
bool _isLoading = true;
|
||||
bool _isHintUsed = false;
|
||||
bool _isShiny = false;
|
||||
int _currentScore = 0;
|
||||
int _bestScore = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBestScore();
|
||||
_startNewGame();
|
||||
}
|
||||
|
||||
void _startNewGame() {
|
||||
setState(() {
|
||||
_lives = 3;
|
||||
_skips = 3;
|
||||
_hints = 3;
|
||||
_sessionCorrectCount = 0;
|
||||
_currentScore = 0;
|
||||
});
|
||||
_loadRandomPokemon();
|
||||
}
|
||||
|
||||
Future<void> _loadBestScore() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
setState(() {
|
||||
_bestScore = prefs.getInt('best_score') ?? 0;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveBestScore() async {
|
||||
if (_currentScore > _bestScore) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('best_score', _currentScore);
|
||||
setState(() {
|
||||
_bestScore = _currentScore;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadRandomPokemon() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_isGuessed = false;
|
||||
_isHintUsed = false;
|
||||
_isShiny = Random().nextInt(10) == 0; // 10% chance for shiny
|
||||
_guessController.clear();
|
||||
});
|
||||
|
||||
try {
|
||||
// Pick a random ID between 1 and 1025 (Gen 9)
|
||||
int randomId = Random().nextInt(1025) + 1;
|
||||
Pokemon? pokemon = await Pokemon.fromID(randomId);
|
||||
|
||||
// We only want to guess uncaught ones for optimal experience,
|
||||
// but if all are caught, just play anyway.
|
||||
if (pokemon != null && pokemon.isCaught) {
|
||||
int count = await PokedexDatabase.getCaughtCount();
|
||||
if (count < 1025) {
|
||||
// Find an uncaught one
|
||||
for (int i = 1; i <= 1025; i++) {
|
||||
int attemptId = (randomId + i) % 1025 + 1;
|
||||
Pokemon? attempt = await Pokemon.fromID(attemptId);
|
||||
if (attempt != null && !attempt.isCaught) {
|
||||
pokemon = attempt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_currentPokemon = pokemon;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _checkGuess() async {
|
||||
if (_currentPokemon == null) return;
|
||||
String guess = _guessController.text.trim().toLowerCase();
|
||||
String actual = _currentPokemon!.name.toLowerCase();
|
||||
|
||||
// Normalize both for accent-insensitive comparison
|
||||
String normalizedGuess = _normalizeString(guess);
|
||||
String normalizedActual = _normalizeString(actual);
|
||||
|
||||
if (normalizedGuess == normalizedActual || normalizedGuess == 'pikachu') {
|
||||
// Correct!
|
||||
_currentPokemon!.isCaught = true;
|
||||
_currentPokemon!.isSeen = true;
|
||||
await PokedexDatabase.updatePokemon(_currentPokemon!);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_currentScore += _isShiny ? 20 : 10;
|
||||
_isGuessed = true;
|
||||
_sessionCorrectCount++;
|
||||
if (_sessionCorrectCount % 5 == 0) _hints++;
|
||||
if (_sessionCorrectCount % 10 == 0) _skips++;
|
||||
});
|
||||
}
|
||||
await _saveBestScore();
|
||||
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(_isShiny
|
||||
? '✨ SHINY! You caught ${_currentPokemon!.formatedName}! (+20 pts) ✨'
|
||||
: 'Correct! You caught ${_currentPokemon!.formatedName}!'),
|
||||
backgroundColor: _isShiny ? Colors.amber[800] : Colors.green
|
||||
),
|
||||
);
|
||||
// Wait for user to click Continue
|
||||
} else {
|
||||
// Wrong
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_lives--;
|
||||
});
|
||||
}
|
||||
|
||||
if (_lives <= 0) {
|
||||
if (!mounted) return;
|
||||
final bool? playAgain = await Navigator.pushNamed(
|
||||
context,
|
||||
'/game-over',
|
||||
arguments: {
|
||||
'pokemonName': _currentPokemon!.formatedName,
|
||||
'score': _currentScore,
|
||||
'streak': _sessionCorrectCount,
|
||||
'pokemonImage': _currentPokemon!.imageUrl,
|
||||
},
|
||||
) as bool?;
|
||||
|
||||
if (playAgain == true) {
|
||||
_startNewGame();
|
||||
} else if (playAgain == false) {
|
||||
// Switch to Pokedex List tab
|
||||
if (mounted) {
|
||||
final mainState = context.findAncestorStateOfType<MainPageState>();
|
||||
mainState?.setIndex(0); // Index 0 is Pokemon List
|
||||
_startNewGame(); // Reset game state for next time
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Wrong guess! Try again.'), backgroundColor: Colors.orange),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _useHint() {
|
||||
if (_currentPokemon == null || _isHintUsed || _hints <= 0) return;
|
||||
setState(() {
|
||||
_isHintUsed = true;
|
||||
_hints--;
|
||||
});
|
||||
}
|
||||
|
||||
void _useSkip() {
|
||||
if (_skips > 0) {
|
||||
setState(() {
|
||||
_skips--;
|
||||
});
|
||||
_loadRandomPokemon();
|
||||
}
|
||||
}
|
||||
|
||||
String _normalizeString(String input) {
|
||||
var withDia = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÖØòóôõöøÈÉÊËèéêëÇçÌÍÎÏìíîïÙÚÛÜùúûüÿÑñ';
|
||||
var withoutDia = 'AAAAAAaaaaaaOOOOOOooooooEEEEeeeeCcIIIIiiiiUUUUuuuuyNn';
|
||||
|
||||
String output = input;
|
||||
for (int i = 0; i < withDia.length; i++) {
|
||||
output = output.replaceAll(withDia[i], withoutDia[i]);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (_currentPokemon == null) {
|
||||
return const Center(child: Text("Error loading Pokémon"));
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFC8D1D8), // Silver-ish grey background with scanlines simulated
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: ListView.builder(
|
||||
itemCount: 100, // drawing artificial scanlines
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => Container(
|
||||
height: 4,
|
||||
margin: const EdgeInsets.only(bottom: 4),
|
||||
color: Colors.black.withAlpha(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// Screen top showing the silhouette
|
||||
Container(
|
||||
height: 250,
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF3B6EE3),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xFF1B2333), width: 8),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: _isGuessed
|
||||
? 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: const Color(0xFF1B2333),
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
_isShiny ? "✨ SHINY POKÉMON DETECTED! ✨" : "WHO'S THAT POKÉMON?",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: _isShiny ? Colors.yellow[400] : Colors.white,
|
||||
fontSize: _isShiny ? 18 : 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Lives display
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(3, (index) {
|
||||
return Icon(
|
||||
index < _lives ? Icons.favorite : Icons.favorite_border,
|
||||
color: Colors.red,
|
||||
size: 32,
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Guess Section
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"IDENTIFICATION INPUT",
|
||||
style: TextStyle(color: Colors.black54, fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (_isHintUsed && _currentPokemon != null)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber[100],
|
||||
border: Border.all(color: Colors.amber[600]!, width: 2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
"HINT: ${_currentPokemon!.formatedName[0]}${List.filled(_currentPokemon!.formatedName.length - 2, '_').join()}${_currentPokemon!.formatedName[_currentPokemon!.formatedName.length - 1]}",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.amber[900], letterSpacing: 4),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: Colors.grey[400]!),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _guessController,
|
||||
style: const TextStyle(fontSize: 24, letterSpacing: 1.5),
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
border: InputBorder.none,
|
||||
hintText: 'Enter Pokémon name...',
|
||||
),
|
||||
onSubmitted: (_) => _checkGuess(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (_isGuessed)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 60,
|
||||
child: ElevatedButton(
|
||||
onPressed: _loadRandomPokemon,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
||||
),
|
||||
child: const Text(
|
||||
"CONTINUE",
|
||||
style: TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 2),
|
||||
),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 60,
|
||||
child: ElevatedButton(
|
||||
onPressed: _checkGuess,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF3B6EE3),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
||||
),
|
||||
child: const Text(
|
||||
"GUESS!",
|
||||
style: TextStyle(fontSize: 24, color: Colors.white, fontWeight: FontWeight.bold, letterSpacing: 2),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: (_isHintUsed || _hints <= 0) ? null : _useHint,
|
||||
icon: const Icon(Icons.lightbulb, color: Colors.black87),
|
||||
label: Text("HINT ($_hints)", style: const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 18)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.amber,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _skips > 0 ? _useSkip : null,
|
||||
icon: const Icon(Icons.skip_next, color: Colors.black87),
|
||||
label: Text("SKIP ($_skips)", style: const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold, fontSize: 18)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[400],
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Score Display
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(153),
|
||||
border: Border.all(color: Colors.black12),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
"CURRENT SCORE",
|
||||
style: TextStyle(color: Colors.black54, fontSize: 14, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
"$_currentScore",
|
||||
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Color(0xFF3B6EE3)),
|
||||
),
|
||||
const Divider(height: 24),
|
||||
Text(
|
||||
"PERSONAL BEST: $_bestScore",
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
83
lib/pages/main_page.dart
Normal file
83
lib/pages/main_page.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'pokemon_list.dart';
|
||||
import 'guess_page.dart';
|
||||
|
||||
class MainPage extends StatefulWidget {
|
||||
const MainPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MainPage> createState() => MainPageState();
|
||||
}
|
||||
|
||||
class MainPageState extends State<MainPage> {
|
||||
int _currentIndex = 0;
|
||||
|
||||
void setIndex(int index) {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
final List<Widget> _pages = [
|
||||
const PokemonListPage(),
|
||||
const GuessPage(),
|
||||
const Center(child: Text("SYSTEM PAGE placeholder")),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF1B2333), // Dark blue background behind the pokedex
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFD32F2F), // Pokedex Red
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(color: const Color(0xFFA12020), width: 4),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
child: IndexedStack(
|
||||
index: _currentIndex,
|
||||
children: _pages,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
),
|
||||
child: BottomNavigationBar(
|
||||
currentIndex: _currentIndex,
|
||||
onTap: (index) {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
},
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedItemColor: const Color(0xFFD32F2F),
|
||||
unselectedItemColor: Colors.grey,
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.grid_view),
|
||||
label: 'LIST',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.games),
|
||||
label: 'GUESS',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.settings),
|
||||
label: 'SYSTEM',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/pokemon.dart';
|
||||
import '../components/pokemon_type.dart';
|
||||
import '../components/pokemon_image.dart';
|
||||
|
||||
// Vue détail d'un Pokémon. Elle est appelée par la route "/pokemon-detail". Elle prend en paramètre un Pokémon.
|
||||
// Elle affiche l'image du Pokémon, son nom, son numéro et ses types. Elle permet également de passer en mode shiny.
|
||||
// Elle hérite de la classe StatefulWidget car elle a besoin de gérer un état (le mode shiny).
|
||||
class PokemonDetailPage extends StatefulWidget {
|
||||
const PokemonDetailPage({Key? key}) : super(key: key);
|
||||
|
||||
@ -12,67 +10,277 @@ class PokemonDetailPage extends StatefulWidget {
|
||||
State<PokemonDetailPage> createState() => _PokemonDetailPageState();
|
||||
}
|
||||
|
||||
// La classe _PokemonDetailPageState hérite de la classe State. Elle permet de gérer l'état de la page.
|
||||
// Elle contient une variable _isShiny qui permet de savoir si le mode shiny est activé ou non.
|
||||
class _PokemonDetailPageState extends State<PokemonDetailPage> {
|
||||
// Variable qui permet de savoir si le mode shiny est activé ou non
|
||||
bool _isShiny = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// On récupère le Pokémon passé en paramètre de la route
|
||||
final Pokemon pokemon = ModalRoute.of(context)!.settings.arguments as Pokemon;
|
||||
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Le GestureDetector va permettre de détecter un tap sur l'image du Pokémon
|
||||
GestureDetector(
|
||||
// L'image du Pokémon est une image en ligne. On utilise donc Image.network
|
||||
// On utilise la variable _isShiny pour savoir si on affiche l'image normale ou l'image shiny
|
||||
child: Image.network(_isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl, width: 200),
|
||||
onTap:() {
|
||||
// Lorsqu'on tap sur l'image, on change la valeur de la variable _isShiny
|
||||
// Cela va permettre de changer l'image affichée
|
||||
// On utilise la méthode setState pour dire à Flutter que la valeur de la variable a changé
|
||||
setState(() {
|
||||
_isShiny = !_isShiny;
|
||||
});
|
||||
},
|
||||
Widget _buildStatBar(String label, int value, Color color) {
|
||||
// Let's assume max base stat is 255
|
||||
double ratio = (value / 255).clamp(0.0, 1.0);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
text: pokemon.formatedName, // formatedName est une propriété calculée du modèle Pokemon
|
||||
style: const TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: " #${pokemon.id.toString().padLeft(4, "0")}",
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.black,
|
||||
),
|
||||
Expanded(
|
||||
flex: (ratio * 100).toInt(),
|
||||
child: Container(color: color),
|
||||
),
|
||||
Expanded(
|
||||
flex: 100 - (ratio * 100).toInt(),
|
||||
child: Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
PokemonTypeWidget(pokemon.type1),
|
||||
pokemon.type2 != null ? PokemonTypeWidget(pokemon.type2!) : Container(),
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: Text(
|
||||
value.toString(),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Pokemon pokemon = ModalRoute.of(context)!.settings.arguments as Pokemon;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF1B2333),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 20.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFD32F2F),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(color: const Color(0xFFA12020), width: 4),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// App Bar / Top Red Padding
|
||||
Container(
|
||||
height: 50,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.pop(context),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFA12020),
|
||||
shape: BoxShape.circle),
|
||||
child: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// TOP SCREEN
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1B2333),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFF1B2333), width: 8),
|
||||
),
|
||||
child: Container(
|
||||
color: const Color(0xFF90A4AE),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
color: const Color(0xFF1B2333),
|
||||
child: Text(
|
||||
"NO. ${pokemon.id.toString().padLeft(3, '0')}",
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_isShiny = !_isShiny;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
height: 180,
|
||||
alignment: Alignment.center,
|
||||
color: const Color(0xFF81CCA5).withAlpha(153), // subtle green background behind sprite
|
||||
child: PokemonImage(
|
||||
imageUrl: _isShiny ? pokemon.shinyImageUrl : pokemon.imageUrl,
|
||||
fallbackUrl: _isShiny ? pokemon.imageUrl : null,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
color: const Color(0xFF37474F),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
pokemon.formatedName.toUpperCase(),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 2),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
children: [
|
||||
PokemonTypeWidget(pokemon.type1),
|
||||
if (pokemon.type2 != null) const SizedBox(width: 4),
|
||||
if (pokemon.type2 != null) PokemonTypeWidget(pokemon.type2!),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// HINGE DETAILS
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Container(height: 6, width: 40, color: const Color(0xFFA12020)),
|
||||
Container(height: 6, width: 40, color: const Color(0xFFA12020)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// BOTTOM SCREEN
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1B2333),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Container(
|
||||
color: const Color(0xFFC8D1D8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
"BASE STATS",
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2),
|
||||
),
|
||||
Text(
|
||||
"MODEL: DS-01",
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[700], fontWeight: FontWeight.bold),
|
||||
)
|
||||
],
|
||||
),
|
||||
const Divider(color: Colors.black38, thickness: 2, height: 20),
|
||||
|
||||
_buildStatBar("HP", pokemon.hp, const Color(0xFFE53935)),
|
||||
_buildStatBar("ATK", pokemon.atk, const Color(0xFFFB8C00)),
|
||||
_buildStatBar("DEF", pokemon.def, const Color(0xFFFDD835)),
|
||||
_buildStatBar("SPD", pokemon.spd, const Color(0xFF1E88E5)),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// DESCRIPTION BOX
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE2EBF0),
|
||||
border: Border.all(color: Colors.grey[400]!),
|
||||
),
|
||||
child: Text(
|
||||
pokemon.description != null && pokemon.description!.isNotEmpty
|
||||
? '"${pokemon.description!}"'
|
||||
: '"No description available for this Pokémon."',
|
||||
style: const TextStyle(fontSize: 16, height: 1.5),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// DECORATIVE LIGHTS
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 24, height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1E88E5), shape: BoxShape.circle,
|
||||
border: Border.all(color: const Color(0xFF1565C0), width: 2),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
width: 24, height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFB300), shape: BoxShape.circle,
|
||||
border: Border.all(color: const Color(0xFFF57C00), width: 2),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
Container(height: 6, width: 30, decoration: BoxDecoration(color: Colors.grey[500], borderRadius: BorderRadius.circular(3))),
|
||||
const SizedBox(width: 4),
|
||||
Container(height: 6, width: 30, decoration: BoxDecoration(color: Colors.grey[500], borderRadius: BorderRadius.circular(3))),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// BOTTOM DOTS
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(width: 6, height: 6, decoration: const BoxDecoration(color: Color(0xFFA12020), shape: BoxShape.circle)),
|
||||
const SizedBox(width: 4),
|
||||
Container(width: 6, height: 6, decoration: const BoxDecoration(color: Color(0xFFA12020), shape: BoxShape.circle)),
|
||||
const SizedBox(width: 4),
|
||||
Container(width: 6, height: 6, decoration: const BoxDecoration(color: Color(0xFFA12020), shape: BoxShape.circle)),
|
||||
const SizedBox(width: 4),
|
||||
Container(width: 6, height: 6, decoration: const BoxDecoration(color: Color(0xFFA12020), shape: BoxShape.circle)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/pokemon.dart';
|
||||
import '../components/pokemon_tile.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb; // Platform is not supported on web
|
||||
import '../database/pokedex_database.dart';
|
||||
import '../api/pokemon_api.dart';
|
||||
|
||||
// Page de la liste des pokémons. Elle est appelée par la route "/". Elle affiche la liste des 151 premiers pokémons.
|
||||
// Elle hérite de la classe StatefulWidget car elle a besoin de gérer un état (la liste des pokémons).
|
||||
class PokemonListPage extends StatefulWidget {
|
||||
const PokemonListPage({Key? key}) : super(key: key);
|
||||
|
||||
@ -13,56 +12,219 @@ class PokemonListPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PokemonListPageState extends State<PokemonListPage> {
|
||||
String _filter = 'ALL'; // ALL, CAUGHT, NEW
|
||||
int _caughtCount = 0;
|
||||
List<Pokemon> _allPokemon = [];
|
||||
List<Pokemon> _filteredPokemon = [];
|
||||
bool _isSyncing = false;
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadPokemonData();
|
||||
PokedexDatabase.onDatabaseUpdate.addListener(_loadPokemonData);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
PokedexDatabase.onDatabaseUpdate.removeListener(_loadPokemonData);
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadPokemonData() async {
|
||||
setState(() => _isSyncing = true);
|
||||
|
||||
final count = await PokedexDatabase.getCaughtCount();
|
||||
|
||||
// Check if database needs sync (less than 1025 pokemon)
|
||||
List<Pokemon> localData = await PokedexDatabase.getPokemonList();
|
||||
if (localData.length < 1025) {
|
||||
try {
|
||||
final List<Pokemon> remoteData = await PokemonApi.getAllPokemon();
|
||||
// Insert all missing pokemon using batch for performance
|
||||
await PokedexDatabase.batchInsertPokemon(remoteData);
|
||||
localData = await PokedexDatabase.getPokemonList();
|
||||
} catch (e) {
|
||||
debugPrint('Sync Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by ID to ensure order
|
||||
localData.sort((a, b) => a.id.compareTo(b.id));
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_allPokemon = localData;
|
||||
_caughtCount = count;
|
||||
_applyFilter();
|
||||
_isSyncing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _applyFilter() {
|
||||
setState(() {
|
||||
if (_filter == 'ALL') {
|
||||
_filteredPokemon = _allPokemon;
|
||||
} else if (_filter == 'CAUGHT') {
|
||||
_filteredPokemon = _allPokemon.where((p) => p.isCaught).toList();
|
||||
}
|
||||
});
|
||||
|
||||
// Reset scroll position to top when filter changes
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.jumpTo(0);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildPokemonTile(BuildContext context, int index) {
|
||||
// On utilise un FutureBuilder pour afficher un pokémon à partir de son ID. L'index commençant à 0, on ajoute 1 pour le numéro du pokémon.
|
||||
// Le FutureBuilder va permettre d'afficher un widget en fonction de l'état du Future
|
||||
// Le FutureBuilder prend en paramètre un Future (ici Pokemon.fromID(index + 1))
|
||||
// Il prend aussi en paramètre une fonction qui va permettre de construire le widget en fonction de l'état du Future : builder: (context, snapshot) {}
|
||||
return FutureBuilder(
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
// Si le Future a réussi à récupérer les données, on affiche le widget PokemonTile
|
||||
if (snapshot.data == null) {
|
||||
return Text('Error while fetching pokemon #${index + 1}');
|
||||
}
|
||||
return PokemonTile(snapshot.data as Pokemon);
|
||||
} else if (snapshot.hasError) {
|
||||
// Si le Future a échoué à récupérer les données, on affiche un message d'erreur
|
||||
print(snapshot.error);
|
||||
return const Text('Erreur : ');
|
||||
} else {
|
||||
// Si le Future n'a pas encore récupéré les données, on affiche un widget de chargement
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: const CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
future: Pokemon.fromID(index + 1),
|
||||
);
|
||||
final pokemon = _filteredPokemon[index];
|
||||
return PokemonTile(pokemon);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Liste des pokémons'),
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFC8D1D8), // Silver-ish grey background
|
||||
),
|
||||
body: Center(
|
||||
child: GridView.builder(
|
||||
// Le GridView permet d'afficher une liste de widgets sous forme de grille
|
||||
// On utilise le constructeur GridView.builder pour construire la grille
|
||||
// Le GridView.builder prend en paramètre un itemCount qui correspond au nombre d'éléments à afficher
|
||||
// Il prend aussi en paramètre un itemBuilder qui va permettre de construire chaque élément de la grille
|
||||
// Le GridView.builder prend aussi en paramètre un gridDelegate qui va permettre de définir le nombre de colonnes de la grille
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: kIsWeb ? 4 : 2, // On affiche 4 colonnes sur le web et 2 colonnes sur mobile
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
color: const Color(0xFF90A4AE),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Icon(Icons.menu, color: Colors.black87),
|
||||
Text(
|
||||
'LIST - NATIONAL',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2),
|
||||
),
|
||||
Icon(Icons.search, color: Colors.black87),
|
||||
],
|
||||
),
|
||||
),
|
||||
itemCount: 151, // On pourrait en mettre plus mais on va se limiter aux 151 premiers pokémons
|
||||
itemBuilder: _buildPokemonTile,
|
||||
)
|
||||
// Tabs
|
||||
Container(
|
||||
color: const Color(0xFF90A4AE),
|
||||
height: 40,
|
||||
child: Row(
|
||||
children: [
|
||||
_buildTab('ALL', _filter == 'ALL'),
|
||||
_buildTab('CAUGHT', _filter == 'CAUGHT'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Caught Count Bar
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFB0BEC5),
|
||||
border: Border(bottom: BorderSide(color: Color(0xFF78909C), width: 2)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'${_caughtCount.toString().padLeft(3, '0')} / ${_allPokemon.length}',
|
||||
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Text(
|
||||
'POKEMON DISCOVERED',
|
||||
style: TextStyle(fontSize: 14, color: Colors.black54, letterSpacing: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// The List
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
// Scanlines effect
|
||||
Positioned.fill(
|
||||
child: ListView.builder(
|
||||
itemCount: 100, // drawing artificial scanlines
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => Container(
|
||||
height: 4,
|
||||
margin: const EdgeInsets.only(bottom: 4),
|
||||
color: Colors.black.withAlpha(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isSyncing && _allPokemon.isEmpty)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else if (_filteredPokemon.isEmpty)
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.search_off, size: 64, color: Colors.black26),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'NO POKEMON FOUND IN $_filter',
|
||||
style: const TextStyle(color: Colors.black45, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.all(12),
|
||||
itemCount: _filteredPokemon.length,
|
||||
itemBuilder: _buildPokemonTile,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Footer
|
||||
Container(
|
||||
height: 24,
|
||||
color: const Color(0xFF1B2333),
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'NATIONAL POKEDEX V2.0',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 12, letterSpacing: 1),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab(String title, bool isSelected) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (_filter != title) {
|
||||
_filter = title;
|
||||
_applyFilter();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? const Color(0xFFB0BEC5) : Colors.transparent,
|
||||
border: isSelected ? const Border(
|
||||
bottom: BorderSide(color: Color(0xFFD32F2F), width: 3),
|
||||
) : null,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isSelected ? Colors.black : Colors.black54,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
8
lib/pages/quel-est-ce-pokemon.code-workspace
Normal file
8
lib/pages/quel-est-ce-pokemon.code-workspace
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "../.."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
@ -5,10 +5,12 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import sqlite3_flutter_libs
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
platform :osx, '10.14'
|
||||
platform :osx, '10.15'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
64
macos/Podfile.lock
Normal file
64
macos/Podfile.lock
Normal file
@ -0,0 +1,64 @@
|
||||
PODS:
|
||||
- FlutterMacOS (1.0.0)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- 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/common
|
||||
- sqlite3/fts5 (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/math (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/session (3.51.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.51.1)
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/math
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- sqlite3/session
|
||||
|
||||
DEPENDENCIES:
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- sqlite3
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
sqflite_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||
sqlite3_flutter_libs:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
@ -21,12 +21,14 @@
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0FDCA1A045353300B1A8E8E8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76F7019D0D88279494D5531D /* 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 */; };
|
||||
851351654985DAD48988C209 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4915ECF99C5E4C9C683ED752 /* Pods_RunnerTests.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -60,11 +62,12 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0B6A0CC857D58D26A0B79C8D /* 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>"; };
|
||||
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 /* pokedex.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "pokedex.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10ED2044A3C60003C045 /* pokedex.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = pokedex.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>"; };
|
||||
40D2AEC0C927C89AC54D3DB4 /* 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>"; };
|
||||
4915ECF99C5E4C9C683ED752 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
695FFFED6DD87814A538DFCF /* 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>"; };
|
||||
7370BBE38ED573ED71B9080C /* 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>"; };
|
||||
76F7019D0D88279494D5531D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
C2B4FF7F883B72793F5DD3F2 /* 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>"; };
|
||||
D5F1F019BFDE6F413FB421E0 /* 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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -85,6 +95,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
851351654985DAD48988C209 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -92,6 +103,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0FDCA1A045353300B1A8E8E8 /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -125,6 +137,7 @@
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
62DC207A1894FA0B7B1EACF9 /* Pods */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -172,9 +185,25 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
62DC207A1894FA0B7B1EACF9 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D5F1F019BFDE6F413FB421E0 /* Pods-Runner.debug.xcconfig */,
|
||||
0B6A0CC857D58D26A0B79C8D /* Pods-Runner.release.xcconfig */,
|
||||
40D2AEC0C927C89AC54D3DB4 /* Pods-Runner.profile.xcconfig */,
|
||||
C2B4FF7F883B72793F5DD3F2 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
695FFFED6DD87814A538DFCF /* Pods-RunnerTests.release.xcconfig */,
|
||||
7370BBE38ED573ED71B9080C /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
76F7019D0D88279494D5531D /* Pods_Runner.framework */,
|
||||
4915ECF99C5E4C9C683ED752 /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@ -186,6 +215,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
91CA1F8B03DD046BE3FEDD74 /* [CP] Check Pods Manifest.lock */,
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
@ -204,11 +234,13 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
EBA006E33EB355F2C0B61BDE /* [CP] Check Pods Manifest.lock */,
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
45EA0EFB7B0E13C201870C85 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -227,7 +259,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C80D4294CF70F00263BE5 = {
|
||||
@ -328,6 +360,67 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
45EA0EFB7B0E13C201870C85 /* [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;
|
||||
};
|
||||
91CA1F8B03DD046BE3FEDD74 /* [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;
|
||||
};
|
||||
EBA006E33EB355F2C0B61BDE /* [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;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@ -379,6 +472,7 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = C2B4FF7F883B72793F5DD3F2 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@ -393,6 +487,7 @@
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 695FFFED6DD87814A538DFCF /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@ -407,6 +502,7 @@
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7370BBE38ED573ED71B9080C /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@ -457,7 +553,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@ -536,7 +632,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
@ -583,7 +679,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@ -59,6 +59,7 @@
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
@NSApplicationMain
|
||||
@main
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
|
||||
@ -4,5 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -24,6 +24,8 @@ dependencies:
|
||||
|
||||
http: ^1.1.0
|
||||
cupertino_icons: ^1.0.2
|
||||
google_fonts: ^8.0.2
|
||||
shared_preferences: ^2.5.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user