Commit 333c066b authored by Mahmoud Aglan's avatar Mahmoud Aglan

Complete Flutter mobile app — all screens, auth, API, animations

Full-featured guardian/participant mobile app for El Captain Sports ERP:
- Riverpod state management, GoRouter navigation with animated transitions
- Arabic-first RTL with English toggle, Cairo + Inter fonts
- OTP auth flow with Sanctum bearer tokens
- Dashboard with children cards, today's sessions, quick stats
- Participant detail with action grid (attendance, invoices, evaluations)
- Weekly schedule view with day selector
- Attendance history with circular rate indicator
- Invoice list with pay button and status badges
- Evaluation cards with criteria progress bars
- Events with registration, Shop product grid
- Messages with chat bubbles, Service requests with create modal
- Multi-step pre-registration wizard (guardian → child → review)
- Notification inbox with type-based icons
- Profile with language toggle and logout
- Everything animated (flutter_animate): fade-slide, scale-in, stagger
- Builds successfully for iOS (verified)
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parents
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "c9a6c484230f8b5e408ec57be1ef71dee1e77020"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
- platform: android
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
- platform: ios
create_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
base_revision: c9a6c484230f8b5e408ec57be1ef71dee1e77020
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
# el_captain_client
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
plugins {
id("com.android.application")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.alcaptain.el_captain_client"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.alcaptain.el_captain_client"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
flutter {
source = "../.."
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="el_captain_client"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
package com.alcaptain.el_captain_client
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
# This newDsl flag was added by the Flutter template
android.newDsl=false
# This builtInKotlin flag was added by the Flutter template
android.builtInKotlin=false
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "9.0.1" apply false
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
}
include(":app")
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
<?xml version="1.0" encoding="UTF-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>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
PODS:
- Firebase/CoreOnly (11.15.0):
- FirebaseCore (~> 11.15.0)
- Firebase/Messaging (11.15.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.15.0)
- firebase_core (3.15.2):
- Firebase/CoreOnly (= 11.15.0)
- Flutter
- firebase_messaging (15.2.10):
- Firebase/Messaging (= 11.15.0)
- firebase_core
- Flutter
- FirebaseCore (11.15.0):
- FirebaseCoreInternal (~> 11.15.0)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Logger (~> 8.1)
- FirebaseCoreInternal (11.15.0):
- "GoogleUtilities/NSData+zlib (~> 8.1)"
- FirebaseInstallations (11.15.0):
- FirebaseCore (~> 11.15.0)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.15.0):
- FirebaseCore (~> 11.15.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Reachability (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- nanopb (~> 3.30910.0)
- Flutter (1.0.0)
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleUtilities/AppDelegateSwizzler (8.1.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.2):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.2):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.2)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.2)
- GoogleUtilities/Reachability (8.1.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- image_picker_ios (0.0.1):
- Flutter
- nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- PromisesObjC (2.4.1)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES:
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS:
trunk:
- Firebase
- FirebaseCore
- FirebaseCoreInternal
- FirebaseInstallations
- FirebaseMessaging
- GoogleDataTransport
- GoogleUtilities
- nanopb
- PromisesObjC
EXTERNAL SOURCES:
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_messaging:
:path: ".symlinks/plugins/firebase_messaging/ios"
Flutter:
:path: Flutter
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 766ace00c6b10d8148408f329d10c4f051931850
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
COCOAPODS: 1.16.2
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
7930EA2BD395AB994D5E4E66 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 496900ECD3D00C358558EBA9 /* Pods_RunnerTests.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
A3AB4B5D8E528CCB594E3802 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C524F3BF6296D2D38CFC267 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3DB8325C4D0990D409BC106C /* 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>"; };
496900ECD3D00C358558EBA9 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5AFF599171DD7C3CCF1C0A76 /* 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>"; };
5FD574C4D783F861C6457F91 /* 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>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8C215028A2D89463A8CFF7B7 /* 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>"; };
8C524F3BF6296D2D38CFC267 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9BC5F518A0809AC88DAD0A63 /* 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>"; };
BB79830F8A9A02FD4D48139F /* 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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
812813278FF8782B5C88EB68 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7930EA2BD395AB994D5E4E66 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A3AB4B5D8E528CCB594E3802 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
58F87FDD07E82A6F818A6F2D /* Pods */ = {
isa = PBXGroup;
children = (
5AFF599171DD7C3CCF1C0A76 /* Pods-Runner.debug.xcconfig */,
3DB8325C4D0990D409BC106C /* Pods-Runner.release.xcconfig */,
BB79830F8A9A02FD4D48139F /* Pods-Runner.profile.xcconfig */,
9BC5F518A0809AC88DAD0A63 /* Pods-RunnerTests.debug.xcconfig */,
8C215028A2D89463A8CFF7B7 /* Pods-RunnerTests.release.xcconfig */,
5FD574C4D783F861C6457F91 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
58F87FDD07E82A6F818A6F2D /* Pods */,
F36CA61FCD258974A33A5797 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
F36CA61FCD258974A33A5797 /* Frameworks */ = {
isa = PBXGroup;
children = (
8C524F3BF6296D2D38CFC267 /* Pods_Runner.framework */,
496900ECD3D00C358558EBA9 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
AA86920B1C65531AC1FE7C0C /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
812813278FF8782B5C88EB68 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
B9AA541EB37F9976B78B3F62 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
41BB84C613006DFE94367216 /* [CP] Embed Pods Frameworks */,
D48FD101326A3B8D976EC3DA /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
41BB84C613006DFE94367216 /* [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;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
AA86920B1C65531AC1FE7C0C /* [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;
};
B9AA541EB37F9976B78B3F62 /* [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;
};
D48FD101326A3B8D976EC3DA /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = TYQA8AL9UP;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.alcaptain.elCaptainClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9BC5F518A0809AC88DAD0A63 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.alcaptain.elCaptainClient.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 8C215028A2D89463A8CFF7B7 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.alcaptain.elCaptainClient.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5FD574C4D783F861C6457F91 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.alcaptain.elCaptainClient.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = TYQA8AL9UP;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.alcaptain.elCaptainClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = TYQA8AL9UP;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.alcaptain.elCaptainClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-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>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-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>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-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>PreviewsEnabled</key>
<false/>
</dict>
</plist>
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-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>
<string>El Captain Client</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>el_captain_client</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<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>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
import Flutter
import UIKit
class SceneDelegate: FlutterSceneDelegate {
}
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}
class InstanceConfig {
// ===== CHANGE THESE PER CLIENT =====
static const String baseUrl = 'https://el-captain.caprover.al-arcade.com';
static const String appName = 'El Captain';
static const String appNameAr = 'الكابتن';
static const String packageId = 'com.alcaptain.elcaptain';
// ====================================
static const String apiVersion = 'v1';
static String get apiUrl => '$baseUrl/api/$apiVersion';
}
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../config/instance.dart';
import '../auth/auth_store.dart';
final dioProvider = Provider<Dio>((ref) {
final dio = Dio(BaseOptions(
baseUrl: InstanceConfig.apiUrl,
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
));
dio.interceptors.add(AuthInterceptor(ref));
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
logPrint: (o) {},
));
return dio;
});
class AuthInterceptor extends Interceptor {
final Ref ref;
AuthInterceptor(this.ref);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final token = ref.read(authStoreProvider).token;
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
if (err.response?.statusCode == 401) {
ref.read(authStoreProvider.notifier).logout();
}
handler.next(err);
}
}
class ApiException implements Exception {
final String code;
final String message;
final int? statusCode;
ApiException({required this.code, required this.message, this.statusCode});
factory ApiException.fromDio(DioException e) {
final data = e.response?.data;
if (data is Map<String, dynamic>) {
return ApiException(
code: data['error'] as String? ?? 'unknown',
message: data['message'] as String? ?? 'حدث خطأ غير متوقع',
statusCode: e.response?.statusCode,
);
}
return ApiException(
code: 'network_error',
message: 'تأكد من اتصالك بالإنترنت',
statusCode: e.response?.statusCode,
);
}
@override
String toString() => message;
}
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'api_client.dart';
final apiServiceProvider = Provider<ApiService>((ref) {
return ApiService(ref.watch(dioProvider));
});
class ApiService {
final Dio _dio;
ApiService(this._dio);
// === Auth ===
Future<Map<String, dynamic>> requestOtp(String phone) async {
final res = await _dio.post('/auth/otp/request', data: {'phone': phone});
return res.data;
}
Future<Map<String, dynamic>> verifyOtp(String phone, String otp) async {
final res = await _dio.post('/auth/otp/verify', data: {'phone': phone, 'otp': otp});
return res.data;
}
Future<void> logout() async {
await _dio.post('/auth/logout');
}
Future<Map<String, dynamic>> getMe() async {
final res = await _dio.get('/auth/me');
return res.data;
}
// === Config ===
Future<Map<String, dynamic>> getAppConfig() async {
final res = await _dio.get('/app/config');
return res.data;
}
// === Dashboard ===
Future<Map<String, dynamic>> getDashboard() async {
final res = await _dio.get('/dashboard');
return res.data['data'];
}
// === Participants ===
Future<List<dynamic>> getChildren() async {
final res = await _dio.get('/guardian/children');
return res.data['data'];
}
Future<Map<String, dynamic>> getParticipant(String uuid) async {
final res = await _dio.get('/participants/$uuid');
return res.data['data'];
}
Future<Map<String, dynamic>> getParticipantSummary(String uuid) async {
final res = await _dio.get('/participants/$uuid/summary');
return res.data['data'];
}
Future<List<dynamic>> getSchedule(String uuid) async {
final res = await _dio.get('/participants/$uuid/schedule');
return res.data['data'];
}
Future<Map<String, dynamic>> getAttendance(String uuid, {int page = 1}) async {
final res = await _dio.get('/participants/$uuid/attendance', queryParameters: {'page': page});
return res.data;
}
Future<Map<String, dynamic>> getInvoices(String uuid, {int page = 1}) async {
final res = await _dio.get('/participants/$uuid/invoices', queryParameters: {'page': page});
return res.data;
}
Future<List<dynamic>> getEnrollments(String uuid) async {
final res = await _dio.get('/participants/$uuid/enrollments');
return res.data['data'];
}
Future<Map<String, dynamic>> getDocuments(String uuid) async {
final res = await _dio.get('/participants/$uuid/documents');
return res.data;
}
Future<Map<String, dynamic>> getWallet(String uuid) async {
final res = await _dio.get('/participants/$uuid/wallet');
return res.data['data'];
}
Future<Map<String, dynamic>> getWalletTransactions(String uuid, {int page = 1}) async {
final res = await _dio.get('/participants/$uuid/wallet/transactions', queryParameters: {'page': page});
return res.data;
}
Future<List<dynamic>> getInstallments(String uuid) async {
final res = await _dio.get('/participants/$uuid/installments');
return res.data['data'];
}
Future<List<dynamic>> getEvaluations(String uuid) async {
final res = await _dio.get('/participants/$uuid/evaluations');
return res.data['data'];
}
Future<Map<String, dynamic>> getEvaluationDetail(String uuid, String evalUuid) async {
final res = await _dio.get('/participants/$uuid/evaluations/$evalUuid');
return res.data['data'];
}
// === Notifications ===
Future<Map<String, dynamic>> getNotifications({int page = 1}) async {
final res = await _dio.get('/notifications', queryParameters: {'page': page});
return res.data;
}
Future<void> markNotificationRead(int id) async {
await _dio.patch('/notifications/$id/read');
}
Future<void> markAllNotificationsRead() async {
await _dio.post('/notifications/read-all');
}
Future<Map<String, dynamic>> getNotificationPreferences() async {
final res = await _dio.get('/notifications/preferences');
return res.data;
}
Future<void> updateNotificationPreferences(List<Map<String, dynamic>> prefs) async {
await _dio.post('/notifications/preferences', data: {'preferences': prefs});
}
// === Profile ===
Future<Map<String, dynamic>> getProfile() async {
final res = await _dio.get('/profile');
return res.data['data'];
}
Future<Map<String, dynamic>> updateProfile(Map<String, dynamic> data) async {
final res = await _dio.patch('/profile', data: data);
return res.data;
}
// === Shop ===
Future<List<dynamic>> getProducts() async {
final res = await _dio.get('/products');
return res.data['data'];
}
Future<Map<String, dynamic>> createOrder(Map<String, dynamic> data) async {
final res = await _dio.post('/orders/create', data: data);
return res.data;
}
Future<Map<String, dynamic>> getOrders({int page = 1}) async {
final res = await _dio.get('/orders', queryParameters: {'page': page});
return res.data;
}
// === Payments ===
Future<Map<String, dynamic>> initiatePayment(String invoiceUuid) async {
final res = await _dio.post('/payments/initiate', data: {'invoice_uuid': invoiceUuid});
return res.data;
}
Future<Map<String, dynamic>> getInvoiceDetail(String uuid) async {
final res = await _dio.get('/invoices/$uuid');
return res.data['data'];
}
Future<Map<String, dynamic>> getPaymentReceipt(String uuid) async {
final res = await _dio.get('/payments/$uuid/receipt');
return res.data['data'];
}
// === Events ===
Future<Map<String, dynamic>> getEvents({int page = 1}) async {
final res = await _dio.get('/academy/events', queryParameters: {'page': page});
return res.data;
}
Future<Map<String, dynamic>> getEventDetail(String uuid) async {
final res = await _dio.get('/academy/events/$uuid');
return res.data['data'];
}
Future<Map<String, dynamic>> registerForEvent(String uuid, String participantUuid, {String? notes}) async {
final res = await _dio.post('/events/$uuid/register', data: {
'participant_uuid': participantUuid,
if (notes != null) 'notes': notes,
});
return res.data;
}
Future<List<dynamic>> getMyRegistrations() async {
final res = await _dio.get('/events/my-registrations');
return res.data['data'];
}
// === Service Requests ===
Future<Map<String, dynamic>> createServiceRequest(Map<String, dynamic> data) async {
final res = await _dio.post('/service-requests', data: data);
return res.data;
}
Future<List<dynamic>> getServiceRequests() async {
final res = await _dio.get('/service-requests');
return res.data['data'];
}
Future<void> cancelServiceRequest(String uuid) async {
await _dio.post('/service-requests/$uuid/cancel');
}
// === Absences ===
Future<void> reportAbsence(Map<String, dynamic> data) async {
await _dio.post('/absences/report', data: data);
}
// === Messages ===
Future<Map<String, dynamic>> sendMessage(Map<String, dynamic> data) async {
final res = await _dio.post('/messages/send', data: data);
return res.data;
}
Future<Map<String, dynamic>> getMessages({int page = 1}) async {
final res = await _dio.get('/messages', queryParameters: {'page': page});
return res.data;
}
// === Registration ===
Future<Map<String, dynamic>> preRegister({
required String guardianNameAr,
String? guardianName,
required String phone,
String? email,
required String participantName,
required int participantAge,
required String gender,
}) async {
final res = await _dio.post('/register/pre-register', data: {
'guardian_name_ar': guardianNameAr,
if (guardianName != null && guardianName.isNotEmpty) 'guardian_name': guardianName,
'phone': phone,
if (email != null && email.isNotEmpty) 'email': email,
'participant_name': participantName,
'participant_age': participantAge,
'gender': gender,
});
return res.data;
}
// === Academy (public) ===
Future<Map<String, dynamic>> getNews({int page = 1}) async {
final res = await _dio.get('/academy/news', queryParameters: {'page': page});
return res.data;
}
Future<Map<String, dynamic>> getNewsDetail(String uuid) async {
final res = await _dio.get('/academy/news/$uuid');
return res.data['data'];
}
Future<List<dynamic>> getPrograms() async {
final res = await _dio.get('/academy/programs');
return res.data['data'];
}
Future<List<dynamic>> getBranches() async {
final res = await _dio.get('/branches');
return res.data['data'];
}
Future<Map<String, dynamic>> getGallery({int page = 1}) async {
final res = await _dio.get('/academy/gallery', queryParameters: {'page': page});
return res.data;
}
// === Devices ===
Future<void> registerDevice(String token, String platform, {String? deviceName, String? appVersion}) async {
await _dio.post('/devices/register', data: {
'token': token,
'platform': platform,
if (deviceName != null) 'device_name': deviceName,
if (appVersion != null) 'app_version': appVersion,
});
}
Future<void> unregisterDevice(String token) async {
await _dio.delete('/devices/$token');
}
// === Push ===
Future<Map<String, dynamic>> getBadgeCount() async {
final res = await _dio.get('/push/badge');
return res.data;
}
Future<void> trackPushEvent(List<Map<String, dynamic>> events) async {
await _dio.post('/push/track', data: {'events': events});
}
}
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
final authStoreProvider = StateNotifierProvider<AuthStoreNotifier, AuthState>((ref) {
return AuthStoreNotifier();
});
class AuthState {
final String? token;
final Map<String, dynamic>? user;
final List<Map<String, dynamic>> participants;
final bool isLoading;
AuthState({
this.token,
this.user,
this.participants = const [],
this.isLoading = true,
});
bool get isAuthenticated => token != null;
String? get userName => user?['name_ar'] as String?;
String? get userPhone => user?['phone'] as String?;
int? get academyId => user?['academy_id'] as int?;
AuthState copyWith({
String? token,
Map<String, dynamic>? user,
List<Map<String, dynamic>>? participants,
bool? isLoading,
}) {
return AuthState(
token: token ?? this.token,
user: user ?? this.user,
participants: participants ?? this.participants,
isLoading: isLoading ?? this.isLoading,
);
}
}
class AuthStoreNotifier extends StateNotifier<AuthState> {
AuthStoreNotifier() : super(AuthState()) {
_loadFromStorage();
}
Future<void> _loadFromStorage() async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
final userJson = prefs.getString('auth_user');
final participantsJson = prefs.getString('auth_participants');
if (token != null && userJson != null) {
state = AuthState(
token: token,
user: jsonDecode(userJson),
participants: participantsJson != null
? List<Map<String, dynamic>>.from(jsonDecode(participantsJson))
: [],
isLoading: false,
);
} else {
state = AuthState(isLoading: false);
}
}
Future<void> setAuth({
required String token,
required Map<String, dynamic> user,
required List<dynamic> participants,
}) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token);
await prefs.setString('auth_user', jsonEncode(user));
await prefs.setString('auth_participants', jsonEncode(participants));
state = AuthState(
token: token,
user: user,
participants: participants.cast<Map<String, dynamic>>(),
isLoading: false,
);
}
Future<void> updateParticipants(List<dynamic> participants) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_participants', jsonEncode(participants));
state = state.copyWith(participants: participants.cast<Map<String, dynamic>>());
}
Future<void> logout() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('auth_token');
await prefs.remove('auth_user');
await prefs.remove('auth_participants');
state = AuthState(isLoading: false);
}
}
import 'package:flutter/material.dart';
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
}
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
bool get isArabic => locale.languageCode == 'ar';
static final Map<String, Map<String, String>> _translations = {
'ar': {
'app_name': 'الكابتن',
'login': 'تسجيل الدخول',
'phone_number': 'رقم الهاتف',
'enter_phone': 'أدخل رقم هاتفك',
'send_otp': 'إرسال رمز التحقق',
'verify_otp': 'تحقق من الرمز',
'enter_otp': 'أدخل رمز التحقق',
'otp_sent': 'تم إرسال رمز التحقق',
'invalid_otp': 'رمز التحقق غير صحيح',
'home': 'الرئيسية',
'schedule': 'الجدول',
'notifications': 'الإشعارات',
'profile': 'حسابي',
'my_children': 'أبنائي',
'today_sessions': 'جلسات اليوم',
'outstanding_balance': 'المبلغ المستحق',
'attendance_rate': 'نسبة الحضور',
'next_session': 'الجلسة القادمة',
'invoices': 'الفواتير',
'enrollments': 'الاشتراكات',
'evaluations': 'التقييمات',
'documents': 'المستندات',
'wallet': 'المحفظة',
'shop': 'المتجر',
'events': 'الفعاليات',
'messages': 'الرسائل',
'service_requests': 'الطلبات',
'pay_now': 'ادفع الآن',
'view_all': 'عرض الكل',
'no_data': 'لا توجد بيانات',
'loading': 'جارٍ التحميل...',
'error_occurred': 'حدث خطأ',
'retry': 'إعادة المحاولة',
'present': 'حاضر',
'absent': 'غائب',
'late': 'متأخر',
'excused': 'بعذر',
'paid': 'مدفوعة',
'pending': 'قيد الانتظار',
'overdue': 'متأخرة',
'active': 'نشط',
'frozen': 'مجمد',
'logout': 'تسجيل الخروج',
'settings': 'الإعدادات',
'language': 'اللغة',
'register': 'تسجيل جديد',
'pre_register': 'التسجيل المسبق',
'welcome_back': 'أهلاً بعودتك',
'good_morning': 'صباح الخير',
'good_evening': 'مساء الخير',
'branches': 'الفروع',
'programs': 'البرامج',
'news': 'الأخبار',
'gallery': 'المعرض',
'report_absence': 'تبليغ غياب',
'send_message': 'إرسال رسالة',
'freeze_request': 'طلب تجميد',
'transfer_request': 'طلب نقل',
'cancel_request': 'طلب إلغاء',
'total_balance': 'إجمالي المستحق',
'installments': 'الأقساط',
'payment_plans': 'خطط الدفع',
'explore': 'استكشف',
'more': 'المزيد',
'mark_all_read': 'قراءة الكل',
'no_notifications': 'لا توجد إشعارات',
'no_sessions': 'لا توجد جلسات',
'no_attendance': 'لا توجد سجلات حضور',
'no_invoices': 'لا توجد فواتير',
'no_evaluations': 'لا توجد تقييمات',
'no_events': 'لا توجد فعاليات',
'no_products': 'لا توجد منتجات',
'no_messages': 'لا توجد رسائل',
'no_requests': 'لا توجد طلبات',
'sessions': 'جلسات',
'edit_profile': 'تعديل الحساب',
'notification_settings': 'إعدادات الإشعارات',
'about': 'عن التطبيق',
'guardian_info': 'بيانات ولي الأمر',
'child_info': 'بيانات المشترك',
'review': 'مراجعة',
'submit': 'إرسال',
'next': 'التالي',
'type_message': 'اكتب رسالة...',
'attendance': 'الحضور',
},
'en': {
'app_name': 'El Captain',
'login': 'Login',
'phone_number': 'Phone Number',
'enter_phone': 'Enter your phone number',
'send_otp': 'Send OTP',
'verify_otp': 'Verify OTP',
'enter_otp': 'Enter verification code',
'otp_sent': 'OTP code sent',
'invalid_otp': 'Invalid verification code',
'home': 'Home',
'schedule': 'Schedule',
'notifications': 'Notifications',
'profile': 'Profile',
'my_children': 'My Children',
'today_sessions': 'Today\'s Sessions',
'outstanding_balance': 'Outstanding Balance',
'attendance_rate': 'Attendance Rate',
'next_session': 'Next Session',
'invoices': 'Invoices',
'enrollments': 'Enrollments',
'evaluations': 'Evaluations',
'documents': 'Documents',
'wallet': 'Wallet',
'shop': 'Shop',
'events': 'Events',
'messages': 'Messages',
'service_requests': 'Requests',
'pay_now': 'Pay Now',
'view_all': 'View All',
'no_data': 'No data available',
'loading': 'Loading...',
'error_occurred': 'An error occurred',
'retry': 'Retry',
'present': 'Present',
'absent': 'Absent',
'late': 'Late',
'excused': 'Excused',
'paid': 'Paid',
'pending': 'Pending',
'overdue': 'Overdue',
'active': 'Active',
'frozen': 'Frozen',
'logout': 'Logout',
'settings': 'Settings',
'language': 'Language',
'register': 'Register',
'pre_register': 'Pre-Register',
'welcome_back': 'Welcome Back',
'good_morning': 'Good Morning',
'good_evening': 'Good Evening',
'branches': 'Branches',
'programs': 'Programs',
'news': 'News',
'gallery': 'Gallery',
'report_absence': 'Report Absence',
'send_message': 'Send Message',
'freeze_request': 'Freeze Request',
'transfer_request': 'Transfer Request',
'cancel_request': 'Cancel Request',
'total_balance': 'Total Balance',
'installments': 'Installments',
'payment_plans': 'Payment Plans',
'explore': 'Explore',
'more': 'More',
'mark_all_read': 'Mark All Read',
'no_notifications': 'No notifications',
'no_sessions': 'No sessions',
'no_attendance': 'No attendance records',
'no_invoices': 'No invoices',
'no_evaluations': 'No evaluations',
'no_events': 'No events',
'no_products': 'No products',
'no_messages': 'No messages',
'no_requests': 'No requests',
'sessions': 'sessions',
'edit_profile': 'Edit Profile',
'notification_settings': 'Notification Settings',
'about': 'About',
'guardian_info': 'Guardian Info',
'child_info': 'Child Info',
'review': 'Review',
'submit': 'Submit',
'next': 'Next',
'type_message': 'Type a message...',
'attendance': 'Attendance',
},
};
String t(String key) => _translations[locale.languageCode]?[key] ?? key;
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) => ['ar', 'en'].contains(locale.languageCode);
@override
Future<AppLocalizations> load(Locale locale) async => AppLocalizations(locale);
@override
bool shouldReload(covariant LocalizationsDelegate<AppLocalizations> old) => false;
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
final localeProvider = StateNotifierProvider<LocaleNotifier, Locale>((ref) {
return LocaleNotifier();
});
class LocaleNotifier extends StateNotifier<Locale> {
LocaleNotifier() : super(const Locale('ar')) {
_load();
}
Future<void> _load() async {
final prefs = await SharedPreferences.getInstance();
final code = prefs.getString('app_locale') ?? 'ar';
state = Locale(code);
}
Future<void> setLocale(Locale locale) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('app_locale', locale.languageCode);
state = locale;
}
void toggle() {
final next = state.languageCode == 'ar' ? const Locale('en') : const Locale('ar');
setLocale(next);
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../auth/auth_store.dart';
import '../theme/app_animations.dart';
import '../../features/splash/splash_screen.dart';
import '../../features/auth/login_screen.dart';
import '../../features/auth/otp_screen.dart';
import '../../features/home/home_shell.dart';
import '../../features/home/dashboard_screen.dart';
import '../../features/schedule/schedule_screen.dart';
import '../../features/notifications/notifications_screen.dart';
import '../../features/profile/profile_screen.dart';
import '../../features/participants/participant_detail_screen.dart';
import '../../features/attendance/attendance_screen.dart';
import '../../features/invoices/invoices_screen.dart';
import '../../features/evaluations/evaluations_screen.dart';
import '../../features/events/events_screen.dart';
import '../../features/shop/shop_screen.dart';
import '../../features/messages/messages_screen.dart';
import '../../features/service_requests/service_requests_screen.dart';
import '../../features/registration/registration_screen.dart';
final routerProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authStoreProvider);
return GoRouter(
initialLocation: '/splash',
redirect: (context, state) {
if (authState.isLoading) return '/splash';
final isAuth = authState.isAuthenticated;
final isAuthRoute = state.matchedLocation.startsWith('/auth');
final isSplash = state.matchedLocation == '/splash';
final isPublic = state.matchedLocation.startsWith('/register');
if (isSplash && !authState.isLoading) {
return isAuth ? '/home' : '/auth/login';
}
if (!isAuth && !isAuthRoute && !isSplash && !isPublic) {
return '/auth/login';
}
if (isAuth && isAuthRoute) {
return '/home';
}
return null;
},
routes: [
GoRoute(
path: '/splash',
pageBuilder: (context, state) =>
PageTransitions.fade(const SplashScreen(), key: 'splash'),
),
GoRoute(
path: '/auth/login',
pageBuilder: (context, state) =>
PageTransitions.fade(const LoginScreen(), key: 'login'),
),
GoRoute(
path: '/auth/otp',
pageBuilder: (context, state) {
final phone = state.extra as String? ?? '';
return PageTransitions.slide(OtpScreen(phone: phone), key: 'otp');
},
),
GoRoute(
path: '/register',
pageBuilder: (context, state) =>
PageTransitions.slide(const RegistrationScreen(), key: 'register'),
),
ShellRoute(
builder: (context, state, child) => HomeShell(child: child),
routes: [
GoRoute(
path: '/home',
pageBuilder: (context, state) =>
PageTransitions.fade(const DashboardScreen(), key: 'home'),
),
GoRoute(
path: '/schedule',
pageBuilder: (context, state) =>
PageTransitions.fade(const ScheduleScreen(), key: 'schedule'),
),
GoRoute(
path: '/notifications',
pageBuilder: (context, state) =>
PageTransitions.fade(const NotificationsScreen(), key: 'notifications'),
),
GoRoute(
path: '/profile',
pageBuilder: (context, state) =>
PageTransitions.fade(const ProfileScreen(), key: 'profile'),
),
],
),
GoRoute(
path: '/participants/:uuid',
pageBuilder: (context, state) {
final uuid = state.pathParameters['uuid']!;
return PageTransitions.slide(
ParticipantDetailScreen(uuid: uuid),
key: 'participant-$uuid',
);
},
),
GoRoute(
path: '/participants/:uuid/attendance',
pageBuilder: (context, state) {
final uuid = state.pathParameters['uuid']!;
return PageTransitions.slide(
AttendanceScreen(participantUuid: uuid),
key: 'attendance-$uuid',
);
},
),
GoRoute(
path: '/participants/:uuid/invoices',
pageBuilder: (context, state) {
final uuid = state.pathParameters['uuid']!;
return PageTransitions.slide(
InvoicesScreen(participantUuid: uuid),
key: 'invoices-$uuid',
);
},
),
GoRoute(
path: '/participants/:uuid/evaluations',
pageBuilder: (context, state) {
final uuid = state.pathParameters['uuid']!;
return PageTransitions.slide(
EvaluationsScreen(participantUuid: uuid),
key: 'evaluations-$uuid',
);
},
),
GoRoute(
path: '/events',
pageBuilder: (context, state) =>
PageTransitions.slide(const EventsScreen(), key: 'events'),
),
GoRoute(
path: '/shop',
pageBuilder: (context, state) =>
PageTransitions.slide(const ShopScreen(), key: 'shop'),
),
GoRoute(
path: '/messages',
pageBuilder: (context, state) =>
PageTransitions.slide(const MessagesScreen(), key: 'messages'),
),
GoRoute(
path: '/service-requests',
pageBuilder: (context, state) =>
PageTransitions.slide(const ServiceRequestsScreen(), key: 'requests'),
),
],
);
});
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:go_router/go_router.dart';
extension AnimateWidgetExtensions on Widget {
Widget fadeSlideUp({int delay = 0, int duration = 500}) {
return animate(delay: Duration(milliseconds: delay))
.fadeIn(duration: Duration(milliseconds: duration), curve: Curves.easeOutCubic)
.slideY(begin: 0.08, end: 0, duration: Duration(milliseconds: duration), curve: Curves.easeOutCubic);
}
Widget fadeSlideRight({int delay = 0, int duration = 400}) {
return animate(delay: Duration(milliseconds: delay))
.fadeIn(duration: Duration(milliseconds: duration), curve: Curves.easeOutCubic)
.slideX(begin: -0.05, end: 0, duration: Duration(milliseconds: duration), curve: Curves.easeOutCubic);
}
Widget scaleIn({int delay = 0, int duration = 400}) {
return animate(delay: Duration(milliseconds: delay))
.fadeIn(duration: Duration(milliseconds: duration))
.scale(begin: const Offset(0.9, 0.9), end: const Offset(1, 1), duration: Duration(milliseconds: duration), curve: Curves.easeOutBack);
}
Widget shimmerEffect() {
return animate(onPlay: (c) => c.repeat())
.shimmer(duration: 1500.ms, color: Colors.white24);
}
}
class StaggeredColumn extends StatelessWidget {
final List<Widget> children;
final int baseDelay;
final int staggerDelay;
const StaggeredColumn({
super.key,
required this.children,
this.baseDelay = 100,
this.staggerDelay = 80,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (int i = 0; i < children.length; i++)
children[i]
.animate(delay: Duration(milliseconds: baseDelay + (i * staggerDelay)))
.fadeIn(duration: 450.ms, curve: Curves.easeOutCubic)
.slideY(begin: 0.06, end: 0, duration: 450.ms, curve: Curves.easeOutCubic),
],
);
}
}
class AnimatedCounter extends StatelessWidget {
final int value;
final TextStyle? style;
final String? suffix;
const AnimatedCounter({super.key, required this.value, this.style, this.suffix});
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder<int>(
tween: IntTween(begin: 0, end: value),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutCubic,
builder: (context, val, _) => Text(
'$val${suffix ?? ''}',
style: style,
),
);
}
}
class PageTransitions {
static CustomTransitionPage slide(Widget child, {required String key}) {
return CustomTransitionPage(
key: ValueKey(key),
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.15, 0),
end: Offset.zero,
).animate(CurvedAnimation(parent: animation, curve: Curves.easeOutCubic)),
child: FadeTransition(opacity: animation, child: child),
);
},
transitionDuration: const Duration(milliseconds: 350),
);
}
static CustomTransitionPage fade(Widget child, {required String key}) {
return CustomTransitionPage(
key: ValueKey(key),
child: child,
transitionsBuilder: (context, animation, _, child) {
return FadeTransition(opacity: animation, child: child);
},
transitionDuration: const Duration(milliseconds: 300),
);
}
}
import 'package:flutter/material.dart';
class AppTheme {
static Color primaryColor = const Color(0xFF1e40af);
static Color accentColor = const Color(0xFFf59e0b);
static void updateFromConfig(String? primary, String? accent) {
if (primary != null && primary.startsWith('#')) {
primaryColor = Color(int.parse('FF${primary.substring(1)}', radix: 16));
}
if (accent != null && accent.startsWith('#')) {
accentColor = Color(int.parse('FF${accent.substring(1)}', radix: 16));
}
}
static ThemeData get light {
final colorScheme = ColorScheme.fromSeed(
seedColor: primaryColor,
brightness: Brightness.light,
primary: primaryColor,
secondary: accentColor,
surface: Colors.white,
onSurface: const Color(0xFF1a1a2e),
);
return ThemeData(
useMaterial3: true,
colorScheme: colorScheme,
fontFamily: 'Cairo',
scaffoldBackgroundColor: const Color(0xFFF8FAFC),
appBarTheme: AppBarTheme(
backgroundColor: Colors.white,
foregroundColor: colorScheme.onSurface,
elevation: 0,
scrolledUnderElevation: 0.5,
centerTitle: true,
titleTextStyle: TextStyle(
fontFamily: 'Cairo',
fontSize: 18,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
cardTheme: CardThemeData(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: Colors.grey.shade100),
),
color: Colors.white,
margin: EdgeInsets.zero,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
textStyle: const TextStyle(
fontFamily: 'Cairo',
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: primaryColor,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
side: BorderSide(color: primaryColor.withValues(alpha: 0.3)),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: const Color(0xFFF1F5F9),
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: primaryColor, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: Colors.red, width: 1.5),
),
hintStyle: TextStyle(
color: Colors.grey.shade400,
fontFamily: 'Cairo',
),
),
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: Colors.white,
selectedItemColor: primaryColor,
unselectedItemColor: Colors.grey.shade400,
type: BottomNavigationBarType.fixed,
elevation: 8,
selectedLabelStyle: const TextStyle(
fontFamily: 'Cairo',
fontSize: 11,
fontWeight: FontWeight.w600,
),
unselectedLabelStyle: const TextStyle(
fontFamily: 'Cairo',
fontSize: 11,
),
),
dividerTheme: DividerThemeData(
color: Colors.grey.shade100,
thickness: 1,
),
chipTheme: ChipThemeData(
backgroundColor: const Color(0xFFF1F5F9),
selectedColor: primaryColor.withValues(alpha: 0.1),
labelStyle: const TextStyle(
fontFamily: 'Cairo',
fontSize: 13,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
side: BorderSide.none,
),
);
}
}
import 'package:intl/intl.dart';
String formatMoney(dynamic piasters, {bool showCurrency = true}) {
final amount = (piasters is int ? piasters : int.tryParse(piasters.toString()) ?? 0) / 100;
final formatted = NumberFormat('#,##0.00', 'ar').format(amount);
return showCurrency ? '$formatted ج.م' : formatted;
}
String formatMoneyEn(dynamic piasters, {bool showCurrency = true}) {
final amount = (piasters is int ? piasters : int.tryParse(piasters.toString()) ?? 0) / 100;
final formatted = NumberFormat('#,##0.00', 'en').format(amount);
return showCurrency ? 'EGP $formatted' : formatted;
}
import 'package:flutter/material.dart';
class AnimatedCard extends StatefulWidget {
final Widget child;
final VoidCallback? onTap;
final EdgeInsets? padding;
final Color? color;
final BorderRadius? borderRadius;
const AnimatedCard({
super.key,
required this.child,
this.onTap,
this.padding,
this.color,
this.borderRadius,
});
@override
State<AnimatedCard> createState() => _AnimatedCardState();
}
class _AnimatedCardState extends State<AnimatedCard> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 150),
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.97).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => _controller.forward(),
onTapUp: (_) {
_controller.reverse();
widget.onTap?.call();
},
onTapCancel: () => _controller.reverse(),
child: AnimatedBuilder(
animation: _scaleAnimation,
builder: (context, child) => Transform.scale(
scale: _scaleAnimation.value,
child: child,
),
child: Container(
padding: widget.padding ?? const EdgeInsets.all(16),
decoration: BoxDecoration(
color: widget.color ?? Colors.white,
borderRadius: widget.borderRadius ?? BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade100),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.03),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: widget.child,
),
),
);
}
}
class ShimmerCard extends StatelessWidget {
final double height;
final double? width;
const ShimmerCard({super.key, this.height = 100, this.width});
@override
Widget build(BuildContext context) {
return Container(
height: height,
width: width,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(16),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
class LoadingOverlay extends StatelessWidget {
final bool isLoading;
final Widget child;
final String? message;
const LoadingOverlay({
super.key,
required this.isLoading,
required this.child,
this.message,
});
@override
Widget build(BuildContext context) {
return Stack(
children: [
child,
if (isLoading)
Positioned.fill(
child: Container(
color: Colors.white.withValues(alpha: 0.8),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 20,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
strokeWidth: 3,
color: Theme.of(context).colorScheme.primary,
),
),
if (message != null) ...[
const SizedBox(height: 16),
Text(
message!,
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
),
],
],
),
).animate().scale(
begin: const Offset(0.8, 0.8),
end: const Offset(1, 1),
duration: 300.ms,
curve: Curves.easeOutBack,
),
],
),
),
),
),
],
);
}
}
class EmptyState extends StatelessWidget {
final String message;
final IconData icon;
final VoidCallback? onAction;
final String? actionLabel;
const EmptyState({
super.key,
required this.message,
this.icon = Icons.inbox_outlined,
this.onAction,
this.actionLabel,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(40),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.grey.shade50,
shape: BoxShape.circle,
),
child: Icon(icon, size: 48, color: Colors.grey.shade300),
),
const SizedBox(height: 20),
Text(
message,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: Colors.grey.shade500,
fontWeight: FontWeight.w500,
),
),
if (onAction != null && actionLabel != null) ...[
const SizedBox(height: 20),
TextButton(
onPressed: onAction,
child: Text(actionLabel!),
),
],
],
),
),
).animate().fadeIn(duration: 400.ms).slideY(begin: 0.05, end: 0);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/widgets/loading_overlay.dart';
final attendanceProvider =
FutureProvider.autoDispose.family<Map<String, dynamic>, String>((ref, uuid) async {
return ref.watch(apiServiceProvider).getAttendance(uuid);
});
class AttendanceScreen extends ConsumerWidget {
final String participantUuid;
const AttendanceScreen({super.key, required this.participantUuid});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final attendance = ref.watch(attendanceProvider(participantUuid));
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('attendance')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, size: 18),
onPressed: () => context.pop(),
),
),
body: attendance.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (data) {
final records = (data['data'] as List?) ?? [];
final rate = data['rate'] ?? 0;
return Column(
children: [
// Rate header
Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 10),
],
),
child: Row(
children: [
SizedBox(
width: 70,
height: 70,
child: Stack(
alignment: Alignment.center,
children: [
CircularProgressIndicator(
value: (rate as num).toDouble() / 100,
strokeWidth: 6,
backgroundColor: Colors.grey.shade200,
color: _rateColor(rate),
),
Text(
'$rate%',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _rateColor(rate),
),
),
],
),
),
const SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l.t('attendance_rate'),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text(
'${records.length} ${l.t('sessions')}',
style: TextStyle(fontSize: 13, color: Colors.grey.shade500),
),
],
),
),
],
),
).animate().fadeIn(duration: 500.ms).slideY(begin: 0.05, end: 0),
// Records
Expanded(
child: records.isEmpty
? EmptyState(icon: Icons.event_busy, message: l.t('no_attendance'))
: ListView.builder(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: records.length,
itemBuilder: (ctx, i) {
final r = records[i] as Map<String, dynamic>;
return _AttendanceTile(record: r, index: i);
},
),
),
],
);
},
),
);
}
Color _rateColor(dynamic rate) {
final r = (rate as num).toDouble();
if (r >= 80) return Colors.green;
if (r >= 60) return Colors.orange;
return Colors.red;
}
}
class _AttendanceTile extends StatelessWidget {
final Map<String, dynamic> record;
final int index;
const _AttendanceTile({required this.record, required this.index});
@override
Widget build(BuildContext context) {
final status = record['status'] ?? 'expected';
final (color, icon, label) = _statusInfo(status);
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade100),
),
child: Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: color, size: 18),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
record['session_date'] ?? '',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
),
if (record['group_name'] != null) ...[
const SizedBox(height: 2),
Text(
record['group_name'],
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
),
],
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(label, style: TextStyle(fontSize: 11, color: color, fontWeight: FontWeight.w600)),
),
],
),
)
.animate(delay: Duration(milliseconds: 40 * index))
.fadeIn(duration: 350.ms)
.slideX(begin: 0.03, end: 0);
}
(Color, IconData, String) _statusInfo(String status) => switch (status) {
'present' => (Colors.green, Icons.check_circle, 'حاضر'),
'late' => (Colors.orange, Icons.schedule, 'متأخر'),
'absent' => (Colors.red, Icons.cancel, 'غائب'),
'excused' => (Colors.blue, Icons.info, 'معذور'),
'no_show' => (Colors.red.shade800, Icons.close, 'لم يحضر'),
_ => (Colors.grey, Icons.circle_outlined, status),
};
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
final _phoneController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
void dispose() {
_phoneController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
final size = MediaQuery.sizeOf(context);
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: SizedBox(
height: size.height - MediaQuery.paddingOf(context).top - MediaQuery.paddingOf(context).bottom,
child: Form(
key: _formKey,
child: Column(
children: [
const Spacer(flex: 2),
// Logo
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppTheme.primaryColor,
AppTheme.primaryColor.withValues(alpha: 0.8),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(28),
boxShadow: [
BoxShadow(
color: AppTheme.primaryColor.withValues(alpha: 0.3),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: const Icon(Icons.sports_soccer, size: 48, color: Colors.white),
)
.animate()
.scale(begin: const Offset(0.6, 0.6), end: const Offset(1, 1), duration: 600.ms, curve: Curves.easeOutBack)
.fadeIn(duration: 400.ms),
const SizedBox(height: 32),
// Title
Text(
l.t('welcome_back'),
style: const TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
).animate(delay: 200.ms).fadeIn(duration: 500.ms).slideY(begin: 0.2, end: 0),
const SizedBox(height: 8),
Text(
l.t('enter_phone'),
style: TextStyle(fontSize: 15, color: Colors.grey.shade500),
).animate(delay: 300.ms).fadeIn(duration: 500.ms),
const SizedBox(height: 48),
// Phone input
Directionality(
textDirection: TextDirection.ltr,
child: TextFormField(
controller: _phoneController,
keyboardType: TextInputType.phone,
textDirection: TextDirection.ltr,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9+]')),
LengthLimitingTextInputFormatter(15),
],
style: const TextStyle(fontSize: 18, letterSpacing: 1),
decoration: InputDecoration(
hintText: '+201XXXXXXXXX',
prefixIcon: Container(
padding: const EdgeInsets.all(12),
child: const Text('🇪🇬', style: TextStyle(fontSize: 24)),
),
),
validator: (v) {
if (v == null || v.isEmpty) return '';
if (v.length < 10) return '';
return null;
},
),
).animate(delay: 400.ms).fadeIn(duration: 500.ms).slideY(begin: 0.1, end: 0),
const SizedBox(height: 24),
// Submit button
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: _submit,
child: Text(l.t('send_otp'), style: const TextStyle(fontSize: 17)),
),
).animate(delay: 500.ms).fadeIn(duration: 500.ms).slideY(begin: 0.1, end: 0),
const SizedBox(height: 20),
// Pre-register link
TextButton(
onPressed: () => context.push('/register'),
child: Text(
l.t('pre_register'),
style: TextStyle(
color: AppTheme.primaryColor,
fontWeight: FontWeight.w600,
),
),
).animate(delay: 600.ms).fadeIn(duration: 400.ms),
const Spacer(flex: 3),
],
),
),
),
),
),
);
}
void _submit() {
if (!_formKey.currentState!.validate()) return;
final phone = _phoneController.text.trim();
context.push('/auth/otp', extra: phone);
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/api/api_client.dart';
import '../../core/api/api_service.dart';
import '../../core/auth/auth_store.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
class OtpScreen extends ConsumerStatefulWidget {
final String phone;
const OtpScreen({super.key, required this.phone});
@override
ConsumerState<OtpScreen> createState() => _OtpScreenState();
}
class _OtpScreenState extends ConsumerState<OtpScreen> {
final List<TextEditingController> _controllers = List.generate(6, (_) => TextEditingController());
final List<FocusNode> _focusNodes = List.generate(6, (_) => FocusNode());
bool _isLoading = false;
bool _otpSent = false;
String? _error;
int _countdown = 60;
Timer? _timer;
@override
void initState() {
super.initState();
_requestOtp();
}
@override
void dispose() {
_timer?.cancel();
for (var c in _controllers) {
c.dispose();
}
for (var n in _focusNodes) {
n.dispose();
}
super.dispose();
}
Future<void> _requestOtp() async {
try {
await ref.read(apiServiceProvider).requestOtp(widget.phone);
setState(() {
_otpSent = true;
_countdown = 60;
});
_startTimer();
} catch (e) {
if (e is ApiException) {
setState(() => _error = e.message);
}
}
}
void _startTimer() {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (t) {
if (_countdown > 0) {
setState(() => _countdown--);
} else {
t.cancel();
}
});
}
Future<void> _verify() async {
final otp = _controllers.map((c) => c.text).join();
if (otp.length != 6) return;
setState(() {
_isLoading = true;
_error = null;
});
try {
final result = await ref.read(apiServiceProvider).verifyOtp(widget.phone, otp);
await ref.read(authStoreProvider.notifier).setAuth(
token: result['token'],
user: result['user'],
participants: result['participants'] ?? [],
);
if (mounted) context.go('/home');
} catch (e) {
setState(() {
_isLoading = false;
_error = e is ApiException ? e.message : 'حدث خطأ';
});
// Shake animation on error
for (var c in _controllers) {
c.clear();
}
_focusNodes[0].requestFocus();
}
}
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
onPressed: () => context.pop(),
),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 20),
Text(
l.t('verify_otp'),
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
).animate().fadeIn(duration: 400.ms).slideY(begin: 0.1, end: 0),
const SizedBox(height: 12),
Text(
'${l.t('otp_sent')} ${widget.phone}',
style: TextStyle(fontSize: 14, color: Colors.grey.shade500),
).animate(delay: 100.ms).fadeIn(duration: 400.ms),
const SizedBox(height: 48),
// OTP Input boxes
Directionality(
textDirection: TextDirection.ltr,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(6, (i) => _buildOtpBox(i)),
),
).animate(delay: 200.ms).fadeIn(duration: 500.ms).slideY(begin: 0.1, end: 0),
if (_error != null) ...[
const SizedBox(height: 20),
Text(
_error!,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red, fontSize: 14),
).animate().shake(hz: 3, duration: 400.ms),
],
const SizedBox(height: 32),
SizedBox(
height: 56,
child: ElevatedButton(
onPressed: _isLoading ? null : _verify,
child: _isLoading
? const SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2.5),
)
: Text(l.t('verify_otp'), style: const TextStyle(fontSize: 17)),
),
).animate(delay: 300.ms).fadeIn(duration: 400.ms),
const SizedBox(height: 24),
Center(
child: _countdown > 0
? Text(
'إعادة الإرسال خلال $_countdown ثانية',
style: TextStyle(color: Colors.grey.shade500, fontSize: 14),
)
: TextButton(
onPressed: _requestOtp,
child: Text(
'إعادة إرسال الرمز',
style: TextStyle(
color: AppTheme.primaryColor,
fontWeight: FontWeight.w600,
),
),
),
).animate(delay: 400.ms).fadeIn(duration: 400.ms),
],
),
),
),
);
}
Widget _buildOtpBox(int index) {
return SizedBox(
width: 48,
height: 56,
child: TextField(
controller: _controllers[index],
focusNode: _focusNodes[index],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 1,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
counterText: '',
contentPadding: EdgeInsets.zero,
filled: true,
fillColor: _controllers[index].text.isNotEmpty
? AppTheme.primaryColor.withValues(alpha: 0.05)
: const Color(0xFFF1F5F9),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppTheme.primaryColor, width: 2),
),
),
onChanged: (v) {
if (v.isNotEmpty && index < 5) {
_focusNodes[index + 1].requestFocus();
}
if (v.isEmpty && index > 0) {
_focusNodes[index - 1].requestFocus();
}
setState(() {});
if (index == 5 && v.isNotEmpty) {
_verify();
}
},
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/widgets/loading_overlay.dart';
final evaluationsProvider =
FutureProvider.autoDispose.family<List<dynamic>, String>((ref, uuid) async {
return ref.watch(apiServiceProvider).getEvaluations(uuid);
});
class EvaluationsScreen extends ConsumerWidget {
final String participantUuid;
const EvaluationsScreen({super.key, required this.participantUuid});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final evaluations = ref.watch(evaluationsProvider(participantUuid));
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('evaluations')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, size: 18),
onPressed: () => context.pop(),
),
),
body: evaluations.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (items) {
if (items.isEmpty) {
return EmptyState(icon: Icons.assessment, message: l.t('no_evaluations'));
}
return ListView.builder(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (ctx, i) {
final eval = items[i] as Map<String, dynamic>;
return _EvaluationCard(evaluation: eval, index: i);
},
);
},
),
);
}
}
class _EvaluationCard extends StatelessWidget {
final Map<String, dynamic> evaluation;
final int index;
const _EvaluationCard({required this.evaluation, required this.index});
@override
Widget build(BuildContext context) {
final criteria = (evaluation['criteria'] as List?) ?? [];
final overallScore = evaluation['overall_score'] ?? 0;
final date = evaluation['evaluation_date'] ?? '';
final trainerName = evaluation['trainer_name'] ?? '';
return Container(
margin: const EdgeInsets.only(bottom: 14),
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 12, offset: const Offset(0, 3)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: _scoreColor(overallScore).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'$overallScore',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: _scoreColor(overallScore),
),
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
evaluation['period_name'] ?? 'تقييم',
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
const SizedBox(height: 2),
Text(
'$trainerName$date',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
),
],
),
),
],
),
if (criteria.isNotEmpty) ...[
const SizedBox(height: 16),
...criteria.map((c) {
final crit = c as Map<String, dynamic>;
final score = (crit['score'] ?? 0) as num;
final maxScore = (crit['max_score'] ?? 10) as num;
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
crit['name'] ?? '',
style: const TextStyle(fontSize: 13),
),
),
Text(
'$score/$maxScore',
style: TextStyle(fontSize: 12, color: Colors.grey.shade600, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: maxScore > 0 ? score / maxScore : 0,
backgroundColor: Colors.grey.shade100,
color: _scoreColor((score / maxScore * 10).round()),
minHeight: 6,
),
),
],
),
);
}),
],
if (evaluation['notes'] != null && (evaluation['notes'] as String).isNotEmpty) ...[
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF8FAFC),
borderRadius: BorderRadius.circular(10),
),
child: Text(
evaluation['notes'],
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
),
),
],
],
),
)
.animate(delay: Duration(milliseconds: 80 * index))
.fadeIn(duration: 450.ms)
.slideY(begin: 0.04, end: 0);
}
Color _scoreColor(dynamic score) {
final s = (score as num).toDouble();
if (s >= 8) return Colors.green;
if (s >= 6) return Colors.orange;
if (s >= 4) return Colors.amber;
return Colors.red;
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/loading_overlay.dart';
final eventsProvider = FutureProvider.autoDispose<Map<String, dynamic>>((ref) async {
return ref.watch(apiServiceProvider).getEvents();
});
class EventsScreen extends ConsumerWidget {
const EventsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final events = ref.watch(eventsProvider);
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('events')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
),
body: RefreshIndicator(
onRefresh: () async => ref.invalidate(eventsProvider),
child: events.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (data) {
final items = (data['data'] as List?) ?? [];
if (items.isEmpty) {
return EmptyState(icon: Icons.event, message: l.t('no_events'));
}
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
padding: const EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (ctx, i) {
final event = items[i] as Map<String, dynamic>;
return _EventCard(event: event, index: i, ref: ref);
},
);
},
),
),
);
}
}
class _EventCard extends StatelessWidget {
final Map<String, dynamic> event;
final int index;
final WidgetRef ref;
const _EventCard({required this.event, required this.index, required this.ref});
@override
Widget build(BuildContext context) {
final isRegistered = event['is_registered'] == true;
return Container(
margin: const EdgeInsets.only(bottom: 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 12, offset: const Offset(0, 3)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Image header
Container(
height: 140,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.08),
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
),
child: event['image_url'] != null
? ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
child: Image.network(
event['image_url'],
width: double.infinity,
fit: BoxFit.cover,
),
)
: Center(
child: Icon(Icons.event, size: 48, color: AppTheme.primaryColor.withValues(alpha: 0.3)),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event['title'] ?? '',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.calendar_today, size: 14, color: Colors.grey.shade500),
const SizedBox(width: 6),
Text(
event['date'] ?? '',
style: TextStyle(fontSize: 13, color: Colors.grey.shade500),
),
const SizedBox(width: 16),
Icon(Icons.access_time, size: 14, color: Colors.grey.shade500),
const SizedBox(width: 6),
Text(
event['time'] ?? '',
style: TextStyle(fontSize: 13, color: Colors.grey.shade500),
),
],
),
if (event['location'] != null) ...[
const SizedBox(height: 6),
Row(
children: [
Icon(Icons.location_on, size: 14, color: Colors.grey.shade500),
const SizedBox(width: 6),
Text(
event['location'],
style: TextStyle(fontSize: 13, color: Colors.grey.shade500),
),
],
),
],
const SizedBox(height: 14),
SizedBox(
width: double.infinity,
height: 42,
child: ElevatedButton(
onPressed: isRegistered
? null
: () async {
await ref.read(apiServiceProvider).registerForEvent(event['uuid'], '');
ref.invalidate(eventsProvider);
},
style: ElevatedButton.styleFrom(
backgroundColor: isRegistered ? Colors.grey.shade200 : AppTheme.primaryColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: Text(
isRegistered ? 'مسجل ✓' : 'سجل الآن',
style: TextStyle(
fontSize: 14,
color: isRegistered ? Colors.grey.shade600 : Colors.white,
),
),
),
),
],
),
),
],
),
)
.animate(delay: Duration(milliseconds: 100 * index))
.fadeIn(duration: 500.ms)
.slideY(begin: 0.05, end: 0, curve: Curves.easeOutCubic);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/api/api_service.dart';
import '../../core/auth/auth_store.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/utils/money.dart';
import '../../core/widgets/animated_card.dart';
final dashboardProvider = FutureProvider.autoDispose<Map<String, dynamic>>((ref) async {
return ref.watch(apiServiceProvider).getDashboard();
});
class DashboardScreen extends ConsumerWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final auth = ref.watch(authStoreProvider);
final dashboard = ref.watch(dashboardProvider);
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
body: RefreshIndicator(
onRefresh: () async => ref.invalidate(dashboardProvider),
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
slivers: [
// Header
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.only(
top: MediaQuery.paddingOf(context).top + 16,
left: 20,
right: 20,
bottom: 20,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(24)),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 10, offset: const Offset(0, 4)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_greeting(l),
style: TextStyle(fontSize: 14, color: Colors.grey.shade500),
),
const SizedBox(height: 4),
Text(
auth.userName ?? '',
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
],
),
),
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(14),
),
child: Icon(Icons.person, color: AppTheme.primaryColor),
),
],
).animate().fadeIn(duration: 500.ms).slideY(begin: -0.1, end: 0),
],
),
),
),
// Body
dashboard.when(
loading: () => const SliverFillRemaining(
child: Center(child: CircularProgressIndicator()),
),
error: (e, _) => SliverFillRemaining(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, size: 48, color: Colors.grey.shade300),
const SizedBox(height: 12),
Text(l.t('error_occurred'), style: TextStyle(color: Colors.grey.shade500)),
const SizedBox(height: 12),
TextButton(
onPressed: () => ref.invalidate(dashboardProvider),
child: Text(l.t('retry')),
),
],
),
),
),
data: (data) => SliverPadding(
padding: const EdgeInsets.all(20),
sliver: SliverList(
delegate: SliverChildListDelegate([
// Quick Stats
_QuickStats(data: data['totals'] ?? {}, l: l),
const SizedBox(height: 20),
// Children cards
if ((data['children'] as List?)?.isNotEmpty ?? false) ...[
_SectionHeader(title: l.t('my_children')),
const SizedBox(height: 12),
..._buildChildrenCards(context, data['children'] as List, l),
const SizedBox(height: 20),
],
// Today sessions
if ((data['today_sessions'] as List?)?.isNotEmpty ?? false) ...[
_SectionHeader(title: l.t('today_sessions')),
const SizedBox(height: 12),
..._buildSessionCards(data['today_sessions'] as List),
const SizedBox(height: 20),
],
// Quick Actions
_QuickActions(l: l),
const SizedBox(height: 40),
]),
),
),
),
],
),
),
);
}
String _greeting(AppLocalizations l) {
final hour = DateTime.now().hour;
return hour < 12 ? l.t('good_morning') : l.t('good_evening');
}
List<Widget> _buildChildrenCards(BuildContext context, List children, AppLocalizations l) {
return children.asMap().entries.map((entry) {
final i = entry.key;
final child = entry.value as Map<String, dynamic>;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: AnimatedCard(
onTap: () => context.push('/participants/${child['uuid']}'),
child: Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(14),
),
child: child['photo_url'] != null
? ClipRRect(
borderRadius: BorderRadius.circular(14),
child: Image.network(child['photo_url'], fit: BoxFit.cover),
)
: Icon(Icons.person, color: AppTheme.primaryColor),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
child['name_ar'] ?? '',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
if (child['next_session'] != null)
Text(
'${l.t('next_session')}: ${child['next_session']['group_name']}',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
),
],
),
),
if ((child['outstanding_balance'] ?? 0) > 0)
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Text(
child['outstanding_display'] ?? '',
style: TextStyle(fontSize: 12, color: Colors.red.shade700, fontWeight: FontWeight.w600),
),
),
const SizedBox(width: 8),
Icon(Icons.chevron_right, color: Colors.grey.shade300),
],
),
),
)
.animate(delay: Duration(milliseconds: 100 + (i * 80)))
.fadeIn(duration: 450.ms)
.slideX(begin: 0.05, end: 0, curve: Curves.easeOutCubic);
}).toList();
}
List<Widget> _buildSessionCards(List sessions) {
return sessions.asMap().entries.map((entry) {
final i = entry.key;
final s = entry.value as Map<String, dynamic>;
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.grey.shade100),
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: AppTheme.accentColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(Icons.sports, color: AppTheme.accentColor, size: 22),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
s['group_name'] ?? '',
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14),
),
Text(
'${s['start_time']} - ${s['end_time']}',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
),
],
),
),
_StatusChip(status: s['status'] ?? 'scheduled'),
],
),
),
)
.animate(delay: Duration(milliseconds: 200 + (i * 60)))
.fadeIn(duration: 400.ms)
.slideY(begin: 0.05, end: 0);
}).toList();
}
}
class _QuickStats extends StatelessWidget {
final Map<String, dynamic> data;
final AppLocalizations l;
const _QuickStats({required this.data, required this.l});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: _StatCard(
icon: Icons.account_balance_wallet,
label: l.t('total_balance'),
value: formatMoney(data['outstanding_balance'] ?? 0),
color: Colors.orange,
),
),
const SizedBox(width: 12),
Expanded(
child: _StatCard(
icon: Icons.today,
label: l.t('today_sessions'),
value: '${data['today_sessions'] ?? 0}',
color: AppTheme.primaryColor,
),
),
],
).animate(delay: 200.ms).fadeIn(duration: 500.ms).slideY(begin: 0.08, end: 0);
}
}
class _StatCard extends StatelessWidget {
final IconData icon;
final String label;
final String value;
final Color color;
const _StatCard({required this.icon, required this.label, required this.value, required this.color});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: color.withValues(alpha: 0.1)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, color: color, size: 22),
const SizedBox(height: 10),
Text(value, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: color)),
const SizedBox(height: 4),
Text(label, style: TextStyle(fontSize: 12, color: Colors.grey.shade500)),
],
),
);
}
}
class _SectionHeader extends StatelessWidget {
final String title;
final VoidCallback? onViewAll;
const _SectionHeader({required this.title, this.onViewAll});
@override
Widget build(BuildContext context) {
return Row(
children: [
Text(title, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold)),
const Spacer(),
if (onViewAll != null)
TextButton(
onPressed: onViewAll,
child: const Text('عرض الكل', style: TextStyle(fontSize: 13)),
),
],
);
}
}
class _StatusChip extends StatelessWidget {
final String status;
const _StatusChip({required this.status});
@override
Widget build(BuildContext context) {
final (color, label) = switch (status) {
'scheduled' => (Colors.blue, 'مجدولة'),
'in_progress' => (Colors.green, 'جارية'),
'completed' => (Colors.grey, 'مكتملة'),
'cancelled' => (Colors.red, 'ملغاة'),
_ => (Colors.grey, status),
};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(label, style: TextStyle(fontSize: 11, color: color, fontWeight: FontWeight.w600)),
);
}
}
class _QuickActions extends StatelessWidget {
final AppLocalizations l;
const _QuickActions({required this.l});
@override
Widget build(BuildContext context) {
final actions = [
(Icons.event, l.t('events'), '/events', AppTheme.primaryColor),
(Icons.shopping_bag_rounded, l.t('shop'), '/shop', AppTheme.accentColor),
(Icons.mail_rounded, l.t('messages'), '/messages', Colors.teal),
(Icons.description_rounded, l.t('service_requests'), '/service-requests', Colors.purple),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SectionHeader(title: l.t('more')),
const SizedBox(height: 12),
GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.85,
children: actions.asMap().entries.map((entry) {
final i = entry.key;
final (icon, label, route, color) = entry.value;
return GestureDetector(
onTap: () => context.push(route),
child: Column(
children: [
Container(
width: 52,
height: 52,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
)
.animate(delay: Duration(milliseconds: 400 + (i * 80)))
.fadeIn(duration: 400.ms)
.scale(begin: const Offset(0.8, 0.8), end: const Offset(1, 1), curve: Curves.easeOutBack);
}).toList(),
),
],
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
class HomeShell extends ConsumerStatefulWidget {
final Widget child;
const HomeShell({super.key, required this.child});
@override
ConsumerState<HomeShell> createState() => _HomeShellState();
}
class _HomeShellState extends ConsumerState<HomeShell> {
int _currentIndex = 0;
static const _routes = ['/home', '/schedule', '/notifications', '/profile'];
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
final location = GoRouterState.of(context).matchedLocation;
_currentIndex = _routes.indexOf(location).clamp(0, 3);
return Scaffold(
body: widget.child,
bottomNavigationBar: Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 20,
offset: const Offset(0, -5),
),
],
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_NavItem(
icon: Icons.home_rounded,
label: l.t('home'),
isActive: _currentIndex == 0,
onTap: () => _navigate(0),
),
_NavItem(
icon: Icons.calendar_month_rounded,
label: l.t('schedule'),
isActive: _currentIndex == 1,
onTap: () => _navigate(1),
),
_NavItem(
icon: Icons.notifications_rounded,
label: l.t('notifications'),
isActive: _currentIndex == 2,
onTap: () => _navigate(2),
badge: 0, // TODO: wire to unread count
),
_NavItem(
icon: Icons.person_rounded,
label: l.t('profile'),
isActive: _currentIndex == 3,
onTap: () => _navigate(3),
),
],
),
),
),
),
);
}
void _navigate(int index) {
if (_currentIndex == index) return;
context.go(_routes[index]);
}
}
class _NavItem extends StatelessWidget {
final IconData icon;
final String label;
final bool isActive;
final VoidCallback onTap;
final int? badge;
const _NavItem({
required this.icon,
required this.label,
required this.isActive,
required this.onTap,
this.badge,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOutCubic,
padding: EdgeInsets.symmetric(
horizontal: isActive ? 16 : 12,
vertical: 8,
),
decoration: BoxDecoration(
color: isActive ? AppTheme.primaryColor.withValues(alpha: 0.1) : Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Stack(
clipBehavior: Clip.none,
children: [
AnimatedScale(
scale: isActive ? 1.1 : 1.0,
duration: const Duration(milliseconds: 200),
child: Icon(
icon,
size: 24,
color: isActive ? AppTheme.primaryColor : Colors.grey.shade400,
),
),
if (badge != null && badge! > 0)
Positioned(
right: -6,
top: -4,
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
'$badge',
style: const TextStyle(color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold),
),
),
),
],
),
const SizedBox(height: 4),
AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 200),
style: TextStyle(
fontSize: 11,
fontFamily: 'Cairo',
fontWeight: isActive ? FontWeight.w600 : FontWeight.w400,
color: isActive ? AppTheme.primaryColor : Colors.grey.shade400,
),
child: Text(label),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/utils/money.dart';
import '../../core/widgets/loading_overlay.dart';
final invoicesProvider =
FutureProvider.autoDispose.family<Map<String, dynamic>, String>((ref, uuid) async {
return ref.watch(apiServiceProvider).getInvoices(uuid);
});
class InvoicesScreen extends ConsumerWidget {
final String participantUuid;
const InvoicesScreen({super.key, required this.participantUuid});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final invoices = ref.watch(invoicesProvider(participantUuid));
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('invoices')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, size: 18),
onPressed: () => context.pop(),
),
),
body: invoices.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (data) {
final items = (data['data'] as List?) ?? [];
if (items.isEmpty) {
return EmptyState(icon: Icons.receipt_long, message: l.t('no_invoices'));
}
return ListView.builder(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (ctx, i) {
final inv = items[i] as Map<String, dynamic>;
return _InvoiceTile(invoice: inv, index: i);
},
);
},
),
);
}
}
class _InvoiceTile extends StatelessWidget {
final Map<String, dynamic> invoice;
final int index;
const _InvoiceTile({required this.invoice, required this.index});
@override
Widget build(BuildContext context) {
final status = invoice['status'] ?? 'draft';
final (statusColor, statusLabel) = _statusInfo(status);
final total = invoice['total_amount'] ?? 0;
final balance = invoice['balance_due'] ?? 0;
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
statusLabel,
style: TextStyle(fontSize: 11, color: statusColor, fontWeight: FontWeight.w600),
),
),
const Spacer(),
Text(
invoice['invoice_number'] ?? '',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
invoice['description'] ?? invoice['items_summary'] ?? '',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
invoice['issued_date'] ?? '',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
formatMoney(total),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
if (balance > 0)
Text(
'متبقي: ${formatMoney(balance)}',
style: TextStyle(fontSize: 12, color: Colors.red.shade600),
),
],
),
],
),
if (balance > 0 && status != 'cancelled') ...[
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
height: 40,
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: AppTheme.primaryColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: const Text('ادفع الآن', style: TextStyle(fontSize: 13, color: Colors.white)),
),
),
],
],
),
)
.animate(delay: Duration(milliseconds: 60 * index))
.fadeIn(duration: 400.ms)
.slideY(begin: 0.04, end: 0, curve: Curves.easeOut);
}
(Color, String) _statusInfo(String status) => switch (status) {
'paid' => (Colors.green, 'مدفوعة'),
'partially_paid' => (Colors.orange, 'مدفوعة جزئياً'),
'sent' => (Colors.blue, 'مرسلة'),
'overdue' => (Colors.red, 'متأخرة'),
'cancelled' => (Colors.grey, 'ملغاة'),
'draft' => (Colors.grey.shade600, 'مسودة'),
_ => (Colors.grey, status),
};
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/loading_overlay.dart';
final messagesProvider = FutureProvider.autoDispose<Map<String, dynamic>>((ref) async {
return ref.watch(apiServiceProvider).getMessages();
});
class MessagesScreen extends ConsumerStatefulWidget {
const MessagesScreen({super.key});
@override
ConsumerState<MessagesScreen> createState() => _MessagesScreenState();
}
class _MessagesScreenState extends ConsumerState<MessagesScreen> {
final _messageController = TextEditingController();
bool _isSending = false;
@override
void dispose() {
_messageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
final messages = ref.watch(messagesProvider);
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('messages')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
),
body: Column(
children: [
Expanded(
child: messages.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (data) {
final items = (data['data'] as List?) ?? [];
if (items.isEmpty) {
return EmptyState(icon: Icons.chat_bubble_outline, message: l.t('no_messages'));
}
return ListView.builder(
physics: const BouncingScrollPhysics(),
reverse: true,
padding: const EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (ctx, i) {
final msg = items[i] as Map<String, dynamic>;
final isMe = msg['is_mine'] == true;
return _MessageBubble(message: msg, isMe: isMe, index: i);
},
);
},
),
),
// Input
Container(
padding: EdgeInsets.only(
left: 16,
right: 16,
top: 12,
bottom: MediaQuery.paddingOf(context).bottom + 12,
),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 10, offset: const Offset(0, -2)),
],
),
child: Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(24),
),
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: l.t('type_message'),
border: InputBorder.none,
hintStyle: TextStyle(color: Colors.grey.shade400, fontSize: 14),
),
maxLines: null,
),
),
),
const SizedBox(width: 10),
GestureDetector(
onTap: _isSending ? null : _send,
child: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: AppTheme.primaryColor,
borderRadius: BorderRadius.circular(14),
),
child: _isSending
? const Padding(
padding: EdgeInsets.all(12),
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
)
: const Icon(Icons.send, color: Colors.white, size: 20),
),
),
],
),
),
],
),
);
}
Future<void> _send() async {
final text = _messageController.text.trim();
if (text.isEmpty) return;
setState(() => _isSending = true);
try {
await ref.read(apiServiceProvider).sendMessage({'body': text});
_messageController.clear();
ref.invalidate(messagesProvider);
} catch (_) {}
setState(() => _isSending = false);
}
}
class _MessageBubble extends StatelessWidget {
final Map<String, dynamic> message;
final bool isMe;
final int index;
const _MessageBubble({required this.message, required this.isMe, required this.index});
@override
Widget build(BuildContext context) {
return Align(
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width * 0.75),
decoration: BoxDecoration(
color: isMe ? AppTheme.primaryColor : Colors.white,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(16),
topRight: const Radius.circular(16),
bottomLeft: Radius.circular(isMe ? 16 : 4),
bottomRight: Radius.circular(isMe ? 4 : 16),
),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 6, offset: const Offset(0, 2)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message['body'] ?? '',
style: TextStyle(
fontSize: 14,
color: isMe ? Colors.white : Colors.black87,
),
),
const SizedBox(height: 4),
Text(
message['sent_at'] ?? '',
style: TextStyle(
fontSize: 10,
color: isMe ? Colors.white60 : Colors.grey.shade400,
),
),
],
),
),
)
.animate(delay: Duration(milliseconds: 30 * index))
.fadeIn(duration: 300.ms)
.slideY(begin: 0.05, end: 0);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/loading_overlay.dart';
final notificationsProvider = FutureProvider.autoDispose<Map<String, dynamic>>((ref) async {
return ref.watch(apiServiceProvider).getNotifications();
});
class NotificationsScreen extends ConsumerWidget {
const NotificationsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final notifications = ref.watch(notificationsProvider);
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('notifications')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
actions: [
TextButton(
onPressed: () async {
await ref.read(apiServiceProvider).markAllNotificationsRead();
ref.invalidate(notificationsProvider);
},
child: Text(l.t('mark_all_read'), style: const TextStyle(fontSize: 12)),
),
],
),
body: RefreshIndicator(
onRefresh: () async => ref.invalidate(notificationsProvider),
child: notifications.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (data) {
final items = (data['data'] as List?) ?? [];
if (items.isEmpty) {
return EmptyState(icon: Icons.notifications_none, message: l.t('no_notifications'));
}
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
padding: const EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (ctx, i) {
final n = items[i] as Map<String, dynamic>;
return _NotificationTile(notification: n, index: i);
},
);
},
),
),
);
}
}
class _NotificationTile extends StatelessWidget {
final Map<String, dynamic> notification;
final int index;
const _NotificationTile({required this.notification, required this.index});
@override
Widget build(BuildContext context) {
final isRead = notification['read_at'] != null;
final type = notification['type'] ?? '';
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: isRead ? Colors.white : AppTheme.primaryColor.withValues(alpha: 0.03),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: isRead ? Colors.grey.shade100 : AppTheme.primaryColor.withValues(alpha: 0.15),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: _iconColor(type).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(_icon(type), color: _iconColor(type), size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
notification['title'] ?? '',
style: TextStyle(
fontSize: 14,
fontWeight: isRead ? FontWeight.w400 : FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
notification['body'] ?? '',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 6),
Text(
notification['created_at'] ?? '',
style: TextStyle(fontSize: 10, color: Colors.grey.shade400),
),
],
),
),
if (!isRead)
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: AppTheme.primaryColor,
shape: BoxShape.circle,
),
),
],
),
)
.animate(delay: Duration(milliseconds: 50 * index))
.fadeIn(duration: 350.ms)
.slideX(begin: 0.03, end: 0);
}
IconData _icon(String type) => switch (type) {
'payment' => Icons.payment,
'attendance' => Icons.check_circle_outline,
'invoice' => Icons.receipt_long,
'session' => Icons.event,
'announcement' => Icons.campaign,
_ => Icons.notifications_outlined,
};
Color _iconColor(String type) => switch (type) {
'payment' => Colors.green,
'attendance' => Colors.blue,
'invoice' => Colors.orange,
'session' => Colors.purple,
'announcement' => Colors.red,
_ => AppTheme.primaryColor,
};
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/utils/money.dart';
final participantDetailProvider =
FutureProvider.autoDispose.family<Map<String, dynamic>, String>((ref, uuid) async {
return ref.watch(apiServiceProvider).getParticipant(uuid);
});
final participantSummaryProvider =
FutureProvider.autoDispose.family<Map<String, dynamic>, String>((ref, uuid) async {
return ref.watch(apiServiceProvider).getParticipantSummary(uuid);
});
class ParticipantDetailScreen extends ConsumerWidget {
final String uuid;
const ParticipantDetailScreen({super.key, required this.uuid});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final detail = ref.watch(participantDetailProvider(uuid));
final summary = ref.watch(participantSummaryProvider(uuid));
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
body: detail.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (participant) => CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
// Header
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.only(
top: MediaQuery.paddingOf(context).top + 10,
left: 20,
right: 20,
bottom: 24,
),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
),
child: Column(
children: [
// Back button
Row(
children: [
GestureDetector(
onTap: () => context.pop(),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.arrow_back_ios_new, size: 18),
),
),
],
),
const SizedBox(height: 16),
// Avatar
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: participant['photo_url'] != null
? ClipOval(child: Image.network(participant['photo_url'], fit: BoxFit.cover))
: Icon(Icons.person, size: 40, color: AppTheme.primaryColor),
).animate().scale(
begin: const Offset(0.7, 0.7),
end: const Offset(1, 1),
duration: 500.ms,
curve: Curves.easeOutBack,
),
const SizedBox(height: 12),
Text(
participant['name_ar'] ?? participant['name'] ?? '',
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
).animate(delay: 100.ms).fadeIn(duration: 400.ms),
const SizedBox(height: 4),
_StatusBadge(status: participant['status'] ?? 'active'),
],
),
),
),
// Summary stats
SliverToBoxAdapter(
child: summary.when(
loading: () => const SizedBox(height: 80, child: Center(child: CircularProgressIndicator())),
error: (_, __) => const SizedBox.shrink(),
data: (s) => Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
_MiniStat(
label: l.t('attendance_rate'),
value: '${s['attendance_rate'] ?? 0}%',
icon: Icons.check_circle,
color: Colors.green,
),
const SizedBox(width: 12),
_MiniStat(
label: l.t('total_balance'),
value: formatMoney(s['outstanding_balance'] ?? 0),
icon: Icons.account_balance_wallet,
color: Colors.orange,
),
const SizedBox(width: 12),
_MiniStat(
label: l.t('enrollments'),
value: '${s['active_enrollments'] ?? 0}',
icon: Icons.school,
color: Colors.blue,
),
],
).animate(delay: 200.ms).fadeIn(duration: 500.ms).slideY(begin: 0.05, end: 0),
),
),
),
// Action Grid
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 20),
sliver: SliverGrid.count(
crossAxisCount: 3,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 1.0,
children: [
_ActionTile(
icon: Icons.event_note,
label: l.t('schedule'),
color: Colors.purple,
onTap: () => context.push('/participants/$uuid/schedule'),
index: 0,
),
_ActionTile(
icon: Icons.fact_check,
label: l.t('attendance'),
color: Colors.green,
onTap: () => context.push('/participants/$uuid/attendance'),
index: 1,
),
_ActionTile(
icon: Icons.receipt_long,
label: l.t('invoices'),
color: Colors.orange,
onTap: () => context.push('/participants/$uuid/invoices'),
index: 2,
),
_ActionTile(
icon: Icons.assessment,
label: l.t('evaluations'),
color: Colors.teal,
onTap: () => context.push('/participants/$uuid/evaluations'),
index: 3,
),
_ActionTile(
icon: Icons.account_balance_wallet,
label: l.t('wallet'),
color: Colors.indigo,
onTap: () {},
index: 4,
),
_ActionTile(
icon: Icons.folder,
label: l.t('documents'),
color: Colors.brown,
onTap: () {},
index: 5,
),
],
),
),
const SliverToBoxAdapter(child: SizedBox(height: 40)),
],
),
),
);
}
}
class _StatusBadge extends StatelessWidget {
final String status;
const _StatusBadge({required this.status});
@override
Widget build(BuildContext context) {
final (color, label) = switch (status) {
'active' => (Colors.green, 'نشط'),
'frozen' => (Colors.blue, 'مجمد'),
'suspended' => (Colors.red, 'موقوف'),
'registered' => (Colors.orange, 'مسجل'),
_ => (Colors.grey, status),
};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(label, style: TextStyle(fontSize: 12, color: color, fontWeight: FontWeight.w600)),
).animate(delay: 150.ms).fadeIn(duration: 300.ms);
}
}
class _MiniStat extends StatelessWidget {
final String label;
final String value;
final IconData icon;
final Color color;
const _MiniStat({required this.label, required this.value, required this.icon, required this.color});
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: color.withValues(alpha: 0.1)),
),
child: Column(
children: [
Icon(icon, size: 18, color: color),
const SizedBox(height: 6),
Text(value, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: color)),
const SizedBox(height: 2),
Text(label, style: TextStyle(fontSize: 10, color: Colors.grey.shade500), textAlign: TextAlign.center),
],
),
),
);
}
}
class _ActionTile extends StatelessWidget {
final IconData icon;
final String label;
final Color color;
final VoidCallback onTap;
final int index;
const _ActionTile({
required this.icon,
required this.label,
required this.color,
required this.onTap,
required this.index,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade100),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 22),
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
],
),
),
)
.animate(delay: Duration(milliseconds: 250 + (index * 60)))
.fadeIn(duration: 400.ms)
.scale(begin: const Offset(0.85, 0.85), end: const Offset(1, 1), curve: Curves.easeOutBack);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/api/api_service.dart';
import '../../core/auth/auth_store.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/l10n/locale_provider.dart';
import '../../core/theme/app_theme.dart';
final profileProvider = FutureProvider.autoDispose<Map<String, dynamic>>((ref) async {
return ref.watch(apiServiceProvider).getProfile();
});
class ProfileScreen extends ConsumerWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final auth = ref.watch(authStoreProvider);
ref.watch(profileProvider);
final locale = ref.watch(localeProvider);
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
// Profile header
SliverToBoxAdapter(
child: Container(
padding: EdgeInsets.only(
top: MediaQuery.paddingOf(context).top + 20,
bottom: 24,
),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
),
child: Column(
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.person, size: 40, color: AppTheme.primaryColor),
)
.animate()
.scale(begin: const Offset(0.7, 0.7), end: const Offset(1, 1), duration: 500.ms, curve: Curves.easeOutBack),
const SizedBox(height: 14),
Text(
auth.userName ?? '',
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
).animate(delay: 100.ms).fadeIn(duration: 400.ms),
const SizedBox(height: 4),
Text(
auth.userPhone ?? '',
style: TextStyle(fontSize: 14, color: Colors.grey.shade500),
).animate(delay: 150.ms).fadeIn(duration: 400.ms),
],
),
),
),
// Settings
SliverPadding(
padding: const EdgeInsets.all(20),
sliver: SliverList(
delegate: SliverChildListDelegate([
const SizedBox(height: 8),
// Language toggle
_SettingsTile(
icon: Icons.language,
title: l.t('language'),
trailing: Switch(
value: locale.languageCode == 'en',
onChanged: (_) => ref.read(localeProvider.notifier).toggle(),
activeColor: AppTheme.primaryColor,
),
subtitle: locale.languageCode == 'ar' ? 'العربية' : 'English',
).animate(delay: 200.ms).fadeIn(duration: 400.ms).slideX(begin: 0.03, end: 0),
const SizedBox(height: 10),
// Edit profile
_SettingsTile(
icon: Icons.edit,
title: l.t('edit_profile'),
onTap: () {},
).animate(delay: 250.ms).fadeIn(duration: 400.ms).slideX(begin: 0.03, end: 0),
const SizedBox(height: 10),
// Notification settings
_SettingsTile(
icon: Icons.notifications_outlined,
title: l.t('notification_settings'),
onTap: () {},
).animate(delay: 300.ms).fadeIn(duration: 400.ms).slideX(begin: 0.03, end: 0),
const SizedBox(height: 10),
// About
_SettingsTile(
icon: Icons.info_outline,
title: l.t('about'),
onTap: () {},
).animate(delay: 350.ms).fadeIn(duration: 400.ms).slideX(begin: 0.03, end: 0),
const SizedBox(height: 30),
// Logout
SizedBox(
width: double.infinity,
height: 52,
child: OutlinedButton.icon(
onPressed: () async {
await ref.read(authStoreProvider.notifier).logout();
if (context.mounted) context.go('/auth/login');
},
icon: const Icon(Icons.logout, color: Colors.red),
label: Text(
l.t('logout'),
style: const TextStyle(color: Colors.red, fontSize: 16),
),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.red),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
),
).animate(delay: 400.ms).fadeIn(duration: 400.ms).slideY(begin: 0.05, end: 0),
]),
),
),
],
),
);
}
}
class _SettingsTile extends StatelessWidget {
final IconData icon;
final String title;
final String? subtitle;
final Widget? trailing;
final VoidCallback? onTap;
const _SettingsTile({
required this.icon,
required this.title,
this.subtitle,
this.trailing,
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.grey.shade100),
),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: AppTheme.primaryColor, size: 20),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
if (subtitle != null) ...[
const SizedBox(height: 2),
Text(subtitle!, style: TextStyle(fontSize: 12, color: Colors.grey.shade500)),
],
],
),
),
if (trailing != null) trailing!,
if (trailing == null && onTap != null)
Icon(Icons.chevron_right, size: 20, color: Colors.grey.shade400),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/api/api_client.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
class RegistrationScreen extends ConsumerStatefulWidget {
const RegistrationScreen({super.key});
@override
ConsumerState<RegistrationScreen> createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends ConsumerState<RegistrationScreen> {
final _formKey = GlobalKey<FormState>();
final _nameArController = TextEditingController();
final _nameController = TextEditingController();
final _phoneController = TextEditingController();
final _emailController = TextEditingController();
final _childNameController = TextEditingController();
final _childAgeController = TextEditingController();
String _gender = 'male';
bool _isLoading = false;
String? _error;
int _step = 0;
@override
void dispose() {
_nameArController.dispose();
_nameController.dispose();
_phoneController.dispose();
_emailController.dispose();
_childNameController.dispose();
_childAgeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new, size: 18),
onPressed: () {
if (_step > 0) {
setState(() => _step--);
} else {
context.pop();
}
},
),
title: Text(l.t('pre_register')),
centerTitle: true,
),
body: SafeArea(
child: Column(
children: [
// Progress indicator
Padding(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 16),
child: Row(
children: List.generate(3, (i) {
return Expanded(
child: Container(
height: 4,
margin: const EdgeInsets.symmetric(horizontal: 3),
decoration: BoxDecoration(
color: i <= _step ? AppTheme.primaryColor : Colors.grey.shade200,
borderRadius: BorderRadius.circular(2),
),
),
);
}),
).animate().fadeIn(duration: 300.ms),
),
// Form
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Form(
key: _formKey,
child: AnimatedSwitcher(
duration: 300.ms,
transitionBuilder: (child, anim) {
return FadeTransition(
opacity: anim,
child: SlideTransition(
position: Tween(
begin: const Offset(0.05, 0),
end: Offset.zero,
).animate(anim),
child: child,
),
);
},
child: _buildStep(l),
),
),
),
),
// Bottom button
Padding(
padding: EdgeInsets.only(
left: 28,
right: 28,
bottom: MediaQuery.paddingOf(context).bottom + 16,
),
child: Column(
children: [
if (_error != null)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Text(
_error!,
style: const TextStyle(color: Colors.red, fontSize: 13),
).animate().shake(duration: 400.ms),
),
SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: _isLoading ? null : _next,
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2.5),
)
: Text(
_step == 2 ? l.t('submit') : l.t('next'),
style: const TextStyle(fontSize: 16),
),
),
),
],
),
).animate(delay: 200.ms).fadeIn(duration: 400.ms),
],
),
),
);
}
Widget _buildStep(AppLocalizations l) {
switch (_step) {
case 0:
return Column(
key: const ValueKey(0),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
Text(l.t('guardian_info'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text(
'بيانات ولي الأمر',
style: TextStyle(fontSize: 14, color: Colors.grey.shade500),
),
const SizedBox(height: 28),
_buildField(
controller: _nameArController,
label: 'الاسم بالعربي',
icon: Icons.person,
required: true,
),
const SizedBox(height: 14),
_buildField(
controller: _nameController,
label: 'Name in English',
icon: Icons.person_outline,
),
const SizedBox(height: 14),
Directionality(
textDirection: TextDirection.ltr,
child: _buildField(
controller: _phoneController,
label: 'رقم الهاتف',
icon: Icons.phone,
required: true,
keyboardType: TextInputType.phone,
formatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9+]'))],
),
),
const SizedBox(height: 14),
_buildField(
controller: _emailController,
label: 'البريد الإلكتروني (اختياري)',
icon: Icons.email,
keyboardType: TextInputType.emailAddress,
),
],
);
case 1:
return Column(
key: const ValueKey(1),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
Text(l.t('child_info'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text(
'بيانات المشترك',
style: TextStyle(fontSize: 14, color: Colors.grey.shade500),
),
const SizedBox(height: 28),
_buildField(
controller: _childNameController,
label: 'اسم المشترك',
icon: Icons.child_care,
required: true,
),
const SizedBox(height: 14),
Directionality(
textDirection: TextDirection.ltr,
child: _buildField(
controller: _childAgeController,
label: 'العمر',
icon: Icons.cake,
required: true,
keyboardType: TextInputType.number,
formatters: [FilteringTextInputFormatter.digitsOnly],
),
),
const SizedBox(height: 14),
// Gender selector
Text('الجنس', style: TextStyle(fontSize: 14, color: Colors.grey.shade600)),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => setState(() => _gender = 'male'),
child: AnimatedContainer(
duration: 200.ms,
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: _gender == 'male'
? AppTheme.primaryColor.withValues(alpha: 0.1)
: const Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(12),
border: _gender == 'male'
? Border.all(color: AppTheme.primaryColor)
: Border.all(color: Colors.transparent),
),
child: Center(
child: Text(
'ذكر',
style: TextStyle(
fontWeight: FontWeight.w600,
color: _gender == 'male' ? AppTheme.primaryColor : Colors.grey.shade600,
),
),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () => setState(() => _gender = 'female'),
child: AnimatedContainer(
duration: 200.ms,
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: _gender == 'female'
? Colors.pink.withValues(alpha: 0.1)
: const Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(12),
border: _gender == 'female'
? Border.all(color: Colors.pink)
: Border.all(color: Colors.transparent),
),
child: Center(
child: Text(
'أنثى',
style: TextStyle(
fontWeight: FontWeight.w600,
color: _gender == 'female' ? Colors.pink : Colors.grey.shade600,
),
),
),
),
),
),
],
),
],
);
case 2:
return Column(
key: const ValueKey(2),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
Text(l.t('review'), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text(
'تأكيد البيانات',
style: TextStyle(fontSize: 14, color: Colors.grey.shade500),
),
const SizedBox(height: 28),
_ReviewRow(label: 'ولي الأمر', value: _nameArController.text),
_ReviewRow(label: 'الهاتف', value: _phoneController.text),
if (_emailController.text.isNotEmpty)
_ReviewRow(label: 'البريد', value: _emailController.text),
const Divider(height: 24),
_ReviewRow(label: 'المشترك', value: _childNameController.text),
_ReviewRow(label: 'العمر', value: _childAgeController.text),
_ReviewRow(label: 'الجنس', value: _gender == 'male' ? 'ذكر' : 'أنثى'),
const SizedBox(height: 20),
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.amber.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.amber.shade200),
),
child: Row(
children: [
Icon(Icons.info_outline, color: Colors.amber.shade700, size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
'يرجى الحضور للمقر لإتمام التسجيل والدفع',
style: TextStyle(fontSize: 13, color: Colors.amber.shade800),
),
),
],
),
),
],
);
default:
return const SizedBox.shrink();
}
}
Widget _buildField({
required TextEditingController controller,
required String label,
required IconData icon,
bool required = false,
TextInputType? keyboardType,
List<TextInputFormatter>? formatters,
}) {
return TextFormField(
controller: controller,
keyboardType: keyboardType,
inputFormatters: formatters,
decoration: InputDecoration(
labelText: label,
prefixIcon: Icon(icon, size: 20),
filled: true,
fillColor: const Color(0xFFF1F5F9),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppTheme.primaryColor, width: 1.5),
),
),
validator: required ? (v) => (v == null || v.isEmpty) ? '' : null : null,
);
}
void _next() {
setState(() => _error = null);
if (_step == 0) {
if (_nameArController.text.isEmpty || _phoneController.text.isEmpty) {
setState(() => _error = 'يرجى ملء الحقول المطلوبة');
return;
}
setState(() => _step = 1);
} else if (_step == 1) {
if (_childNameController.text.isEmpty || _childAgeController.text.isEmpty) {
setState(() => _error = 'يرجى ملء الحقول المطلوبة');
return;
}
setState(() => _step = 2);
} else {
_submit();
}
}
Future<void> _submit() async {
setState(() => _isLoading = true);
try {
await ref.read(apiServiceProvider).preRegister(
guardianNameAr: _nameArController.text,
guardianName: _nameController.text,
phone: _phoneController.text,
email: _emailController.text,
participantName: _childNameController.text,
participantAge: int.tryParse(_childAgeController.text) ?? 0,
gender: _gender,
);
if (mounted) {
_showSuccess();
}
} catch (e) {
setState(() {
_isLoading = false;
_error = e is ApiException ? e.message : 'حدث خطأ';
});
}
}
void _showSuccess() {
showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
child: Padding(
padding: const EdgeInsets.all(28),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 70,
height: 70,
decoration: BoxDecoration(
color: Colors.green.shade50,
shape: BoxShape.circle,
),
child: Icon(Icons.check_circle, size: 40, color: Colors.green.shade600),
).animate().scale(
begin: const Offset(0.5, 0.5),
end: const Offset(1, 1),
duration: 500.ms,
curve: Curves.easeOutBack,
),
const SizedBox(height: 20),
const Text(
'تم التسجيل بنجاح!',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'يرجى الحضور للمقر لإتمام التسجيل',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, color: Colors.grey.shade600),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.pop(ctx);
context.go('/auth/login');
},
child: const Text('حسناً'),
),
),
],
),
),
),
);
}
}
class _ReviewRow extends StatelessWidget {
final String label;
final String value;
const _ReviewRow({required this.label, required this.value});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
children: [
SizedBox(
width: 80,
child: Text(label, style: TextStyle(fontSize: 13, color: Colors.grey.shade500)),
),
Expanded(
child: Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/api/api_service.dart';
import '../../core/auth/auth_store.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/loading_overlay.dart';
final scheduleProvider = FutureProvider.autoDispose.family<List<dynamic>, String>((ref, participantId) async {
return ref.watch(apiServiceProvider).getSchedule(participantId);
});
class ScheduleScreen extends ConsumerStatefulWidget {
const ScheduleScreen({super.key});
@override
ConsumerState<ScheduleScreen> createState() => _ScheduleScreenState();
}
class _ScheduleScreenState extends ConsumerState<ScheduleScreen> {
int _selectedDay = DateTime.now().weekday - 1;
static const _daysEn = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
final auth = ref.watch(authStoreProvider);
final participants = auth.participants;
final firstId = participants.isNotEmpty ? (participants.first['uuid'] ?? '') : '';
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('schedule')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
),
body: Column(
children: [
// Day selector
Container(
height: 80,
color: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 12),
itemCount: 7,
itemBuilder: (ctx, i) {
final isSelected = i == _selectedDay;
final now = DateTime.now();
final dayDate = now.subtract(Duration(days: now.weekday - 1 - i));
return GestureDetector(
onTap: () => setState(() => _selectedDay = i),
child: AnimatedContainer(
duration: 250.ms,
width: 52,
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: isSelected ? AppTheme.primaryColor : Colors.transparent,
borderRadius: BorderRadius.circular(14),
border: isSelected ? null : Border.all(color: Colors.grey.shade200),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_daysEn[i],
style: TextStyle(
fontSize: 11,
color: isSelected ? Colors.white70 : Colors.grey.shade500,
),
),
const SizedBox(height: 4),
Text(
'${dayDate.day}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: isSelected ? Colors.white : Colors.black87,
),
),
],
),
),
).animate(delay: Duration(milliseconds: i * 40)).fadeIn(duration: 300.ms);
},
),
),
const SizedBox(height: 8),
// Sessions
Expanded(
child: firstId.isEmpty
? EmptyState(icon: Icons.calendar_today, message: l.t('no_sessions'))
: Consumer(
builder: (ctx, ref, _) {
final schedule = ref.watch(scheduleProvider(firstId));
return schedule.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (sessions) {
final filtered = sessions.where((s) {
final dayName = (s as Map)['day_of_week'];
return dayName == _daysEn[_selectedDay].toLowerCase() ||
_matchDay(dayName, _selectedDay);
}).toList();
if (filtered.isEmpty) {
return EmptyState(
icon: Icons.event_busy,
message: l.t('no_sessions'),
);
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: filtered.length,
itemBuilder: (ctx, i) {
final session = filtered[i] as Map<String, dynamic>;
return _SessionTile(session: session, index: i);
},
);
},
);
},
),
),
],
),
);
}
bool _matchDay(dynamic dayName, int index) {
if (dayName == null) return false;
final d = dayName.toString().toLowerCase();
final matches = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
return index < matches.length && d == matches[index];
}
}
class _SessionTile extends StatelessWidget {
final Map<String, dynamic> session;
final int index;
const _SessionTile({required this.session, required this.index});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.03), blurRadius: 8, offset: const Offset(0, 2)),
],
),
child: Row(
children: [
// Time column
Column(
children: [
Text(
session['start_time'] ?? '',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: AppTheme.primaryColor),
),
Container(
width: 1,
height: 20,
margin: const EdgeInsets.symmetric(vertical: 4),
color: AppTheme.primaryColor.withValues(alpha: 0.3),
),
Text(
session['end_time'] ?? '',
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
),
],
),
const SizedBox(width: 16),
Container(
width: 4,
height: 50,
decoration: BoxDecoration(
color: AppTheme.primaryColor,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
session['group_name'] ?? '',
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
if (session['trainer_name'] != null)
Text(
session['trainer_name'],
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
),
if (session['facility_name'] != null) ...[
const SizedBox(height: 2),
Row(
children: [
Icon(Icons.location_on, size: 12, color: Colors.grey.shade400),
const SizedBox(width: 4),
Text(
session['facility_name'],
style: TextStyle(fontSize: 11, color: Colors.grey.shade400),
),
],
),
],
],
),
),
],
),
)
.animate(delay: Duration(milliseconds: 80 * index))
.fadeIn(duration: 400.ms)
.slideX(begin: 0.05, end: 0, curve: Curves.easeOut);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/widgets/loading_overlay.dart';
final serviceRequestsProvider = FutureProvider.autoDispose<List<dynamic>>((ref) async {
return ref.watch(apiServiceProvider).getServiceRequests();
});
class ServiceRequestsScreen extends ConsumerStatefulWidget {
const ServiceRequestsScreen({super.key});
@override
ConsumerState<ServiceRequestsScreen> createState() => _ServiceRequestsScreenState();
}
class _ServiceRequestsScreenState extends ConsumerState<ServiceRequestsScreen> {
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
final requests = ref.watch(serviceRequestsProvider);
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('service_requests')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
),
floatingActionButton: FloatingActionButton.extended(
onPressed: _showCreateDialog,
backgroundColor: AppTheme.primaryColor,
icon: const Icon(Icons.add, color: Colors.white),
label: const Text('طلب جديد', style: TextStyle(color: Colors.white)),
),
body: RefreshIndicator(
onRefresh: () async => ref.invalidate(serviceRequestsProvider),
child: requests.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (items) {
if (items.isEmpty) {
return EmptyState(icon: Icons.description, message: l.t('no_requests'));
}
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
padding: const EdgeInsets.all(16),
itemCount: items.length,
itemBuilder: (ctx, i) {
final req = items[i] as Map<String, dynamic>;
return _RequestTile(request: req, index: i);
},
);
},
),
),
);
}
void _showCreateDialog() {
final typeCtrl = TextEditingController();
final descCtrl = TextEditingController();
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (ctx) => Container(
padding: EdgeInsets.only(
left: 20,
right: 20,
top: 20,
bottom: MediaQuery.viewInsetsOf(ctx).bottom + 20,
),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 20),
const Text('طلب خدمة جديد', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
TextField(
controller: typeCtrl,
decoration: InputDecoration(
labelText: 'نوع الطلب',
filled: true,
fillColor: const Color(0xFFF1F5F9),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
),
const SizedBox(height: 14),
TextField(
controller: descCtrl,
maxLines: 3,
decoration: InputDecoration(
labelText: 'الوصف',
filled: true,
fillColor: const Color(0xFFF1F5F9),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: () async {
if (typeCtrl.text.isEmpty || descCtrl.text.isEmpty) return;
await ref.read(apiServiceProvider).createServiceRequest({
'type': typeCtrl.text,
'description': descCtrl.text,
});
ref.invalidate(serviceRequestsProvider);
if (ctx.mounted) Navigator.pop(ctx);
},
child: const Text('إرسال', style: TextStyle(fontSize: 16)),
),
),
],
),
),
);
}
}
class _RequestTile extends StatelessWidget {
final Map<String, dynamic> request;
final int index;
const _RequestTile({required this.request, required this.index});
@override
Widget build(BuildContext context) {
final status = request['status'] ?? 'pending';
final (statusColor, statusLabel) = _statusInfo(status);
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.grey.shade100),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(_statusIcon(status), color: statusColor, size: 18),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
request['type'] ?? '',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
Text(
request['created_at'] ?? '',
style: TextStyle(fontSize: 11, color: Colors.grey.shade500),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
statusLabel,
style: TextStyle(fontSize: 11, color: statusColor, fontWeight: FontWeight.w600),
),
),
],
),
if (request['description'] != null) ...[
const SizedBox(height: 10),
Text(
request['description'],
style: TextStyle(fontSize: 13, color: Colors.grey.shade600),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
if (request['response'] != null) ...[
const SizedBox(height: 10),
Container(
width: double.infinity,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Text(
request['response'],
style: TextStyle(fontSize: 12, color: Colors.green.shade700),
),
),
],
],
),
)
.animate(delay: Duration(milliseconds: 60 * index))
.fadeIn(duration: 350.ms)
.slideX(begin: 0.03, end: 0);
}
(Color, String) _statusInfo(String status) => switch (status) {
'pending' => (Colors.orange, 'قيد المراجعة'),
'in_progress' => (Colors.blue, 'جاري التنفيذ'),
'resolved' => (Colors.green, 'تم الحل'),
'rejected' => (Colors.red, 'مرفوض'),
_ => (Colors.grey, status),
};
IconData _statusIcon(String status) => switch (status) {
'pending' => Icons.hourglass_empty,
'in_progress' => Icons.autorenew,
'resolved' => Icons.check_circle,
'rejected' => Icons.cancel,
_ => Icons.info_outline,
};
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/api/api_service.dart';
import '../../core/l10n/app_localizations.dart';
import '../../core/theme/app_theme.dart';
import '../../core/utils/money.dart';
import '../../core/widgets/loading_overlay.dart';
final shopProvider = FutureProvider.autoDispose<List<dynamic>>((ref) async {
return ref.watch(apiServiceProvider).getProducts();
});
class ShopScreen extends ConsumerWidget {
const ShopScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l = AppLocalizations.of(context);
final shop = ref.watch(shopProvider);
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: Text(l.t('shop')),
centerTitle: true,
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
),
body: RefreshIndicator(
onRefresh: () async => ref.invalidate(shopProvider),
child: shop.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text(l.t('error_occurred'))),
data: (products) {
if (products.isEmpty) {
return EmptyState(icon: Icons.shopping_bag, message: l.t('no_products'));
}
return GridView.builder(
physics: const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.72,
),
itemCount: products.length,
itemBuilder: (ctx, i) {
final product = products[i] as Map<String, dynamic>;
return _ProductCard(product: product, index: i);
},
);
},
),
),
);
}
}
class _ProductCard extends StatelessWidget {
final Map<String, dynamic> product;
final int index;
const _ProductCard({required this.product, required this.index});
@override
Widget build(BuildContext context) {
final price = product['selling_price'] ?? 0;
final inStock = (product['quantity_available'] ?? 0) > 0;
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(color: Colors.black.withValues(alpha: 0.04), blurRadius: 10, offset: const Offset(0, 3)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Image
Expanded(
flex: 3,
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: AppTheme.primaryColor.withValues(alpha: 0.05),
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: product['image_url'] != null
? ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
child: Image.network(product['image_url'], fit: BoxFit.cover),
)
: Center(
child: Icon(Icons.inventory_2, size: 36, color: AppTheme.primaryColor.withValues(alpha: 0.3)),
),
),
),
// Info
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product['name_ar'] ?? product['name'] ?? '',
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Row(
children: [
Text(
formatMoney(price),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppTheme.primaryColor,
),
),
const Spacer(),
if (!inStock)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'نفد',
style: TextStyle(fontSize: 10, color: Colors.red.shade600),
),
),
],
),
],
),
),
),
],
),
)
.animate(delay: Duration(milliseconds: 60 * index))
.fadeIn(duration: 400.ms)
.scale(begin: const Offset(0.92, 0.92), end: const Offset(1, 1), curve: Curves.easeOut);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
class SplashScreen extends StatelessWidget {
const SplashScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(30),
),
child: Icon(
Icons.sports_soccer,
size: 60,
color: Theme.of(context).colorScheme.primary,
),
)
.animate()
.scale(
begin: const Offset(0.5, 0.5),
end: const Offset(1, 1),
duration: 600.ms,
curve: Curves.easeOutBack,
)
.fadeIn(duration: 400.ms),
const SizedBox(height: 24),
Text(
'الكابتن',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
)
.animate(delay: 300.ms)
.fadeIn(duration: 500.ms)
.slideY(begin: 0.3, end: 0),
const SizedBox(height: 32),
SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2.5,
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.5),
),
).animate(delay: 600.ms).fadeIn(duration: 400.ms),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'core/l10n/app_localizations.dart';
import 'core/l10n/locale_provider.dart';
import 'core/router/app_router.dart';
import 'core/theme/app_theme.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
systemNavigationBarColor: Colors.white,
));
runApp(const ProviderScope(child: ElCaptainApp()));
}
class ElCaptainApp extends ConsumerWidget {
const ElCaptainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(routerProvider);
final locale = ref.watch(localeProvider);
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'El Captain',
theme: AppTheme.light,
locale: locale,
supportedLocales: const [Locale('ar'), Locale('en')],
localizationsDelegates: const [
AppLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
routerConfig: router,
builder: (context, child) {
return Directionality(
textDirection: locale.languageCode == 'ar' ? TextDirection.rtl : TextDirection.ltr,
child: child!,
);
},
);
}
}
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
url: "https://pub.dev"
source: hosted
version: "85.0.0"
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11
url: "https://pub.dev"
source: hosted
version: "1.3.59"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c
url: "https://pub.dev"
source: hosted
version: "7.6.0"
analyzer_plugin:
dependency: transitive
description:
name: analyzer_plugin
sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce
url: "https://pub.dev"
source: hosted
version: "0.13.4"
archive:
dependency: transitive
description:
name: archive
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
url: "https://pub.dev"
source: hosted
version: "4.0.9"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
version: "2.13.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
build:
dependency: transitive
description:
name: build
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78
url: "https://pub.dev"
source: hosted
version: "4.1.2"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
url: "https://pub.dev"
source: hosted
version: "2.5.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
url: "https://pub.dev"
source: hosted
version: "9.1.2"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
url: "https://pub.dev"
source: hosted
version: "8.12.6"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
characters:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
url: "https://pub.dev"
source: hosted
version: "1.2.1"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d"
url: "https://pub.dev"
source: hosted
version: "4.11.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51"
url: "https://pub.dev"
source: hosted
version: "0.3.5+4"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
url: "https://pub.dev"
source: hosted
version: "1.0.9"
custom_lint_core:
dependency: transitive
description:
name: custom_lint_core
sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be"
url: "https://pub.dev"
source: hosted
version: "0.7.5"
custom_lint_visitor:
dependency: transitive
description:
name: custom_lint_visitor
sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2"
url: "https://pub.dev"
source: hosted
version: "1.0.0+7.7.0"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
dbus:
dependency: transitive
description:
name: dbus
sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645"
url: "https://pub.dev"
source: hosted
version: "0.7.14"
dio:
dependency: "direct main"
description:
name: dio
sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c"
url: "https://pub.dev"
source: hosted
version: "5.11.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
url: "https://pub.dev"
source: hosted
version: "0.9.5"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
url: "https://pub.dev"
source: hosted
version: "0.9.3+5"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5"
url: "https://pub.dev"
source: hosted
version: "3.15.2"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37"
url: "https://pub.dev"
source: hosted
version: "2.24.1"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc"
url: "https://pub.dev"
source: hosted
version: "15.2.10"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754"
url: "https://pub.dev"
source: hosted
version: "4.6.10"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390"
url: "https://pub.dev"
source: hosted
version: "3.10.10"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_animate:
dependency: "direct main"
description:
name: flutter_animate
sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5"
url: "https://pub.dev"
source: hosted
version: "4.5.2"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "1de7849213b4c73c85aca7e0ac687a9a5d82ccdb594366b9dcc26cb6a2189cd2"
url: "https://pub.dev"
source: hosted
version: "3.4.2"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610
url: "https://pub.dev"
source: hosted
version: "18.0.1"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
url: "https://pub.dev"
source: hosted
version: "2.0.35"
flutter_riverpod:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
url: "https://pub.dev"
source: hosted
version: "9.2.4"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
url: "https://pub.dev"
source: hosted
version: "1.2.3"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_shaders:
dependency: transitive
description:
name: flutter_shaders
sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2"
url: "https://pub.dev"
source: hosted
version: "0.1.3"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
gap:
dependency: "direct main"
description:
name: gap
sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d
url: "https://pub.dev"
source: hosted
version: "3.0.1"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3
url: "https://pub.dev"
source: hosted
version: "14.8.1"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hooks:
dependency: transitive
description:
name: hooks
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
url: "https://pub.dev"
source: hosted
version: "3.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
iconsax_flutter:
dependency: "direct main"
description:
name: iconsax_flutter
sha256: d14b4cec8586025ac15276bdd40f6eea308cb85748135965bb6255f14beb2564
url: "https://pub.dev"
source: hosted
version: "1.0.1"
image_picker:
dependency: "direct main"
description:
name: image_picker
sha256: d8402284df184bc05f4a2210c6c23983b0720f4cd87cbd05c5390a78af602667
url: "https://pub.dev"
source: hosted
version: "1.2.3"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a"
url: "https://pub.dev"
source: hosted
version: "0.8.13+19"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
url: "https://pub.dev"
source: hosted
version: "0.8.13+6"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
url: "https://pub.dev"
source: hosted
version: "0.2.2+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
url: "https://pub.dev"
source: hosted
version: "2.11.1"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
url: "https://pub.dev"
source: hosted
version: "0.2.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
io:
dependency: transitive
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
url: "https://pub.dev"
source: hosted
version: "1.0.5"
jni:
dependency: transitive
description:
name: jni
sha256: ca1efa31ef27a0b8ecbb4f8dcf3785e5ad1b8a54c64af9b72d287ae81f93a4f4
url: "https://pub.dev"
source: hosted
version: "1.0.1"
jni_flutter:
dependency: transitive
description:
name: jni_flutter
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
jni_util:
dependency: transitive
description:
name: jni_util
sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: "direct main"
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
url: "https://pub.dev"
source: hosted
version: "6.9.5"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
dependency: transitive
description:
name: lints
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
lottie:
dependency: "direct main"
description:
name: lottie
sha256: "58dda9eee3f1b1fb9d490b0161635cffe93022b3b8ca0687e3df33a53620ead2"
url: "https://pub.dev"
source: hosted
version: "3.5.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e
url: "https://pub.dev"
source: hosted
version: "9.5.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev"
source: hosted
version: "2.2.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
url: "https://pub.dev"
source: hosted
version: "2.1.6"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
percent_indicator:
dependency: "direct main"
description:
name: percent_indicator
sha256: "157d29133bbc6ecb11f923d36e7960a96a3f28837549a20b65e5135729f0f9fd"
url: "https://pub.dev"
source: hosted
version: "4.2.5"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pool:
dependency: transitive
description:
name: pool
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
posix:
dependency: transitive
description:
name: posix
sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
url: "https://pub.dev"
source: hosted
version: "6.5.2"
protobuf:
dependency: transitive
description:
name: protobuf
sha256: de9c9eb2c33f8e933a42932fe1dc504800ca45ebc3d673e6ed7f39754ee4053e
url: "https://pub.dev"
source: hosted
version: "4.2.0"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
pull_to_refresh_flutter3:
dependency: "direct main"
description:
name: pull_to_refresh_flutter3
sha256: "37a88d901cca9a46dbdd46523de8e7b35a3e58634a0e775b1a5904981f69b353"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
record_use:
dependency: transitive
description:
name: record_use
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
retrofit:
dependency: "direct main"
description:
name: retrofit
sha256: "0f629ed26b2c48c66fe54bd548313c6fdf7955be18bff37e08a46dd3f97f8eaf"
url: "https://pub.dev"
source: hosted
version: "4.9.2"
retrofit_generator:
dependency: "direct dev"
description:
name: retrofit_generator
sha256: "9abcf21acb95bf7040546eafff87f60cf0aee20b05101d71f99876fc4df1f522"
url: "https://pub.dev"
source: hosted
version: "9.7.0"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: "03a17170088c63aab6c54c44456f5ab78876a1ddb6032ffde1662ddab4959611"
url: "https://pub.dev"
source: hosted
version: "0.5.10"
riverpod_annotation:
dependency: "direct main"
description:
name: riverpod_annotation
sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8
url: "https://pub.dev"
source: hosted
version: "2.6.1"
riverpod_generator:
dependency: "direct dev"
description:
name: riverpod_generator
sha256: "44a0992d54473eb199ede00e2260bd3c262a86560e3c6f6374503d86d0580e36"
url: "https://pub.dev"
source: hosted
version: "2.6.5"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
url: "https://pub.dev"
source: hosted
version: "2.5.5"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7"
url: "https://pub.dev"
source: hosted
version: "2.4.27"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev"
source: hosted
version: "1.4.2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
shimmer:
dependency: "direct main"
description:
name: shimmer
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
smooth_page_indicator:
dependency: "direct main"
description:
name: smooth_page_indicator
sha256: b21ebb8bc39cf72d11c7cfd809162a48c3800668ced1c9da3aade13a32cf6c1c
url: "https://pub.dev"
source: hosted
version: "1.2.1"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
url: "https://pub.dev"
source: hosted
version: "1.3.7"
source_span:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.dev"
source: hosted
version: "1.10.2"
sqflite:
dependency: transitive
description:
name: sqflite
sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b
url: "https://pub.dev"
source: hosted
version: "2.4.3"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590"
url: "https://pub.dev"
source: hosted
version: "2.5.11"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f
url: "https://pub.dev"
source: hosted
version: "2.4.3+1"
sqflite_platform_interface:
dependency: transitive
description:
name: sqflite_platform_interface
sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50
url: "https://pub.dev"
source: hosted
version: "2.4.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
state_notifier:
dependency: transitive
description:
name: state_notifier
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
url: "https://pub.dev"
source: hosted
version: "1.0.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
url: "https://pub.dev"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "61894a1956de6b4fc1aefd0892e109514a1a706cbece3ac59decd90ff5a7a423"
url: "https://pub.dev"
source: hosted
version: "3.4.1+1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
timezone:
dependency: transitive
description:
name: timezone
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
url: "https://pub.dev"
source: hosted
version: "0.10.1"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
url: "https://pub.dev"
source: hosted
version: "6.3.32"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
url: "https://pub.dev"
source: hosted
version: "3.2.5"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
uuid:
dependency: transitive
description:
name: uuid
sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
url: "https://pub.dev"
source: hosted
version: "4.6.0"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
url: "https://pub.dev"
source: hosted
version: "1.1.13"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3"
url: "https://pub.dev"
source: hosted
version: "1.2.6"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
url: "https://pub.dev"
source: hosted
version: "15.2.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
win32:
dependency: transitive
description:
name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
url: "https://pub.dev"
source: hosted
version: "5.15.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.12.2 <4.0.0"
flutter: ">=3.44.0"
name: el_captain_client
description: El Captain Sports Management — Guardian & Participant Mobile App
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.12.2
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# State Management
flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
# Networking
dio: ^5.7.0
retrofit: ^4.4.1
json_annotation: ^4.9.0
# Local Storage
shared_preferences: ^2.3.3
flutter_secure_storage: ^9.2.2
# Navigation
go_router: ^14.6.2
# UI & Animations
flutter_animate: ^4.5.2
shimmer: ^3.0.0
cached_network_image: ^3.4.1
flutter_svg: ^2.0.16
lottie: ^3.3.1
smooth_page_indicator: ^1.2.0+3
# Firebase
firebase_core: ^3.8.1
firebase_messaging: ^15.1.6
# Utilities
intl: ^0.20.2
url_launcher: ^6.3.1
image_picker: ^1.1.2
pull_to_refresh_flutter3: ^2.0.2
flutter_local_notifications: ^18.0.1
percent_indicator: ^4.2.3
gap: ^3.0.1
# Icons
cupertino_icons: ^1.0.8
iconsax_flutter: ^1.0.0+1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
build_runner: ^2.4.13
retrofit_generator: ^9.1.5
json_serializable: ^6.9.0
riverpod_generator: ^2.6.2
flutter:
uses-material-design: true
assets:
- assets/branding/
- assets/animations/
- assets/icons/
fonts:
- family: Cairo
fonts:
- asset: assets/fonts/Cairo-Regular.ttf
weight: 400
- asset: assets/fonts/Cairo-Medium.ttf
weight: 500
- asset: assets/fonts/Cairo-SemiBold.ttf
weight: 600
- asset: assets/fonts/Cairo-Bold.ttf
weight: 700
- family: Inter
fonts:
- asset: assets/fonts/Inter-Regular.ttf
weight: 400
- asset: assets/fonts/Inter-Medium.ttf
weight: 500
- asset: assets/fonts/Inter-SemiBold.ttf
weight: 600
- asset: assets/fonts/Inter-Bold.ttf
weight: 700
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:el_captain_client/main.dart';
void main() {
testWidgets('App renders without crash', (WidgetTester tester) async {
await tester.pumpWidget(const ProviderScope(child: ElCaptainApp()));
await tester.pump();
expect(find.byType(ElCaptainApp), findsOneWidget);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment