├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── io │ │ │ │ └── flutter │ │ │ │ └── plugins │ │ │ │ └── GeneratedPluginRegistrant.java │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── emad │ │ │ │ └── beltaje │ │ │ │ └── getx_skeleton │ │ │ │ └── getx_skeleton │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── assets ├── fonts │ ├── Cairo-Medium.ttf │ ├── Cairo-Regular.ttf │ ├── Cairo-SemiBold.ttf │ ├── Poppins-Medium.ttf │ ├── Poppins-Regular.ttf │ └── Poppins-SemiBold.ttf ├── images │ ├── app_icon.png │ ├── person1.png │ ├── person2.png │ └── profile.png └── vectors │ ├── absent.svg │ ├── alarm.svg │ ├── calendar.svg │ ├── language.svg │ ├── moon.svg │ ├── person1.svg │ ├── profile.svg │ ├── sun.svg │ ├── tasks.svg │ └── vocation.svg ├── integration_test ├── awesome_notifications_helper_test.dart ├── baseclient_test.dart └── my_widget_animator_test.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 102.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 128.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 16.png │ │ │ ├── 167.png │ │ │ ├── 172.png │ │ │ ├── 180.png │ │ │ ├── 196.png │ │ │ ├── 20.png │ │ │ ├── 216.png │ │ │ ├── 256.png │ │ │ ├── 29.png │ │ │ ├── 32.png │ │ │ ├── 40.png │ │ │ ├── 48.png │ │ │ ├── 50.png │ │ │ ├── 512.png │ │ │ ├── 55.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 64.png │ │ │ ├── 66.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ ├── 88.png │ │ │ ├── 92.png │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── firebase_app_id_file.json ├── lib ├── app │ ├── components │ │ ├── api_error_widget.dart │ │ ├── custom_loading_overlay.dart │ │ ├── custom_snackbar.dart │ │ └── my_widgets_animator.dart │ ├── data │ │ ├── local │ │ │ ├── my_hive.dart │ │ │ └── my_shared_pref.dart │ │ └── models │ │ │ ├── user_model.dart │ │ │ └── user_model.g.dart │ ├── modules │ │ └── home │ │ │ ├── bindings │ │ │ └── home_binding.dart │ │ │ ├── controllers │ │ │ └── home_controller.dart │ │ │ └── views │ │ │ ├── home_view.dart │ │ │ └── widgets │ │ │ ├── data_grid.dart │ │ │ ├── employees_list.dart │ │ │ └── header.dart │ ├── routes │ │ ├── app_pages.dart │ │ └── app_routes.dart │ └── services │ │ ├── api_call_status.dart │ │ ├── api_exceptions.dart │ │ └── base_client.dart ├── config │ ├── theme │ │ ├── dark_theme_colors.dart │ │ ├── light_theme_colors.dart │ │ ├── my_fonts.dart │ │ ├── my_styles.dart │ │ ├── my_theme.dart │ │ └── theme_extensions │ │ │ ├── employee_list_item_theme_data.dart │ │ │ └── header_container_theme_data.dart │ └── translations │ │ ├── ar_AR │ │ └── ar_ar_translation.dart │ │ ├── en_US │ │ └── en_us_translation.dart │ │ ├── localization_service.dart │ │ └── strings_enum.dart ├── main.dart └── utils │ ├── awesome_notifications_helper.dart │ ├── constants.dart │ └── fcm_helper.dart ├── preview_images ├── fail_snackbar.jpg ├── fail_toast.jpg ├── github.png ├── success_snackbar.jpg └── success_toast.jpg ├── pubspec.lock ├── pubspec.yaml └── test ├── baseclient_test.dart ├── localization_service_test.dart ├── my_hive_test.dart └── sharedpref_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | lib/firebase_options.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5f105a6ca7a5ac7b8bc9b241f4c2d86f4188cf5c 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | #*.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | 55 | # Freeline 56 | freeline.py 57 | freeline/ 58 | freeline_project_description.json 59 | 60 | # fastlane 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | fastlane/readme.md 66 | 67 | 68 | ### https://raw.github.com/github/gitignore/80a8803b004013d17291196825a327b9e871f009/Dart.gitignore 69 | 70 | # See https://www.dartlang.org/guides/libraries/private-files 71 | 72 | # Files and directories created by pub 73 | .dart_tool/ 74 | .packages 75 | .pub/ 76 | build/ 77 | # If you're building an application, you may want to check-in your pubspec.lock 78 | pubspec.lock 79 | 80 | # Directory created by dartdoc 81 | # If you don't generate documentation locally you can remove this line. 82 | doc/api/ 83 | 84 | 85 | ### https://raw.github.com/github/gitignore/80a8803b004013d17291196825a327b9e871f009/Global/Linux.gitignore 86 | 87 | *~ 88 | 89 | # temporary files which can be created if a process still has a handle open of a deleted file 90 | .fuse_hidden* 91 | 92 | # KDE directory preferences 93 | .directory 94 | 95 | # Linux trash folder which might appear on any partition or disk 96 | .Trash-* 97 | 98 | # .nfs files are created when an open file is removed but is still being accessed 99 | .nfs* 100 | 101 | 102 | ### https://raw.github.com/github/gitignore/80a8803b004013d17291196825a327b9e871f009/Global/JetBrains.gitignore 103 | 104 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 105 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 106 | 107 | # User-specific stuff 108 | .idea/**/workspace.xml 109 | .idea/**/tasks.xml 110 | .idea/**/dictionaries 111 | .idea/**/shelf 112 | 113 | # Sensitive or high-churn files 114 | .idea/**/dataSources/ 115 | .idea/**/dataSources.ids 116 | .idea/**/dataSources.local.xml 117 | .idea/**/sqlDataSources.xml 118 | .idea/**/dynamic.xml 119 | .idea/**/uiDesigner.xml 120 | .idea/**/dbnavigator.xml 121 | 122 | # Gradle 123 | .idea/**/gradle.xml 124 | .idea/**/libraries 125 | 126 | # CMake 127 | cmake-build-debug/ 128 | cmake-build-release/ 129 | 130 | # Mongo Explorer plugin 131 | .idea/**/mongoSettings.xml 132 | 133 | # File-based project format 134 | *.iws 135 | 136 | # IntelliJ 137 | out/ 138 | 139 | # mpeltonen/sbt-idea plugin 140 | .idea_modules/ 141 | 142 | # JIRA plugin 143 | atlassian-ide-plugin.xml 144 | 145 | # Cursive Clojure plugin 146 | .idea/replstate.xml 147 | 148 | # Crashlytics plugin (for Android Studio and IntelliJ) 149 | com_crashlytics_export_strings.xml 150 | crashlytics.properties 151 | crashlytics-build.properties 152 | fabric.properties 153 | 154 | # Editor-based Rest Client 155 | .idea/httpRequests 156 | 157 | 158 | ### https://raw.github.com/github/gitignore/80a8803b004013d17291196825a327b9e871f009/Global/macOS.gitignore 159 | 160 | # General 161 | .DS_Store 162 | .AppleDouble 163 | .LSOverride 164 | 165 | # Icon must end with two \r 166 | Icon 167 | 168 | 169 | # Thumbnails 170 | ._* 171 | 172 | # Files that might appear in the root of a volume 173 | .DocumentRevisions-V100 174 | .fseventsd 175 | .Spotlight-V100 176 | .TemporaryItems 177 | .Trashes 178 | .VolumeIcon.icns 179 | .com.apple.timemachine.donotpresent 180 | 181 | # Directories potentially created on remote AFP share 182 | .AppleDB 183 | .AppleDesktop 184 | Network Trash Folder 185 | Temporary Items 186 | .apdisk 187 | 188 | 189 | ### https://raw.github.com/github/gitignore/80a8803b004013d17291196825a327b9e871f009/Global/Vim.gitignore 190 | 191 | # Swap 192 | [._]*.s[a-v][a-z] 193 | [._]*.sw[a-p] 194 | [._]s[a-v][a-z] 195 | [._]sw[a-p] 196 | 197 | # Session 198 | Session.vim 199 | 200 | # Temporary 201 | .netrwhist 202 | *~ 203 | # Auto-generated tag files 204 | tags 205 | # Persistent undo 206 | [._]*.un~ 207 | 208 | 209 | ### https://raw.github.com/github/gitignore/80a8803b004013d17291196825a327b9e871f009/Global/Xcode.gitignore 210 | 211 | # Xcode 212 | # 213 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 214 | 215 | ## User settings 216 | xcuserdata/ 217 | 218 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 219 | *.xcscmblueprint 220 | *.xccheckout 221 | 222 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 223 | build/ 224 | DerivedData/ 225 | *.moved-aside 226 | *.pbxuser 227 | !default.pbxuser 228 | *.mode1v3 229 | !default.mode1v3 230 | *.mode2v3 231 | !default.mode2v3 232 | *.perspectivev3 233 | !default.perspectivev3 234 | 235 | 236 | ### https://raw.github.com/github/gitignore/80a8803b004013d17291196825a327b9e871f009/Global/VisualStudioCode.gitignore 237 | 238 | .vscode/* 239 | !.vscode/settings.json 240 | !.vscode/tasks.json 241 | !.vscode/launch.json 242 | !.vscode/extensions.json 243 | 244 | 245 | ## Custom 246 | # Google API key and other resources 247 | GoogleService-Info.plist 248 | google-services.json 249 | # Plugins (already resolved through pubspec.yaml) 250 | .flutter-plugins -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 34 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.emad.beltaje.getx_skeleton.getx_skeleton" 47 | minSdkVersion 21 48 | targetSdkVersion 34 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import androidx.annotation.Keep; 4 | import androidx.annotation.NonNull; 5 | import io.flutter.Log; 6 | 7 | import io.flutter.embedding.engine.FlutterEngine; 8 | 9 | /** 10 | * Generated file. Do not edit. 11 | * This file is generated by the Flutter tool based on the 12 | * plugins that support the Android platform. 13 | */ 14 | @Keep 15 | public final class GeneratedPluginRegistrant { 16 | private static final String TAG = "GeneratedPluginRegistrant"; 17 | public static void registerWith(@NonNull FlutterEngine flutterEngine) { 18 | try { 19 | flutterEngine.getPlugins().add(new me.carda.awesome_notifications.AwesomeNotificationsPlugin()); 20 | } catch (Exception e) { 21 | Log.e(TAG, "Error registering plugin awesome_notifications, me.carda.awesome_notifications.AwesomeNotificationsPlugin", e); 22 | } 23 | try { 24 | flutterEngine.getPlugins().add(new io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin()); 25 | } catch (Exception e) { 26 | Log.e(TAG, "Error registering plugin firebase_core, io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin", e); 27 | } 28 | try { 29 | flutterEngine.getPlugins().add(new io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingPlugin()); 30 | } catch (Exception e) { 31 | Log.e(TAG, "Error registering plugin firebase_messaging, io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingPlugin", e); 32 | } 33 | try { 34 | flutterEngine.getPlugins().add(new dev.flutter.plugins.integration_test.IntegrationTestPlugin()); 35 | } catch (Exception e) { 36 | Log.e(TAG, "Error registering plugin integration_test, dev.flutter.plugins.integration_test.IntegrationTestPlugin", e); 37 | } 38 | try { 39 | flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin()); 40 | } catch (Exception e) { 41 | Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e); 42 | } 43 | try { 44 | flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin()); 45 | } catch (Exception e) { 46 | Log.e(TAG, "Error registering plugin shared_preferences_android, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/emad/beltaje/getx_skeleton/getx_skeleton/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.emad.beltaje.getx_skeleton.getx_skeleton 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.8.21' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.2.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/fonts/Cairo-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/fonts/Cairo-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Cairo-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/fonts/Cairo-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Cairo-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/fonts/Cairo-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/fonts/Poppins-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/fonts/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /assets/images/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/images/app_icon.png -------------------------------------------------------------------------------- /assets/images/person1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/images/person1.png -------------------------------------------------------------------------------- /assets/images/person2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/images/person2.png -------------------------------------------------------------------------------- /assets/images/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/assets/images/profile.png -------------------------------------------------------------------------------- /assets/vectors/absent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/vectors/alarm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/vectors/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/vectors/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/vectors/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /assets/vectors/profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/vectors/sun.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/vectors/tasks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/vectors/vocation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /integration_test/awesome_notifications_helper_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_notifications/awesome_notifications.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:getx_skeleton/utils/awesome_notifications_helper.dart'; 6 | import 'package:integration_test/integration_test.dart'; 7 | 8 | 9 | /// test awesome notifications helper class 10 | /// 1- check if notifications shows up 11 | /// 2- check if handlers work fine 12 | /// 3- check action handler (when you click on notification action buttons) 13 | /// at the end of test lower your mobile/emulator status bar to see the notifications 14 | /// they should be grouped in 2 groups (4 in general channel & 4 in chat channel) 15 | 16 | 17 | main() async { 18 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 19 | 20 | // initialize awesome notifications 21 | await AwesomeNotificationsHelper.init(); 22 | 23 | testWidgets('Test showing local notification using awesome notifications', (tester) async { 24 | // create controller for test 25 | TestController controller = Get.put(TestController()); 26 | 27 | // set mock controller for awesome notification (just for test) 28 | AwesomeNotificationsHelper.awesomeNotifications.setListeners( 29 | onActionReceivedMethod: MockNotificationController.onActionReceivedMethod, 30 | onNotificationCreatedMethod: MockNotificationController.onNotificationCreatedMethod, 31 | onNotificationDisplayedMethod: MockNotificationController.onNotificationDisplayedMethod, 32 | onDismissActionReceivedMethod: MockNotificationController.onDismissActionReceivedMethod 33 | ); 34 | 35 | // pump widget 36 | await tester.pumpWidget(const TestWidget()); 37 | 38 | // request permission to show notifications 39 | bool showNotificationsPermissionGranted = await AwesomeNotificationsHelper.awesomeNotifications.requestPermissionToSendNotifications(); 40 | 41 | // if user didn't give permission then we cant show notifications 42 | if(showNotificationsPermissionGranted == false) return; 43 | 44 | // pump widget when value of counter or action changed 45 | // this is only for integration test other wise ui should 46 | // be updated directly without needing for this code.. 47 | controller.notificationsCounter.stream.listen((event) async { 48 | await tester.pump(); 49 | }); 50 | controller.notificationAction.stream.listen((event) async { 51 | await tester.pump(); 52 | }); 53 | 54 | // find floating button 55 | Finder generalNotificationsFab = find.byKey(const ValueKey('general_notifications_fap')); 56 | Finder chatNotificationsFab = find.byKey(const ValueKey('chat_notifications_fap')); 57 | 58 | // press on fab button to show notifications 59 | await tester.tap(generalNotificationsFab); 60 | await Future.delayed(const Duration(seconds: 3)); 61 | await tester.tap(generalNotificationsFab); 62 | await Future.delayed(const Duration(seconds: 3)); 63 | await tester.tap(generalNotificationsFab); 64 | await Future.delayed(const Duration(seconds: 3)); 65 | await tester.tap(generalNotificationsFab); 66 | await Future.delayed(const Duration(seconds: 5)); 67 | 68 | // press on fab button to show notifications 69 | await tester.tap(chatNotificationsFab); 70 | await Future.delayed(const Duration(seconds: 3)); 71 | await tester.tap(chatNotificationsFab); 72 | await Future.delayed(const Duration(seconds: 3)); 73 | await tester.tap(chatNotificationsFab); 74 | await Future.delayed(const Duration(seconds: 3)); 75 | await tester.tap(chatNotificationsFab); 76 | await Future.delayed(const Duration(seconds: 5)); 77 | 78 | // we displayed 4 notifications 79 | expect(controller.notificationsCounter.value, 8); 80 | 81 | // wait before closing app/test 82 | await Future.delayed(const Duration(seconds: 30)); 83 | }); 84 | } 85 | 86 | 87 | class TestController extends GetxController { 88 | // notifications counter 89 | Rx notificationsCounter = 0.obs; 90 | // when user click on action button 91 | Rx notificationAction = ''.obs; 92 | 93 | incrementCounter() { 94 | notificationsCounter.value += 1; 95 | } 96 | 97 | decrementCounter() { 98 | notificationsCounter.value -= 1; 99 | } 100 | 101 | onNotificationActionClicked(String actionKey){ 102 | notificationAction.value = actionKey; 103 | } 104 | } 105 | 106 | class TestWidget extends GetWidget { 107 | const TestWidget({Key? key}) : super(key: key); 108 | 109 | @override 110 | Widget build(BuildContext context) { 111 | return GetMaterialApp( 112 | home: Scaffold( 113 | body: SafeArea( 114 | child: Center( 115 | child: Obx(() => Column( 116 | mainAxisSize: MainAxisSize.min, 117 | mainAxisAlignment: MainAxisAlignment.center, 118 | children: [ 119 | Text('Notifications Count => ${controller.notificationsCounter.value}'), 120 | Text('Notifications Action => ${controller.notificationAction.value}'), 121 | ], 122 | )), 123 | ), 124 | ), 125 | floatingActionButton: Row( 126 | children: [ 127 | FloatingActionButton( 128 | key: const ValueKey('general_notifications_fap'), 129 | child: const Icon(Icons.add,color: Colors.white,), 130 | backgroundColor: Colors.purple, 131 | onPressed: () async { 132 | await AwesomeNotificationsHelper.showNotification( 133 | title: 'test number ${controller.notificationsCounter.value}', 134 | body: 'test notification number ${controller.notificationsCounter.value}', 135 | id: controller.notificationsCounter.value, 136 | actionButtons: [ 137 | NotificationActionButton(key: 'submit', label: 'Submit'), 138 | NotificationActionButton(key: 'cancel', label: 'Cancel'), 139 | ] 140 | ); 141 | }, 142 | ), 143 | const SizedBox(width: 20,), 144 | FloatingActionButton( 145 | key: const ValueKey('chat_notifications_fap'), 146 | child: const Icon(Icons.add,color: Colors.white,), 147 | backgroundColor: Colors.blue, 148 | onPressed: () async { 149 | await AwesomeNotificationsHelper.showNotification( 150 | title: 'test number ${controller.notificationsCounter.value}', 151 | body: 'test notification number ${controller.notificationsCounter.value}', 152 | id: controller.notificationsCounter.value, 153 | channelKey: NotificationChannels.chatChannelKey, 154 | groupKey: NotificationChannels.chatGroupKey, 155 | actionButtons: [ 156 | NotificationActionButton(key: 'submit', label: 'Submit'), 157 | NotificationActionButton(key: 'cancel', label: 'Cancel'), 158 | ] 159 | ); 160 | }, 161 | ), 162 | ], 163 | ), 164 | ), 165 | ); 166 | } 167 | } 168 | 169 | class MockNotificationController { 170 | /// Use this method to detect when a new notification or a schedule is created 171 | @pragma("vm:entry-point") 172 | static Future onNotificationCreatedMethod(ReceivedNotification receivedNotification) async { 173 | Get.find().incrementCounter(); 174 | } 175 | 176 | /// Use this method to detect every time that a new notification is displayed 177 | @pragma("vm:entry-point") 178 | static Future onNotificationDisplayedMethod(ReceivedNotification receivedNotification) async { 179 | // Your code goes here 180 | } 181 | 182 | /// Use this method to detect if the user dismissed a notification 183 | @pragma("vm:entry-point") 184 | static Future onDismissActionReceivedMethod(ReceivedAction receivedAction) async { 185 | Get.find().decrementCounter(); 186 | } 187 | 188 | /// Use this method to detect when the user taps on a notification or action button 189 | @pragma("vm:entry-point") 190 | static Future onActionReceivedMethod(ReceivedAction receivedAction) async { 191 | Get.find().onNotificationActionClicked(receivedAction.buttonKeyPressed); 192 | } 193 | } 194 | 195 | -------------------------------------------------------------------------------- /integration_test/baseclient_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:getx_skeleton/app/services/base_client.dart'; 5 | import 'package:http_mock_adapter/http_mock_adapter.dart'; 6 | import 'package:integration_test/integration_test.dart'; 7 | 8 | 9 | /// this is widget test for BaseClient and the main point of it 10 | /// is to test if BaseClient shows error (Snackbar) automatically if you 11 | /// don't pass onError() callback 12 | 13 | 14 | void main() { 15 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 16 | 17 | // adapter to mock dio 18 | final dioAdapter = DioAdapter(dio: BaseClient.dio); 19 | 20 | // dummy url 21 | String url = 'https://www.facebook.com/emadbeltaje'; 22 | 23 | group('Error cases in BaseClient', () { 24 | testWidgets('Error case with error message from api', (tester) async { 25 | await tester.pumpWidget( 26 | const GetMaterialApp( 27 | home: Scaffold( 28 | body: Center(child: Text('Testing..')), 29 | ), 30 | ), 31 | ); 32 | 33 | // mock dio response 34 | String apiErrorMessage = 'Bad Request'; 35 | dioAdapter.onPost(url, (server) { 36 | server.reply(400, { 37 | 'error': apiErrorMessage, 38 | }); 39 | }); 40 | 41 | // perform api request 42 | await BaseClient.safeApiCall( 43 | url, 44 | RequestType.post, 45 | onSuccess: (res) {}, 46 | ); 47 | 48 | // update ui to show the snackbar 49 | await tester.pumpAndSettle(); 50 | 51 | // in unauthorized request we expect 2 things 52 | // 1- snackbar should be displayed 53 | // 2- snackbar text must be our error message from api 54 | expect(Get.isSnackbarOpen, true); 55 | expect(find.text(apiErrorMessage), findsOneWidget); 56 | 57 | // close snackbar 58 | await Future.delayed(const Duration(seconds: 2)); 59 | Get.closeAllSnackbars(); 60 | await tester.pumpAndSettle(); 61 | }); 62 | 63 | 64 | testWidgets('Error case with no message from api', (tester) async { 65 | await tester.pumpWidget( 66 | const GetMaterialApp( 67 | home: Scaffold( 68 | body: Center(child: Text('Testing..')), 69 | ), 70 | ), 71 | ); 72 | 73 | // mock dio response 74 | dioAdapter.onPost(url, (server) { 75 | server.reply(401, null); 76 | }); 77 | 78 | // perform api request 79 | await BaseClient.safeApiCall( 80 | url, 81 | RequestType.post, 82 | onSuccess: (res) {}, 83 | ); 84 | 85 | // update ui to show the snackbar 86 | await tester.pumpAndSettle(); 87 | 88 | // in error case with no message from api we expect 89 | // snackbar to be displayed 90 | // and the message will be from dio (so it will not be user friendly) 91 | // you can always change message from ApiException => toString() method 92 | expect(Get.isSnackbarOpen, true); 93 | 94 | // close snackbar 95 | await Future.delayed(const Duration(seconds: 2)); 96 | Get.closeAllSnackbars(); 97 | await tester.pumpAndSettle(); 98 | }); 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /integration_test/my_widget_animator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:getx_skeleton/app/components/my_widgets_animator.dart'; 5 | import 'package:getx_skeleton/app/services/api_call_status.dart'; 6 | 7 | 8 | /// widget animator test code to make sure the widget switch 9 | /// correctly between different widgets according to api call status 10 | 11 | 12 | class TestController extends GetxController { 13 | Rx apiCallStatus = ApiCallStatus.loading.obs; 14 | } 15 | 16 | class TestView extends StatelessWidget { 17 | const TestView({Key? key}) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return MaterialApp( 22 | home: Scaffold( 23 | body: Center( 24 | child: Obx( 25 | () => 26 | MyWidgetsAnimator( 27 | apiCallStatus: Get.find().apiCallStatus.value, 28 | animationDuration: const Duration(seconds: 2), 29 | loadingWidget: () => const Text('Loading',key: ValueKey('Loading'),), 30 | errorWidget: ()=> const Text('Error',key: ValueKey('Error'),), 31 | successWidget: () => const Text('Success',key: ValueKey('Success'),), 32 | emptyWidget: () => const Text('Empty',key: ValueKey('Empty'),), 33 | holdingWidget: () => const Text('Holding',key: ValueKey('Holding'),), 34 | ), 35 | ), 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | 42 | 43 | 44 | main() { 45 | testWidgets('test widget animator animating between widgets according to api call status', (tester) async { 46 | // initialize controller 47 | TestController controller = Get.put(TestController()); 48 | 49 | // animation controller (just a work around bcz animated switcher not working fine in integration test 'Flutter bug') 50 | final animationController = AnimationController(vsync: tester,duration: const Duration(seconds: 2)); 51 | 52 | // pump widget 53 | await tester.pumpWidget(const TestView()); 54 | 55 | // loading state (default) in the controller 56 | expect(find.text('Loading'), findsOneWidget); 57 | 58 | // error 59 | controller.apiCallStatus.value = ApiCallStatus.error; 60 | animationController.forward(); 61 | await tester.pumpAndSettle(); 62 | expect(find.text('Error'), findsOneWidget); 63 | 64 | // success 65 | controller.apiCallStatus.value = ApiCallStatus.success; 66 | animationController.forward(); 67 | await tester.pumpAndSettle(); 68 | expect(find.text('Success'), findsOneWidget); 69 | 70 | // holding 71 | controller.apiCallStatus.value = ApiCallStatus.holding; 72 | animationController.forward(); 73 | await tester.pumpAndSettle(); 74 | expect(find.text('Holding'), findsOneWidget); 75 | 76 | // empty 77 | controller.apiCallStatus.value = ApiCallStatus.empty; 78 | animationController.forward(); 79 | await tester.pumpAndSettle(); 80 | expect(find.text('Empty'), findsOneWidget); 81 | 82 | // wait before close test 83 | await Future.delayed(const Duration(seconds: 3)); 84 | animationController.dispose(); 85 | }); 86 | } -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | # target 'RunnerTests' do 36 | # inherit! :search_paths 37 | # end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | target.build_configurations.each do |config| 44 | xcconfig_path = config.base_configuration_reference.real_path 45 | xcconfig = File.read(xcconfig_path) 46 | xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR") 47 | File.open(xcconfig_path, "w") { |file| file << xcconfig_mod } 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - awesome_notifications (0.9.3): 3 | - Flutter 4 | - IosAwnCore (~> 0.9.3) 5 | - Firebase/CoreOnly (10.25.0): 6 | - FirebaseCore (= 10.25.0) 7 | - Firebase/Messaging (10.25.0): 8 | - Firebase/CoreOnly 9 | - FirebaseMessaging (~> 10.25.0) 10 | - firebase_core (2.31.0): 11 | - Firebase/CoreOnly (= 10.25.0) 12 | - Flutter 13 | - firebase_messaging (14.9.2): 14 | - Firebase/Messaging (= 10.25.0) 15 | - firebase_core 16 | - Flutter 17 | - FirebaseCore (10.25.0): 18 | - FirebaseCoreInternal (~> 10.0) 19 | - GoogleUtilities/Environment (~> 7.12) 20 | - GoogleUtilities/Logger (~> 7.12) 21 | - FirebaseCoreInternal (10.25.0): 22 | - "GoogleUtilities/NSData+zlib (~> 7.8)" 23 | - FirebaseInstallations (10.25.0): 24 | - FirebaseCore (~> 10.0) 25 | - GoogleUtilities/Environment (~> 7.8) 26 | - GoogleUtilities/UserDefaults (~> 7.8) 27 | - PromisesObjC (~> 2.1) 28 | - FirebaseMessaging (10.25.0): 29 | - FirebaseCore (~> 10.0) 30 | - FirebaseInstallations (~> 10.0) 31 | - GoogleDataTransport (~> 9.3) 32 | - GoogleUtilities/AppDelegateSwizzler (~> 7.8) 33 | - GoogleUtilities/Environment (~> 7.8) 34 | - GoogleUtilities/Reachability (~> 7.8) 35 | - GoogleUtilities/UserDefaults (~> 7.8) 36 | - nanopb (< 2.30911.0, >= 2.30908.0) 37 | - Flutter (1.0.0) 38 | - GoogleDataTransport (9.4.1): 39 | - GoogleUtilities/Environment (~> 7.7) 40 | - nanopb (< 2.30911.0, >= 2.30908.0) 41 | - PromisesObjC (< 3.0, >= 1.2) 42 | - GoogleUtilities/AppDelegateSwizzler (7.13.3): 43 | - GoogleUtilities/Environment 44 | - GoogleUtilities/Logger 45 | - GoogleUtilities/Network 46 | - GoogleUtilities/Privacy 47 | - GoogleUtilities/Environment (7.13.3): 48 | - GoogleUtilities/Privacy 49 | - PromisesObjC (< 3.0, >= 1.2) 50 | - GoogleUtilities/Logger (7.13.3): 51 | - GoogleUtilities/Environment 52 | - GoogleUtilities/Privacy 53 | - GoogleUtilities/Network (7.13.3): 54 | - GoogleUtilities/Logger 55 | - "GoogleUtilities/NSData+zlib" 56 | - GoogleUtilities/Privacy 57 | - GoogleUtilities/Reachability 58 | - "GoogleUtilities/NSData+zlib (7.13.3)": 59 | - GoogleUtilities/Privacy 60 | - GoogleUtilities/Privacy (7.13.3) 61 | - GoogleUtilities/Reachability (7.13.3): 62 | - GoogleUtilities/Logger 63 | - GoogleUtilities/Privacy 64 | - GoogleUtilities/UserDefaults (7.13.3): 65 | - GoogleUtilities/Logger 66 | - GoogleUtilities/Privacy 67 | - integration_test (0.0.1): 68 | - Flutter 69 | - IosAwnCore (0.9.3) 70 | - nanopb (2.30910.0): 71 | - nanopb/decode (= 2.30910.0) 72 | - nanopb/encode (= 2.30910.0) 73 | - nanopb/decode (2.30910.0) 74 | - nanopb/encode (2.30910.0) 75 | - path_provider_ios (0.0.1): 76 | - Flutter 77 | - PromisesObjC (2.4.0) 78 | - shared_preferences_foundation (0.0.1): 79 | - Flutter 80 | - FlutterMacOS 81 | 82 | DEPENDENCIES: 83 | - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`) 84 | - firebase_core (from `.symlinks/plugins/firebase_core/ios`) 85 | - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) 86 | - Flutter (from `Flutter`) 87 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 88 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) 89 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 90 | 91 | SPEC REPOS: 92 | trunk: 93 | - Firebase 94 | - FirebaseCore 95 | - FirebaseCoreInternal 96 | - FirebaseInstallations 97 | - FirebaseMessaging 98 | - GoogleDataTransport 99 | - GoogleUtilities 100 | - IosAwnCore 101 | - nanopb 102 | - PromisesObjC 103 | 104 | EXTERNAL SOURCES: 105 | awesome_notifications: 106 | :path: ".symlinks/plugins/awesome_notifications/ios" 107 | firebase_core: 108 | :path: ".symlinks/plugins/firebase_core/ios" 109 | firebase_messaging: 110 | :path: ".symlinks/plugins/firebase_messaging/ios" 111 | Flutter: 112 | :path: Flutter 113 | integration_test: 114 | :path: ".symlinks/plugins/integration_test/ios" 115 | path_provider_ios: 116 | :path: ".symlinks/plugins/path_provider_ios/ios" 117 | shared_preferences_foundation: 118 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 119 | 120 | SPEC CHECKSUMS: 121 | awesome_notifications: 66d28ab7174ca2823b04d275cb043e0a4a3eb9cf 122 | Firebase: 0312a2352584f782ea56f66d91606891d4607f06 123 | firebase_core: 0b39f4f424e02eecabb2356ddf331fa07b772af8 124 | firebase_messaging: 8999827b6efc9c3ab4b1f9dc246deaa7f13dbf88 125 | FirebaseCore: 7ec4d0484817f12c3373955bc87762d96842d483 126 | FirebaseCoreInternal: 910a81992c33715fec9263ca7381d59ab3a750b7 127 | FirebaseInstallations: 91950fe859846fff0fbd296180909dd273103b09 128 | FirebaseMessaging: 88950ba9485052891ebe26f6c43a52bb62248952 129 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 130 | GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a 131 | GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 132 | integration_test: 13825b8a9334a850581300559b8839134b124670 133 | IosAwnCore: b8601fbb37f7b3560f31b84ebf55a72f65812e05 134 | nanopb: 438bc412db1928dac798aa6fd75726007be04262 135 | path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 136 | PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 137 | shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 138 | 139 | PODFILE CHECKSUM: 721b743907f26f295e56eb7e85c461aecd1a2179 140 | 141 | COCOAPODS: 1.15.2 142 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import awesome_notifications 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | GeneratedPluginRegistrant.register(with: self) 12 | 13 | // This function registers the desired plugins to be used within a notification background action 14 | SwiftAwesomeNotificationsPlugin.setPluginRegistrantCallback { registry in 15 | SwiftAwesomeNotificationsPlugin.register( 16 | with: registry.registrar(forPlugin: "io.flutter.plugins.awesomenotifications.AwesomeNotificationsPlugin")!) 17 | } 18 | 19 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/102.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/102.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/172.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/196.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/216.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/48.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/55.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/66.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/88.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/92.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/92.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"102.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"41mm","scale":"2x","size":"45x45","expected-size":"102","role":"appLauncher"},{"idiom":"watch","filename":"92.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"41mm","scale":"2x","size":"46x46","expected-size":"92","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"idiom":"watch","filename":"66.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"45mm","scale":"2x","size":"33x33","expected-size":"66","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Getx Skeleton 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | getx_skeleton 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:119264282603:ios:5af876596b1069174bfbae", 5 | "FIREBASE_PROJECT_ID": "bagala-51be9", 6 | "GCM_SENDER_ID": "119264282603" 7 | } -------------------------------------------------------------------------------- /lib/app/components/api_error_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../../config/translations/strings_enum.dart'; 6 | 7 | class ApiErrorWidget extends StatelessWidget { 8 | const ApiErrorWidget({super.key, required this.message, required this.retryAction, this.padding}); 9 | 10 | final String message; 11 | final Function retryAction; 12 | final EdgeInsets? padding; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | padding: padding, 18 | child: Center( 19 | child: Column( 20 | crossAxisAlignment: CrossAxisAlignment.center, 21 | mainAxisAlignment: MainAxisAlignment.center, 22 | children: [ 23 | Text(message), 24 | 10.verticalSpace, 25 | SizedBox(width: double.infinity,child: ElevatedButton(onPressed: () => retryAction(), child: Text(Strings.retry.tr),)), 26 | ], 27 | ), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/app/components/custom_loading_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:logger/logger.dart'; 5 | 6 | import '../../config/translations/strings_enum.dart'; 7 | 8 | 9 | 10 | 11 | /// this method will show black overlay which look like dialog 12 | /// and it will have loading animation inside of it 13 | /// this will make sure user cant interact with ui until 14 | /// any (async) method is executing cuz it will wait for async function 15 | /// to end and then it will dismiss the overlay 16 | showLoadingOverLay({required Future Function() asyncFunction,String? msg,}) async 17 | { 18 | await Get.showOverlay(asyncFunction: () async { 19 | try{ 20 | await asyncFunction(); 21 | }catch(error){ 22 | Logger().e(error); 23 | Logger().e(StackTrace.current); 24 | } 25 | },loadingWidget: Center( 26 | child: _getLoadingIndicator(msg: msg), 27 | ),opacity: 0.7, 28 | opacityColor: Colors.black, 29 | ); 30 | } 31 | 32 | Widget _getLoadingIndicator({String? msg}){ 33 | return Container( 34 | padding: EdgeInsets.symmetric( 35 | horizontal: 20.w, 36 | vertical: 10.h, 37 | ), 38 | decoration: BoxDecoration( 39 | borderRadius: BorderRadius.circular(10.r), 40 | color: Colors.white, 41 | ), 42 | child: Column(mainAxisSize: MainAxisSize.min,children: [ 43 | Image.asset('assets/images/app_icon.png',height: 45.h,), 44 | SizedBox(width: 8.h,), 45 | Text(msg ?? Strings.loading.tr,style: Get.theme.textTheme.bodyLarge), 46 | ],), 47 | ); 48 | } -------------------------------------------------------------------------------- /lib/app/components/custom_snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class CustomSnackBar { 5 | static showCustomSnackBar({required String title, required String message,Duration? duration}) 6 | { 7 | Get.snackbar( 8 | title, 9 | message, 10 | duration: duration ?? const Duration(seconds: 3), 11 | margin: const EdgeInsets.only(top: 10,left: 10,right: 10), 12 | colorText: Colors.white, 13 | backgroundColor: Colors.green, 14 | icon: const Icon(Icons.check_circle, color: Colors.white,), 15 | ); 16 | } 17 | 18 | 19 | static showCustomErrorSnackBar({required String title, required String message,Color? color,Duration? duration}) 20 | { 21 | Get.snackbar( 22 | title, 23 | message, 24 | duration: duration ?? const Duration(seconds: 3), 25 | margin: const EdgeInsets.only(top: 10,left: 10,right: 10), 26 | colorText: Colors.white, 27 | backgroundColor: color ?? Colors.redAccent, 28 | icon: const Icon(Icons.error, color: Colors.white,), 29 | ); 30 | } 31 | 32 | 33 | 34 | static showCustomToast({String? title, required String message,Color? color,Duration? duration}){ 35 | Get.rawSnackbar( 36 | title: title, 37 | duration: duration ?? const Duration(seconds: 3), 38 | snackStyle: SnackStyle.GROUNDED, 39 | backgroundColor: color ?? Colors.green, 40 | onTap: (snack){ 41 | Get.closeAllSnackbars(); 42 | }, 43 | //overlayBlur: 0.8, 44 | message: message, 45 | ); 46 | } 47 | 48 | 49 | static showCustomErrorToast({String? title, required String message,Color? color,Duration? duration}){ 50 | Get.rawSnackbar( 51 | title: title, 52 | duration: duration ?? const Duration(seconds: 3), 53 | snackStyle: SnackStyle.GROUNDED, 54 | backgroundColor: color ?? Colors.redAccent, 55 | onTap: (snack){ 56 | Get.closeAllSnackbars(); 57 | }, 58 | //overlayBlur: 0.8, 59 | message: message, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/app/components/my_widgets_animator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | import '../services/api_call_status.dart'; 4 | 5 | // switch between different widgets with animation 6 | // depending on api call status 7 | class MyWidgetsAnimator extends StatelessWidget { 8 | final ApiCallStatus apiCallStatus; 9 | final Widget Function() loadingWidget; 10 | final Widget Function() successWidget; 11 | final Widget Function() errorWidget; 12 | final Widget Function()? emptyWidget; 13 | final Widget Function()? holdingWidget; 14 | final Widget Function()? refreshWidget; 15 | final Duration? animationDuration; 16 | final Widget Function(Widget, Animation)? transitionBuilder; 17 | // this will be used to not hide the success widget when refresh 18 | // if its true success widget will still be shown 19 | // if false refresh widget will be shown or empty box if passed (refreshWidget) is null 20 | final bool hideSuccessWidgetWhileRefreshing; 21 | 22 | 23 | const MyWidgetsAnimator( 24 | {Key? key, 25 | required this.apiCallStatus, 26 | required this.loadingWidget, 27 | required this.errorWidget, 28 | required this.successWidget, 29 | this.holdingWidget, 30 | this.emptyWidget, 31 | this.refreshWidget, 32 | this.animationDuration, 33 | this.transitionBuilder, 34 | this.hideSuccessWidgetWhileRefreshing = false, 35 | }) 36 | : super(key: key); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return AnimatedSwitcher( 41 | duration: animationDuration ?? const Duration(milliseconds: 300), 42 | child: switch(apiCallStatus){ 43 | (ApiCallStatus.success) => successWidget, 44 | (ApiCallStatus.error) => errorWidget, 45 | (ApiCallStatus.holding) => holdingWidget ?? () { return const SizedBox();}, 46 | (ApiCallStatus.loading) => loadingWidget, 47 | (ApiCallStatus.empty) => emptyWidget ?? (){return const SizedBox();}, 48 | (ApiCallStatus.refresh) => refreshWidget ?? (hideSuccessWidgetWhileRefreshing ? successWidget : (){return const SizedBox();}), 49 | (ApiCallStatus.cache) => successWidget, 50 | }(), 51 | transitionBuilder: transitionBuilder ?? AnimatedSwitcher.defaultTransitionBuilder 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/app/data/local/my_hive.dart: -------------------------------------------------------------------------------- 1 | import '../models/user_model.dart'; 2 | import 'package:hive_flutter/hive_flutter.dart'; 3 | 4 | class MyHive { 5 | // prevent making instance 6 | MyHive._(); 7 | 8 | // hive box to store user data 9 | static late Box _userBox; 10 | // box name its like table name 11 | static const String _userBoxName = 'user'; 12 | // store current user as (key => value) 13 | static const String _currentUserKey = 'local_user'; 14 | 15 | /// initialize local db (HIVE) 16 | /// pass testPath only if you are testing hive 17 | static Future init({Function(HiveInterface)? registerAdapters,String? testPath}) async { 18 | if(testPath != null) { 19 | Hive.init(testPath); 20 | }else { 21 | await Hive.initFlutter(); 22 | } 23 | await registerAdapters?.call(Hive); 24 | await initUserBox(); 25 | } 26 | 27 | /// initialize user box 28 | static Future initUserBox() async { 29 | _userBox = await Hive.openBox(_userBoxName); 30 | } 31 | 32 | /// save user to database 33 | static Future saveUserToHive(UserModel user) async { 34 | try { 35 | await _userBox.put(_currentUserKey, user); 36 | return true; 37 | } catch (error) { 38 | return false; 39 | } 40 | } 41 | 42 | /// get current logged user 43 | static UserModel? getCurrentUser() { 44 | try { 45 | return _userBox.get(_currentUserKey); 46 | } catch (error) { 47 | return null; 48 | } 49 | } 50 | 51 | /// delete the current user 52 | static Future deleteCurrentUser() async { 53 | try { 54 | await _userBox.delete(_currentUserKey); 55 | return true; 56 | } catch (error) { 57 | return false; 58 | } 59 | } 60 | 61 | 62 | // setter for _userBox (only using it for testing) 63 | set userBox(Box box) { 64 | _userBox = box; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/app/data/local/my_shared_pref.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | import '../../../config/translations/localization_service.dart'; 5 | 6 | class MySharedPref { 7 | // prevent making instance 8 | MySharedPref._(); 9 | 10 | // get storage 11 | static late SharedPreferences _sharedPreferences; 12 | 13 | // STORING KEYS 14 | static const String _fcmTokenKey = 'fcm_token'; 15 | static const String _currentLocalKey = 'current_local'; 16 | static const String _lightThemeKey = 'is_theme_light'; 17 | 18 | /// init get storage services 19 | static Future init() async { 20 | _sharedPreferences = await SharedPreferences.getInstance(); 21 | } 22 | 23 | static setStorage(SharedPreferences sharedPreferences) { 24 | _sharedPreferences = sharedPreferences; 25 | } 26 | 27 | /// set theme current type as light theme 28 | static Future setThemeIsLight(bool lightTheme) => 29 | _sharedPreferences.setBool(_lightThemeKey, lightTheme); 30 | 31 | /// get if the current theme type is light 32 | static bool getThemeIsLight() => 33 | _sharedPreferences.getBool(_lightThemeKey) ?? true; // todo set the default theme (true for light, false for dark) 34 | 35 | /// save current locale 36 | static Future setCurrentLanguage(String languageCode) => 37 | _sharedPreferences.setString(_currentLocalKey, languageCode); 38 | 39 | /// get current locale 40 | static Locale getCurrentLocal(){ 41 | String? langCode = _sharedPreferences.getString(_currentLocalKey); 42 | // default language is english 43 | if(langCode == null){ 44 | return LocalizationService.defaultLanguage; 45 | } 46 | return LocalizationService.supportedLanguages[langCode]!; 47 | } 48 | 49 | /// save generated fcm token 50 | static Future setFcmToken(String token) => 51 | _sharedPreferences.setString(_fcmTokenKey, token); 52 | 53 | /// get authorization token 54 | static String? getFcmToken() => 55 | _sharedPreferences.getString(_fcmTokenKey); 56 | 57 | /// clear all data from shared pref 58 | static Future clear() async => await _sharedPreferences.clear(); 59 | 60 | } -------------------------------------------------------------------------------- /lib/app/data/models/user_model.dart: -------------------------------------------------------------------------------- 1 | // note you have to hardcode this part 2 | // for example => (part 'classname.g.dart'); 3 | // class name must be in lower case 4 | import 'package:hive/hive.dart'; 5 | 6 | // this line must be written by you 7 | // for example => ( part 'filename.g.dart'; ) 8 | // FILENAME not class name and also all in lower case 9 | part 'user_model.g.dart'; 10 | 11 | @HiveType(typeId: 1) // id must be unique 12 | class UserModel { 13 | @HiveField(0) 14 | late final String username; 15 | @HiveField(1) 16 | late final int age; 17 | @HiveField(2) 18 | late final String phoneNumber; 19 | 20 | // you must provide empty constructor 21 | // so hive can generate(serializable) object 22 | // so you u can store this object in local db (hive) 23 | UserModel(); 24 | 25 | UserModel.fromData({required this.age, required this.phoneNumber,required this.username}); 26 | 27 | @override 28 | String toString(){ 29 | return 'Username => $username\nAge => $age\nPhone number => $phoneNumber'; 30 | } 31 | } -------------------------------------------------------------------------------- /lib/app/data/models/user_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_model.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class UserModelAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 1; 12 | 13 | @override 14 | UserModel read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return UserModel() 20 | ..username = fields[0] as String 21 | ..age = fields[1] as int 22 | ..phoneNumber = fields[2] as String; 23 | } 24 | 25 | @override 26 | void write(BinaryWriter writer, UserModel obj) { 27 | writer 28 | ..writeByte(3) 29 | ..writeByte(0) 30 | ..write(obj.username) 31 | ..writeByte(1) 32 | ..write(obj.age) 33 | ..writeByte(2) 34 | ..write(obj.phoneNumber); 35 | } 36 | 37 | @override 38 | int get hashCode => typeId.hashCode; 39 | 40 | @override 41 | bool operator ==(Object other) => 42 | identical(this, other) || 43 | other is UserModelAdapter && 44 | runtimeType == other.runtimeType && 45 | typeId == other.typeId; 46 | } 47 | -------------------------------------------------------------------------------- /lib/app/modules/home/bindings/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../controllers/home_controller.dart'; 4 | 5 | class HomeBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => HomeController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/home/controllers/home_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../../../utils/constants.dart'; 4 | import '../../../services/api_call_status.dart'; 5 | import '../../../services/base_client.dart'; 6 | 7 | class HomeController extends GetxController { 8 | // hold data coming from api 9 | List? data; 10 | // api call status 11 | ApiCallStatus apiCallStatus = ApiCallStatus.holding; 12 | 13 | // getting data from api 14 | getData() async { 15 | // *) perform api call 16 | await BaseClient.safeApiCall( 17 | Constants.todosApiUrl, // url 18 | RequestType.get, // request type (get,post,delete,put) 19 | onLoading: () { 20 | // *) indicate loading state 21 | apiCallStatus = ApiCallStatus.loading; 22 | update(); 23 | }, 24 | onSuccess: (response){ // api done successfully 25 | data = List.from(response.data); 26 | // *) indicate success state 27 | apiCallStatus = ApiCallStatus.success; 28 | update(); 29 | }, 30 | // if you don't pass this method base client 31 | // will automaticly handle error and show message to user 32 | onError: (error){ 33 | // show error message to user 34 | BaseClient.handleApiError(error); 35 | // *) indicate error status 36 | apiCallStatus = ApiCallStatus.error; 37 | update(); 38 | }, 39 | ); 40 | } 41 | 42 | @override 43 | void onInit() { 44 | getData(); 45 | super.onInit(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/home_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:flutter_svg/flutter_svg.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | import '../../../../config/translations/strings_enum.dart'; 8 | import '../../../components/api_error_widget.dart'; 9 | import '../../../components/my_widgets_animator.dart'; 10 | import '../controllers/home_controller.dart'; 11 | import 'widgets/data_grid.dart'; 12 | import 'widgets/employees_list.dart'; 13 | import 'widgets/header.dart'; 14 | 15 | class HomeView extends GetView { 16 | const HomeView({Key? key}) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | body: Column( 22 | children: [ 23 | // ----------------------- Header ----------------------- // 24 | const Header(), 25 | 26 | // ----------------------- Content ----------------------- // 27 | GetBuilder( 28 | builder: (_) { 29 | return Expanded( 30 | child: MyWidgetsAnimator( 31 | apiCallStatus: controller.apiCallStatus, 32 | loadingWidget: () => const Center(child: CupertinoActivityIndicator(),), 33 | errorWidget: () => ApiErrorWidget( 34 | message: Strings.internetError.tr, 35 | retryAction: () => controller.getData(), 36 | padding: EdgeInsets.symmetric(horizontal: 20.w), 37 | ), 38 | successWidget: () => SingleChildScrollView( 39 | child: Column( 40 | children: [ 41 | 20.verticalSpace, 42 | // ----------------------- Attendance List Tile ----------------------- // 43 | Padding( 44 | padding: EdgeInsets.symmetric(horizontal: 20.w), 45 | child: ListTile( 46 | title: Text(Strings.attendanceRegistration.tr), 47 | subtitle: Text(Strings.time.tr), 48 | trailing: const Icon(Icons.arrow_forward), 49 | leading: Container( 50 | height: 47.h, 51 | width: 47.h, 52 | decoration: BoxDecoration( 53 | color: Theme.of(context).primaryColor, 54 | borderRadius: BorderRadius.circular(8), 55 | ), 56 | child: SvgPicture.asset( 57 | 'assets/vectors/profile.svg', 58 | fit: BoxFit.none, 59 | color: Colors.white, 60 | height: 19.h, 61 | width: 19.h, 62 | ), 63 | ), 64 | ), 65 | ), 66 | 20.verticalSpace, 67 | 68 | // ----------------------- Employee details cards ----------------------- // 69 | Padding( 70 | padding: EdgeInsets.symmetric(horizontal: 20.w), 71 | child: DataGrid(), 72 | ), 73 | 20.verticalSpace, 74 | 75 | // ----------------------- Employees List ----------------------- // 76 | EmployeesList(), 77 | ], 78 | ), 79 | ), 80 | ), 81 | ); 82 | } 83 | ), 84 | ], 85 | ), 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/widgets/data_grid.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | import '../../../../../config/translations/strings_enum.dart'; 7 | 8 | // mock model 9 | class DataGridModelMock { 10 | final String title; 11 | final String subtitle; 12 | final String iconPath; 13 | final Color backgroundColor; 14 | final Color iconBackgroundColor; 15 | 16 | DataGridModelMock({ 17 | required this.title, 18 | required this.subtitle, 19 | required this.iconPath, 20 | required this.backgroundColor, 21 | required this.iconBackgroundColor, 22 | }); 23 | } 24 | 25 | class DataGrid extends StatelessWidget { 26 | DataGrid({super.key}); 27 | 28 | final List data = [ 29 | DataGridModelMock( 30 | title: Strings.vocation.tr, 31 | subtitle: '10 - 20 ${Strings.vocation.tr}', 32 | iconPath: 'assets/vectors/vocation.svg', 33 | backgroundColor: const Color(0xFFEFF5FB), 34 | iconBackgroundColor: const Color(0xFF83A0EC), 35 | ), 36 | DataGridModelMock( 37 | title: Strings.remainingTasks.tr, 38 | subtitle: '5 - 10 ${Strings.tasks.tr}', 39 | iconPath: 'assets/vectors/tasks.svg', 40 | backgroundColor: const Color(0xFFEEF9FF), 41 | iconBackgroundColor: const Color(0xFF92D5F6), 42 | ), 43 | DataGridModelMock( 44 | title: Strings.daysOfDelays.tr, 45 | subtitle: '10 - 20 ${Strings.days.tr}', 46 | iconPath: 'assets/vectors/alarm.svg', 47 | backgroundColor: const Color(0xFFF4F0FC), 48 | iconBackgroundColor: const Color(0xFFAB99D9), 49 | ), 50 | DataGridModelMock( 51 | title: Strings.absentDays.tr, 52 | subtitle: '10 - 20 ${Strings.days.tr}', 53 | iconPath: 'assets/vectors/absent.svg', 54 | backgroundColor: const Color(0xFFFEF0EF), 55 | iconBackgroundColor: const Color(0xFFF9928A), 56 | ), 57 | ]; 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | var theme = Theme.of(context); 62 | 63 | return GridView.builder( 64 | padding: EdgeInsets.zero, 65 | physics: const NeverScrollableScrollPhysics(), 66 | itemCount: data.length, 67 | shrinkWrap: true, 68 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 69 | crossAxisCount: 2, 70 | crossAxisSpacing: 11.w, 71 | mainAxisSpacing: 10.h, 72 | mainAxisExtent: 120.h, 73 | ), 74 | itemBuilder: (ctx, index) { 75 | var gridData = data[index]; 76 | return Container( 77 | padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h), 78 | decoration: BoxDecoration( 79 | color: gridData.backgroundColor, 80 | borderRadius: BorderRadius.circular(8), 81 | ), 82 | child: Column( 83 | crossAxisAlignment: CrossAxisAlignment.start, 84 | children: [ 85 | Container( 86 | height: 37.h, 87 | width: 37.h, 88 | decoration: BoxDecoration( 89 | color: gridData.iconBackgroundColor, 90 | borderRadius: BorderRadius.circular(8), 91 | ), 92 | child: SvgPicture.asset( 93 | gridData.iconPath, 94 | height: 19.h, 95 | fit: BoxFit.none, 96 | ), 97 | ), 98 | 8.verticalSpace, 99 | Text(gridData.title,style: theme.textTheme.bodySmall,), 100 | 7.verticalSpace, 101 | Text(gridData.subtitle,style: theme.textTheme.bodyMedium?.copyWith(color: const Color(0xFF4B4C4D),)), 102 | ], 103 | ), 104 | ); 105 | }, 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/widgets/employees_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:flutter_svg/flutter_svg.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | import '../../../../../config/theme/theme_extensions/employee_list_item_theme_data.dart'; 7 | import '../../../../../config/translations/strings_enum.dart'; 8 | 9 | class EmployeeMockModel { 10 | final String name; 11 | final String date; 12 | final String location; 13 | final Color backgroundColor; 14 | final String imagePath; 15 | 16 | EmployeeMockModel(this.name, this.date, this.location,this.backgroundColor,this.imagePath); 17 | } 18 | 19 | class EmployeesList extends StatelessWidget { 20 | EmployeesList({super.key}); 21 | 22 | List employees = [ 23 | EmployeeMockModel( 24 | Strings.name.tr, 25 | '4 july 2023', 26 | Strings.gaza.tr, 27 | const Color(0xFFFFE2C2), 28 | 'assets/images/person1.png' 29 | ), 30 | EmployeeMockModel( 31 | Strings.abdQader.tr, 32 | '16 july 2023', 33 | Strings.gaza.tr, 34 | const Color(0xFFD9839F), 35 | 'assets/images/person2.png' 36 | ), 37 | EmployeeMockModel( 38 | Strings.loai.tr, 39 | '13 mar 2023', 40 | Strings.gaza.tr, 41 | const Color(0xFFFFE2C2), 42 | 'assets/images/person1.png' 43 | ), 44 | ]; 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | var theme = Theme.of(context); 49 | var employeeItemTheme = theme.extension(); 50 | return Column( 51 | mainAxisSize: MainAxisSize.min, 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | children: [ 54 | Padding( 55 | padding: EdgeInsets.symmetric(horizontal: 20.w), 56 | child: Row( 57 | crossAxisAlignment: CrossAxisAlignment.center, 58 | children: [ 59 | Text( 60 | Strings.vacationingEmployees.tr, 61 | style: theme.textTheme.displaySmall, 62 | ), 63 | const Spacer(), 64 | Text( 65 | Strings.viewAll.tr, 66 | style: theme.textTheme.bodySmall?.copyWith(fontSize: 12.sp), 67 | ), 68 | ], 69 | ), 70 | ), 71 | 20.verticalSpace, 72 | ListView.builder( 73 | padding: EdgeInsets.zero, 74 | shrinkWrap: true, 75 | physics: const NeverScrollableScrollPhysics(), 76 | itemCount: employees.length, 77 | itemBuilder: (ctx, index) { 78 | return Container( 79 | margin: EdgeInsets.zero, 80 | padding: EdgeInsets.symmetric(horizontal: 20.h,vertical: 13.h), 81 | // border only from top and bottom 82 | decoration: BoxDecoration( 83 | color: employeeItemTheme?.backgroundColor, 84 | border: Border( 85 | // bottom: BorderSide( 86 | // color: theme.dividerColor 87 | // ), 88 | top: BorderSide( 89 | color: Get.isDarkMode ? const Color(0xFF414141) : const Color(0xFFF6F6F6), 90 | ), 91 | ) 92 | ), 93 | child: Row( 94 | children: [ 95 | Container( 96 | height: 65.h, 97 | width: 65.h, 98 | decoration: BoxDecoration( 99 | color: employees[index].backgroundColor, 100 | borderRadius: BorderRadius.circular(8), 101 | ), 102 | child: Image.asset(employees[index].imagePath), 103 | ), 104 | 17.horizontalSpace, 105 | Column( 106 | crossAxisAlignment: CrossAxisAlignment.start, 107 | children: [ 108 | Text(employees[index].name,style: employeeItemTheme?.nameTextStyle,), 109 | 4.verticalSpace, 110 | Row( 111 | children: [ 112 | SvgPicture.asset('assets/vectors/calendar.svg',color: employeeItemTheme?.iconTheme?.color,), 113 | 4.horizontalSpace, 114 | Text(employees[index].date,style: employeeItemTheme?.subtitleTextStyle,), 115 | ], 116 | ), 117 | 6.verticalSpace, 118 | Row( 119 | children: [ 120 | SvgPicture.asset('assets/vectors/vocation.svg',color: employeeItemTheme?.iconTheme?.color,height: 15.h,), 121 | 4.horizontalSpace, 122 | Text(employees[index].location,style: employeeItemTheme?.subtitleTextStyle,), 123 | ], 124 | ), 125 | ], 126 | ) 127 | ], 128 | ), 129 | ); 130 | }), 131 | ], 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/widgets/header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:getx_skeleton/config/theme/my_theme.dart'; 6 | import 'package:getx_skeleton/config/translations/localization_service.dart'; 7 | 8 | import '../../../../../config/theme/theme_extensions/header_container_theme_data.dart'; 9 | import '../../../../../config/translations/strings_enum.dart'; 10 | 11 | class Header extends StatelessWidget { 12 | const Header({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final theme = Theme.of(context); 17 | return Container( 18 | height: 110.h, 19 | width: double.infinity, 20 | decoration: BoxDecoration( 21 | color: theme.primaryColor, 22 | ), 23 | child: Stack( 24 | fit: StackFit.expand, 25 | children: [ 26 | //----------------white circles decor----------------// 27 | Positioned( 28 | right: 0, 29 | top: -125.h, 30 | child: CircleAvatar( 31 | backgroundColor: Colors.white.withOpacity(0.05), 32 | radius: 111, 33 | ), 34 | ), 35 | Positioned( 36 | right: -7.w, 37 | top: -160.h, 38 | child: CircleAvatar( 39 | backgroundColor: Colors.white.withOpacity(0.05), 40 | radius: 111, 41 | ), 42 | ), 43 | Positioned( 44 | right: -21.w, 45 | top: -195.h, 46 | child: CircleAvatar( 47 | backgroundColor: Colors.white.withOpacity(0.05), 48 | radius: 111, 49 | ), 50 | ), 51 | 52 | //----------------Data row----------------// 53 | Positioned( 54 | bottom: 10, 55 | right: 16.w, 56 | left: 16.w, 57 | child: Row( 58 | crossAxisAlignment: CrossAxisAlignment.center, 59 | children: [ 60 | Container( 61 | height: 39.h, 62 | width: 39.h, 63 | decoration: BoxDecoration( 64 | color: const Color(0xFFFFE2C2), 65 | borderRadius: BorderRadius.circular(8.r), 66 | border: Border.all( 67 | color: Colors.white, 68 | width: 1 69 | ) 70 | ), 71 | child: Image.asset('assets/images/person1.png',height: double.infinity,), 72 | ), 73 | 9.horizontalSpace, 74 | Column( 75 | crossAxisAlignment: CrossAxisAlignment.start, 76 | mainAxisSize: MainAxisSize.min, 77 | children: [ 78 | Text( 79 | '${Strings.goodMorning.tr},🌞', 80 | style: theme.textTheme.bodyMedium?.copyWith( 81 | color: Colors.white, 82 | ), 83 | ), 84 | Text( 85 | Strings.name.tr, 86 | style: theme.textTheme.bodyMedium?.copyWith( 87 | color: Colors.white, 88 | fontSize: 12.sp, 89 | ), 90 | ), 91 | ], 92 | ), 93 | const Spacer(), 94 | 95 | //----------------Theme Button----------------// 96 | InkWell( 97 | onTap: () => MyTheme.changeTheme(), 98 | child: Ink( 99 | child: Container( 100 | height: 39.h, 101 | width: 39.h, 102 | decoration: theme.extension()?.decoration, 103 | child: SvgPicture.asset( 104 | Get.isDarkMode ? 'assets/vectors/moon.svg' : 'assets/vectors/sun.svg', 105 | fit: BoxFit.none, 106 | color: Colors.white, 107 | height: 10, 108 | width: 10, 109 | ), 110 | ), 111 | ), 112 | ), 113 | 114 | 10.horizontalSpace, 115 | 116 | //----------------Language Button----------------// 117 | InkWell( 118 | onTap: () => LocalizationService.updateLanguage( 119 | LocalizationService.getCurrentLocal().languageCode == 'ar' ? 'en' : 'ar', 120 | ), 121 | child: Ink( 122 | child: Container( 123 | height: 39.h, 124 | width: 39.h, 125 | decoration: theme.extension()?.decoration, 126 | child: SvgPicture.asset( 127 | 'assets/vectors/language.svg', 128 | fit: BoxFit.none, 129 | color: Colors.white, 130 | height: 10, 131 | width: 10, 132 | ), 133 | ), 134 | ), 135 | ), 136 | ], 137 | ), 138 | ) 139 | ], 140 | ), 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/app/routes/app_pages.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../modules/home/bindings/home_binding.dart'; 4 | import '../modules/home/views/home_view.dart'; 5 | 6 | part 'app_routes.dart'; 7 | 8 | class AppPages { 9 | AppPages._(); 10 | 11 | static const INITIAL = Routes.HOME; 12 | 13 | static final routes = [ 14 | GetPage( 15 | name: _Paths.HOME, 16 | page: () => const HomeView(), 17 | binding: HomeBinding(), 18 | ), 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /lib/app/routes/app_routes.dart: -------------------------------------------------------------------------------- 1 | part of 'app_pages.dart'; 2 | // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart 3 | 4 | abstract class Routes { 5 | Routes._(); 6 | static const HOME = _Paths.HOME; 7 | } 8 | 9 | abstract class _Paths { 10 | _Paths._(); 11 | static const HOME = '/home'; 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/services/api_call_status.dart: -------------------------------------------------------------------------------- 1 | enum ApiCallStatus { 2 | loading, 3 | success, 4 | error, 5 | empty, 6 | holding, 7 | cache, 8 | refresh, 9 | } -------------------------------------------------------------------------------- /lib/app/services/api_exceptions.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class ApiException implements Exception { 4 | final String url; 5 | final String message; 6 | final int? statusCode; 7 | final Response? response; 8 | 9 | ApiException({ 10 | required this.url, 11 | required this.message, 12 | this.response, 13 | this.statusCode, 14 | }); 15 | 16 | /// IMPORTANT NOTE 17 | /// here you can take advantage of toString() method to display the error for user 18 | /// lets make an example 19 | /// so in onError method when you make api you can just user apiExceptionInstance.toString() to get the error message from api 20 | @override 21 | toString() { 22 | String result = ''; 23 | 24 | // TODO add error message field which is coming from api for you (For ex: response.data['error']['message'] 25 | result += response?.data?['error'] ?? ''; 26 | 27 | if(result.isEmpty){ 28 | result += message; // message is the (dio error message) so usualy its not user friendly 29 | } 30 | 31 | return result; 32 | } 33 | } -------------------------------------------------------------------------------- /lib/app/services/base_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:get/get_utils/get_utils.dart'; 6 | import 'package:getx_skeleton/app/data/local/my_shared_pref.dart'; 7 | import 'package:logger/logger.dart'; 8 | import 'package:pretty_dio_logger/pretty_dio_logger.dart'; 9 | 10 | import '../../config/translations/strings_enum.dart'; 11 | import '../components/custom_snackbar.dart'; 12 | import 'api_exceptions.dart'; 13 | 14 | enum RequestType { 15 | get, 16 | post, 17 | put, 18 | delete, 19 | } 20 | 21 | class BaseClient { 22 | static final Dio _dio = Dio( 23 | BaseOptions( 24 | headers: { 25 | 'Content-Type' : 'application/json', 26 | 'Accept' : 'application/json' 27 | } 28 | ) 29 | ) 30 | ..interceptors.add(PrettyDioLogger( 31 | requestHeader: true, 32 | requestBody: true, 33 | responseBody: true, 34 | responseHeader: false, 35 | error: true, 36 | compact: true, 37 | maxWidth: 90, 38 | )); 39 | 40 | // request timeout (default 10 seconds) 41 | static const int _timeoutInSeconds = 10; 42 | 43 | /// dio getter (used for testing) 44 | static get dio => _dio; 45 | 46 | /// perform safe api request 47 | static safeApiCall( 48 | String url, 49 | RequestType requestType, { 50 | Map? headers, 51 | Map? queryParameters, 52 | required Function(Response response) onSuccess, 53 | Function(ApiException)? onError, 54 | Function(int value, int progress)? onReceiveProgress, 55 | Function(int total, int progress)? 56 | onSendProgress, // while sending (uploading) progress 57 | Function? onLoading, 58 | dynamic data, 59 | }) async { 60 | try { 61 | 62 | 63 | // 1) indicate loading state 64 | await onLoading?.call(); 65 | // 2) try to perform http request 66 | late Response response; 67 | if (requestType == RequestType.get) { 68 | response = await _dio.get( 69 | url, 70 | onReceiveProgress: onReceiveProgress, 71 | queryParameters: queryParameters, 72 | options: Options( 73 | headers: headers, 74 | ), 75 | ); 76 | } else if (requestType == RequestType.post) { 77 | response = await _dio.post( 78 | url, 79 | data: data, 80 | onReceiveProgress: onReceiveProgress, 81 | onSendProgress: onSendProgress, 82 | queryParameters: queryParameters, 83 | options: Options(headers: headers), 84 | ); 85 | } else if (requestType == RequestType.put) { 86 | response = await _dio.put( 87 | url, 88 | data: data, 89 | onReceiveProgress: onReceiveProgress, 90 | onSendProgress: onSendProgress, 91 | queryParameters: queryParameters, 92 | options: Options(headers: headers), 93 | ); 94 | } else { 95 | response = await _dio.delete( 96 | url, 97 | data: data, 98 | queryParameters: queryParameters, 99 | options: Options(headers: headers), 100 | ); 101 | } 102 | // 3) return response (api done successfully) 103 | await onSuccess(response); 104 | } on DioException catch (error) { 105 | // dio error (api reach the server but not performed successfully 106 | _handleDioError(error: error, url: url, onError: onError); 107 | } on SocketException { 108 | // No internet connection 109 | _handleSocketException(url: url, onError: onError); 110 | } on TimeoutException { 111 | // Api call went out of time 112 | _handleTimeoutException(url: url, onError: onError); 113 | } catch (error, stackTrace) { 114 | // print the line of code that throw unexpected exception 115 | Logger().e(stackTrace); 116 | // unexpected error for example (parsing json error) 117 | _handleUnexpectedException(url: url, onError: onError, error: error); 118 | } 119 | } 120 | 121 | /// download file 122 | static download( 123 | {required String url, // file url 124 | required String savePath, // where to save file 125 | Function(ApiException)? onError, 126 | Function(int value, int progress)? onReceiveProgress, 127 | required Function onSuccess}) async { 128 | try { 129 | await _dio.download( 130 | url, 131 | savePath, 132 | options: Options(receiveTimeout: const Duration(seconds: _timeoutInSeconds), sendTimeout: const Duration(seconds: _timeoutInSeconds)), 133 | onReceiveProgress: onReceiveProgress, 134 | ); 135 | onSuccess(); 136 | } catch (error) { 137 | var exception = ApiException(url: url, message: error.toString()); 138 | onError?.call(exception) ?? _handleError(error.toString()); 139 | } 140 | } 141 | 142 | /// handle unexpected error 143 | static _handleUnexpectedException( 144 | {Function(ApiException)? onError, 145 | required String url, 146 | required Object error}) { 147 | if (onError != null) { 148 | onError(ApiException( 149 | message: error.toString(), 150 | url: url, 151 | )); 152 | } else { 153 | _handleError(error.toString()); 154 | } 155 | } 156 | 157 | /// handle timeout exception 158 | static _handleTimeoutException( 159 | {Function(ApiException)? onError, required String url}) { 160 | if (onError != null) { 161 | onError(ApiException( 162 | message: Strings.serverNotResponding.tr, 163 | url: url, 164 | )); 165 | } else { 166 | _handleError(Strings.serverNotResponding.tr); 167 | } 168 | } 169 | 170 | /// handle timeout exception 171 | static _handleSocketException( 172 | {Function(ApiException)? onError, required String url}) { 173 | if (onError != null) { 174 | onError(ApiException( 175 | message: Strings.noInternetConnection.tr, 176 | url: url, 177 | )); 178 | } else { 179 | _handleError(Strings.noInternetConnection.tr); 180 | } 181 | } 182 | 183 | /// handle Dio error 184 | static _handleDioError( 185 | {required DioException error, 186 | Function(ApiException)? onError, 187 | required String url}) { 188 | 189 | // no internet connection 190 | if(error.type == DioExceptionType.connectionError){ 191 | return _handleSocketException(url: url,onError: onError); 192 | } 193 | 194 | // 404 error 195 | if (error.response?.statusCode == 404) { 196 | if (onError != null) { 197 | return onError(ApiException( 198 | message: Strings.urlNotFound.tr, 199 | url: url, 200 | statusCode: 404, 201 | )); 202 | } else { 203 | return _handleError(Strings.urlNotFound.tr); 204 | } 205 | } 206 | 207 | // no internet connection 208 | if (error.message != null && error.message!.toLowerCase().contains('socket')) { 209 | if (onError != null) { 210 | return onError(ApiException( 211 | message: Strings.noInternetConnection.tr, 212 | url: url, 213 | )); 214 | } else { 215 | return _handleError(Strings.noInternetConnection.tr); 216 | } 217 | } 218 | 219 | // check if the error is 500 (server problem) 220 | if (error.response?.statusCode == 500) { 221 | var exception = ApiException( 222 | message: Strings.serverError.tr, 223 | url: url, 224 | statusCode: 500, 225 | ); 226 | 227 | if (onError != null) { 228 | return onError(exception); 229 | } else { 230 | return handleApiError(exception); 231 | } 232 | } 233 | 234 | var exception = ApiException( 235 | url: url, 236 | message: error.message ?? 'Un Expected Api Error!', 237 | response: error.response, 238 | statusCode: error.response?.statusCode); 239 | if (onError != null) { 240 | return onError(exception); 241 | } else { 242 | return handleApiError(exception); 243 | } 244 | } 245 | 246 | /// handle error automaticly (if user didnt pass onError) method 247 | /// it will try to show the message from api if there is no message 248 | /// from api it will show the reason (the dio message) 249 | static handleApiError(ApiException apiException) { 250 | String msg = apiException.toString(); 251 | CustomSnackBar.showCustomErrorToast(message: msg); 252 | } 253 | 254 | /// handle errors without response (500, out of time, no internet,..etc) 255 | static _handleError(String msg) { 256 | CustomSnackBar.showCustomErrorToast(message: msg); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /lib/config/theme/dark_theme_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // TODO add your dark theme colors palette 4 | class DarkThemeColors 5 | { 6 | // PRIMARY 7 | static const Color primaryColor = Color(0xFFFF8C00); 8 | 9 | // SECONDARY 10 | static Color accentColor = Colors.blueAccent; 11 | 12 | //Appbar 13 | static const Color appbarColor = Colors.black; 14 | 15 | //SCAFFOLD 16 | static const Color scaffoldBackgroundColor = Color(0xff2D2D2D); 17 | static const Color backgroundColor = Color(0xff2D2D2D); 18 | static const Color dividerColor = Color(0xff686868); 19 | static const Color cardColor = Color(0xff1e2336); 20 | 21 | //ICONS 22 | static const Color appBarIconsColor = Colors.white; 23 | static const Color iconColor = primaryColor; 24 | 25 | //BUTTON 26 | static const Color buttonColor = primaryColor; 27 | static const Color buttonTextColor = Colors.black; 28 | static const Color buttonDisabledColor = Colors.grey; 29 | static const Color buttonDisabledTextColor = Colors.black; 30 | 31 | //TEXT 32 | static const Color bodyTextColor = Colors.white70; 33 | static const Color displayTextColor = Colors.white; 34 | static const Color bodySmallTextColor = Color(0xff7C7C7C); 35 | static const Color hintTextColor = Color(0xff686868); 36 | 37 | //chip 38 | static const Color chipBackground = primaryColor; 39 | static const Color chipTextColor = Colors.black87; 40 | 41 | // progress bar indicator 42 | static const Color progressIndicatorColor = Color(0xFF40A76A); 43 | 44 | // list tile 45 | static const Color listTileTitleColor = Colors.white; 46 | static const Color listTileSubtitleColor = Colors.white; 47 | static const Color listTileBackgroundColor = Color(0xFF414141); 48 | static const Color listTileIconColor = Colors.white; 49 | 50 | //------------------- custom theme (extensions) ------------------- // 51 | // header containers 52 | static const Color headerContainerBackgroundColor = Color(0XFFf8a319); 53 | 54 | // employee list item 55 | static const Color employeeListItemBackgroundColor = Color(0xFF393939); 56 | static const Color employeeListItemNameColor = Colors.white; 57 | static const Color employeeListItemSubtitleColor = Color(0xFFEDEDED); 58 | static const Color employeeListItemIconsColor = Color(0xFFEDEDED); 59 | 60 | } -------------------------------------------------------------------------------- /lib/config/theme/light_theme_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // TODO add your light theme colors palette 4 | class LightThemeColors 5 | { 6 | // PRIMARY 7 | static const Color primaryColor = Color(0xFF42A7DE); 8 | 9 | // SECONDARY COLOR 10 | static const Color accentColor = Color(0xFFD9EDE1); 11 | 12 | //APPBAR 13 | static const Color appBarColor = primaryColor; 14 | 15 | //SCAFFOLD 16 | static const Color scaffoldBackgroundColor = Colors.white; 17 | static const Color backgroundColor = Colors.white; 18 | static const Color dividerColor = Color(0xff686868); 19 | static const Color cardColor = Color(0xfffafafa); 20 | 21 | //ICONS 22 | static const Color appBarIconsColor = Colors.white; 23 | static const Color iconColor = Colors.black; 24 | 25 | //BUTTON 26 | static const Color buttonColor = primaryColor; 27 | static const Color buttonTextColor = Colors.white; 28 | static const Color buttonDisabledColor = Colors.grey; 29 | static const Color buttonDisabledTextColor = Colors.black; 30 | 31 | //TEXT 32 | static const Color bodyTextColor = Colors.black; 33 | static const Color displayTextColor = Color(0xFF1E2432); 34 | static const Color bodySmallTextColor = Color(0xff7C7C7C); 35 | static const Color hintTextColor = Color(0xff686868); 36 | 37 | //chip 38 | static const Color chipBackground = primaryColor; 39 | static const Color chipTextColor = Colors.white; 40 | 41 | // progress bar indicator 42 | static const Color progressIndicatorColor = Color(0xFF40A76A); 43 | 44 | // list tile 45 | static const Color listTileTitleColor = Color(0xFF575757); 46 | static const Color listTileSubtitleColor = Color(0xFF575757); 47 | static const Color listTileBackgroundColor = Color(0xFFF8F8F8); 48 | static const Color listTileIconColor = Color(0xFF575757); 49 | 50 | //------------------- custom theme (extensions) ------------------- // 51 | // header containers 52 | static const Color headerContainerBackgroundColor = Color(0XFF38B6F0); 53 | 54 | // employee list item 55 | static const Color employeeListItemBackgroundColor = Colors.white; 56 | static const Color employeeListItemNameColor = Color(0xFF4A4A4A); 57 | static const Color employeeListItemSubtitleColor = Color(0xFFA1A4B1); 58 | static const Color employeeListItemIconsColor = Color(0xFFA1A4B1); 59 | } -------------------------------------------------------------------------------- /lib/config/theme/my_fonts.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | import '../../app/data/local/my_shared_pref.dart'; 5 | import '../translations/localization_service.dart'; 6 | 7 | // todo configure text family and size 8 | class MyFonts 9 | { 10 | // return the right font depending on app language 11 | static TextStyle get getAppFontType => LocalizationService.supportedLanguagesFontsFamilies[MySharedPref.getCurrentLocal().languageCode]!; 12 | 13 | // headlines text font 14 | static TextStyle get displayTextStyle => getAppFontType; 15 | 16 | // body text font 17 | static TextStyle get bodyTextStyle => getAppFontType; 18 | 19 | // button text font 20 | static TextStyle get buttonTextStyle => getAppFontType; 21 | 22 | // app bar text font 23 | static TextStyle get appBarTextStyle => getAppFontType; 24 | 25 | // chips text font 26 | static TextStyle get chipTextStyle => getAppFontType; 27 | 28 | // appbar font size 29 | static double get appBarTittleSize => 18.sp; 30 | 31 | // body font size 32 | static double get bodySmallTextSize => 11.sp; 33 | static double get bodyMediumSize => 13.sp; // default font 34 | static double get bodyLargeSize => 16.sp; 35 | // display font size 36 | static double get displayLargeSize => 20.sp; 37 | static double get displayMediumSize => 17.sp; 38 | static double get displaySmallSize => 14.sp; 39 | 40 | //button font size 41 | static double get buttonTextSize => 16.sp; 42 | 43 | //chip font size 44 | static double get chipTextSize => 10.sp; 45 | 46 | // list tile fonts sizes 47 | static double get listTileTitleSize => 13.sp; 48 | static double get listTileSubtitleSize => 12.sp; 49 | 50 | // custom themes (extensions) 51 | static double get employeeListItemNameSize => 13.sp; 52 | static double get employeeListItemSubtitleSize => 13.sp; 53 | } -------------------------------------------------------------------------------- /lib/config/theme/my_styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:getx_skeleton/config/theme/theme_extensions/header_container_theme_data.dart'; 5 | 6 | import 'dark_theme_colors.dart'; 7 | import 'my_fonts.dart'; 8 | import 'light_theme_colors.dart'; 9 | import 'theme_extensions/employee_list_item_theme_data.dart'; 10 | 11 | class MyStyles { 12 | /// custom employee list item theme 13 | static EmployeeListItemThemeData getEmployeeListItemTheme({required bool isLightTheme}) { 14 | return EmployeeListItemThemeData( 15 | backgroundColor: isLightTheme ? LightThemeColors.employeeListItemBackgroundColor : DarkThemeColors.employeeListItemBackgroundColor, 16 | iconTheme: IconThemeData( 17 | color: isLightTheme ? LightThemeColors.employeeListItemIconsColor : DarkThemeColors.employeeListItemIconsColor, 18 | ), 19 | nameTextStyle: MyFonts.bodyTextStyle.copyWith( 20 | fontSize: MyFonts.employeeListItemNameSize, 21 | fontWeight: FontWeight.bold, 22 | color: isLightTheme ? LightThemeColors.employeeListItemNameColor : DarkThemeColors.employeeListItemNameColor, 23 | ), 24 | subtitleTextStyle: MyFonts.bodyTextStyle.copyWith( 25 | fontSize: MyFonts.employeeListItemSubtitleSize, 26 | fontWeight: FontWeight.normal, 27 | color: isLightTheme ? LightThemeColors.employeeListItemSubtitleColor : DarkThemeColors.employeeListItemSubtitleColor, 28 | ), 29 | ); 30 | } 31 | 32 | /// custom header theme 33 | static HeaderContainerThemeData getHeaderContainerTheme( 34 | {required bool isLightTheme}) => 35 | HeaderContainerThemeData( 36 | decoration: BoxDecoration( 37 | color: isLightTheme 38 | ? LightThemeColors.headerContainerBackgroundColor 39 | : DarkThemeColors.headerContainerBackgroundColor, 40 | borderRadius: BorderRadius.circular(8), 41 | )); 42 | 43 | ///icons theme 44 | static IconThemeData getIconTheme({required bool isLightTheme}) => 45 | IconThemeData( 46 | color: isLightTheme 47 | ? LightThemeColors.iconColor 48 | : DarkThemeColors.iconColor, 49 | ); 50 | 51 | ///app bar theme 52 | static AppBarTheme getAppBarTheme({required bool isLightTheme}) => 53 | AppBarTheme( 54 | elevation: 0, 55 | titleTextStyle: 56 | getTextTheme(isLightTheme: isLightTheme).bodyMedium!.copyWith( 57 | color: Colors.white, 58 | fontSize: MyFonts.appBarTittleSize, 59 | ), 60 | iconTheme: IconThemeData( 61 | color: isLightTheme 62 | ? LightThemeColors.appBarIconsColor 63 | : DarkThemeColors.appBarIconsColor), 64 | backgroundColor: isLightTheme 65 | ? LightThemeColors.appBarColor 66 | : DarkThemeColors.appbarColor, 67 | ); 68 | 69 | ///text theme 70 | static TextTheme getTextTheme({required bool isLightTheme}) => TextTheme( 71 | labelLarge: MyFonts.buttonTextStyle.copyWith( 72 | fontSize: MyFonts.buttonTextSize, 73 | ), 74 | bodyLarge: (MyFonts.bodyTextStyle).copyWith( 75 | fontWeight: FontWeight.bold, 76 | fontSize: MyFonts.bodyLargeSize, 77 | color: isLightTheme 78 | ? LightThemeColors.bodyTextColor 79 | : DarkThemeColors.bodyTextColor, 80 | ), 81 | bodyMedium: (MyFonts.bodyTextStyle).copyWith( 82 | fontSize: MyFonts.bodyMediumSize, 83 | color: isLightTheme 84 | ? LightThemeColors.bodyTextColor 85 | : DarkThemeColors.bodyTextColor, 86 | ), 87 | displayLarge: (MyFonts.displayTextStyle).copyWith( 88 | fontSize: MyFonts.displayLargeSize, 89 | fontWeight: FontWeight.bold, 90 | color: isLightTheme 91 | ? LightThemeColors.displayTextColor 92 | : DarkThemeColors.displayTextColor, 93 | ), 94 | bodySmall: TextStyle( 95 | color: isLightTheme 96 | ? LightThemeColors.bodySmallTextColor 97 | : DarkThemeColors.bodySmallTextColor, 98 | fontSize: MyFonts.bodySmallTextSize), 99 | displayMedium: (MyFonts.displayTextStyle).copyWith( 100 | fontSize: MyFonts.displayMediumSize, 101 | fontWeight: FontWeight.bold, 102 | color: isLightTheme 103 | ? LightThemeColors.displayTextColor 104 | : DarkThemeColors.displayTextColor), 105 | displaySmall: (MyFonts.displayTextStyle).copyWith( 106 | fontSize: MyFonts.displaySmallSize, 107 | fontWeight: FontWeight.bold, 108 | color: isLightTheme 109 | ? LightThemeColors.displayTextColor 110 | : DarkThemeColors.displayTextColor, 111 | ), 112 | ); 113 | 114 | static ChipThemeData getChipTheme({required bool isLightTheme}) { 115 | return ChipThemeData( 116 | backgroundColor: isLightTheme 117 | ? LightThemeColors.chipBackground 118 | : DarkThemeColors.chipBackground, 119 | brightness: Brightness.light, 120 | labelStyle: getChipTextStyle(isLightTheme: isLightTheme), 121 | secondaryLabelStyle: getChipTextStyle(isLightTheme: isLightTheme), 122 | selectedColor: Colors.black, 123 | disabledColor: Colors.green, 124 | padding: const EdgeInsets.all(5), 125 | secondarySelectedColor: Colors.purple, 126 | ); 127 | } 128 | 129 | ///Chips text style 130 | static TextStyle getChipTextStyle({required bool isLightTheme}) { 131 | return MyFonts.chipTextStyle.copyWith( 132 | fontSize: MyFonts.chipTextSize, 133 | color: isLightTheme 134 | ? LightThemeColors.chipTextColor 135 | : DarkThemeColors.chipTextColor, 136 | ); 137 | } 138 | 139 | // elevated button text style 140 | static MaterialStateProperty? getElevatedButtonTextStyle( 141 | bool isLightTheme, 142 | {bool isBold = true, 143 | double? fontSize}) { 144 | return MaterialStateProperty.resolveWith( 145 | (Set states) { 146 | if (states.contains(MaterialState.pressed)) { 147 | return MyFonts.buttonTextStyle.copyWith( 148 | fontWeight: isBold ? FontWeight.bold : FontWeight.normal, 149 | fontSize: fontSize ?? MyFonts.buttonTextSize, 150 | color: isLightTheme 151 | ? LightThemeColors.buttonTextColor 152 | : DarkThemeColors.buttonTextColor, 153 | ); 154 | } else if (states.contains(MaterialState.disabled)) { 155 | return MyFonts.buttonTextStyle.copyWith( 156 | fontSize: fontSize ?? MyFonts.buttonTextSize, 157 | fontWeight: isBold ? FontWeight.bold : FontWeight.normal, 158 | color: isLightTheme 159 | ? LightThemeColors.buttonDisabledTextColor 160 | : DarkThemeColors.buttonDisabledTextColor, 161 | ); 162 | } 163 | return MyFonts.buttonTextStyle.copyWith( 164 | fontSize: fontSize ?? MyFonts.buttonTextSize, 165 | fontWeight: isBold ? FontWeight.bold : FontWeight.normal, 166 | color: isLightTheme 167 | ? LightThemeColors.buttonTextColor 168 | : DarkThemeColors.buttonTextColor, 169 | ); // Use the component's default. 170 | }, 171 | ); 172 | } 173 | 174 | //elevated button theme data 175 | static ElevatedButtonThemeData getElevatedButtonTheme( 176 | {required bool isLightTheme}) => 177 | ElevatedButtonThemeData( 178 | style: ButtonStyle( 179 | shape: MaterialStateProperty.all( 180 | RoundedRectangleBorder( 181 | borderRadius: BorderRadius.circular(6.r), 182 | //side: BorderSide(color: Colors.teal, width: 2.0), 183 | ), 184 | ), 185 | elevation: MaterialStateProperty.all(0), 186 | padding: MaterialStateProperty.all( 187 | EdgeInsets.symmetric(vertical: 8.h)), 188 | textStyle: getElevatedButtonTextStyle(isLightTheme), 189 | backgroundColor: MaterialStateProperty.resolveWith( 190 | (Set states) { 191 | if (states.contains(MaterialState.pressed)) { 192 | return isLightTheme 193 | ? LightThemeColors.buttonColor.withOpacity(0.5) 194 | : DarkThemeColors.buttonColor.withOpacity(0.5); 195 | } else if (states.contains(MaterialState.disabled)) { 196 | return isLightTheme 197 | ? LightThemeColors.buttonDisabledColor 198 | : DarkThemeColors.buttonDisabledColor; 199 | } 200 | return isLightTheme 201 | ? LightThemeColors.buttonColor 202 | : DarkThemeColors.buttonColor; // Use the component's default. 203 | }, 204 | ), 205 | ), 206 | ); 207 | 208 | /// list tile theme data 209 | static ListTileThemeData getListTileThemeData({required bool isLightTheme}) { 210 | return ListTileThemeData( 211 | shape: RoundedRectangleBorder( 212 | borderRadius: BorderRadius.circular(8.r), 213 | ), 214 | iconColor: isLightTheme 215 | ? LightThemeColors.listTileIconColor 216 | : DarkThemeColors.listTileIconColor, 217 | tileColor: isLightTheme 218 | ? LightThemeColors.listTileBackgroundColor 219 | : DarkThemeColors.listTileBackgroundColor, 220 | titleTextStyle: TextStyle( 221 | fontSize: MyFonts.listTileTitleSize, 222 | color: isLightTheme 223 | ? LightThemeColors.listTileTitleColor 224 | : DarkThemeColors.listTileTitleColor, 225 | ), 226 | subtitleTextStyle: TextStyle( 227 | fontSize: MyFonts.listTileSubtitleSize, 228 | color: isLightTheme 229 | ? LightThemeColors.listTileSubtitleColor 230 | : DarkThemeColors.listTileSubtitleColor, 231 | ), 232 | ); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /lib/config/theme/my_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:getx_skeleton/config/theme/my_fonts.dart'; 5 | 6 | import '../../app/data/local/my_shared_pref.dart'; 7 | import 'dark_theme_colors.dart'; 8 | import 'light_theme_colors.dart'; 9 | import 'my_styles.dart'; 10 | import 'theme_extensions/employee_list_item_theme_data.dart'; 11 | 12 | class MyTheme { 13 | static getThemeData({required bool isLight}){ 14 | return ThemeData( 15 | // main color (app bar,tabs..etc) 16 | primaryColor: isLight ? LightThemeColors.primaryColor : DarkThemeColors.primaryColor, 17 | 18 | // secondary & background color 19 | colorScheme: ColorScheme.fromSwatch( 20 | accentColor: isLight ? LightThemeColors.accentColor : DarkThemeColors.accentColor, 21 | backgroundColor: isLight ? LightThemeColors.backgroundColor : DarkThemeColors.backgroundColor, 22 | brightness: isLight ? Brightness.light : Brightness.dark, 23 | ) 24 | .copyWith( 25 | secondary: isLight ? LightThemeColors.accentColor : DarkThemeColors.accentColor, 26 | ), 27 | 28 | // color contrast (if the theme is dark text should be white for example) 29 | brightness: isLight ? Brightness.light : Brightness.dark, 30 | 31 | // card widget background color 32 | cardColor: isLight ? LightThemeColors.cardColor : DarkThemeColors.cardColor, 33 | 34 | // hint text color 35 | hintColor: isLight ? LightThemeColors.hintTextColor : DarkThemeColors.hintTextColor, 36 | 37 | // divider color 38 | dividerColor: isLight ? LightThemeColors.dividerColor : DarkThemeColors.dividerColor, 39 | 40 | // app background color 41 | scaffoldBackgroundColor: isLight ? LightThemeColors.scaffoldBackgroundColor : DarkThemeColors.scaffoldBackgroundColor, 42 | 43 | // progress bar theme 44 | progressIndicatorTheme: ProgressIndicatorThemeData( 45 | color: isLight ? LightThemeColors.primaryColor : DarkThemeColors.primaryColor, 46 | ), 47 | 48 | // appBar theme 49 | appBarTheme: MyStyles.getAppBarTheme(isLightTheme: isLight), 50 | 51 | // elevated button theme 52 | elevatedButtonTheme: MyStyles.getElevatedButtonTheme(isLightTheme: isLight), 53 | 54 | // text theme 55 | textTheme: MyStyles.getTextTheme(isLightTheme: isLight), 56 | 57 | // chip theme 58 | chipTheme: MyStyles.getChipTheme(isLightTheme: isLight), 59 | 60 | // icon theme 61 | iconTheme: MyStyles.getIconTheme(isLightTheme: isLight), 62 | 63 | // list tile theme 64 | listTileTheme: MyStyles.getListTileThemeData(isLightTheme: isLight), 65 | 66 | // custom themes 67 | extensions: [ 68 | MyStyles.getHeaderContainerTheme(isLightTheme: isLight), 69 | MyStyles.getEmployeeListItemTheme(isLightTheme: isLight), 70 | ] 71 | ); 72 | } 73 | 74 | /// update app theme and save theme type to shared pref 75 | /// (so when the app is killed and up again theme will remain the same) 76 | static changeTheme(){ 77 | // *) check if the current theme is light (default is light) 78 | bool isLightTheme = MySharedPref.getThemeIsLight(); 79 | 80 | // *) store the new theme mode on get storage 81 | MySharedPref.setThemeIsLight(!isLightTheme); 82 | 83 | // *) let GetX change theme 84 | Get.changeThemeMode(!isLightTheme ? ThemeMode.light : ThemeMode.dark); 85 | } 86 | 87 | /// check if the theme is light or dark 88 | bool get getThemeIsLight => MySharedPref.getThemeIsLight(); 89 | } -------------------------------------------------------------------------------- /lib/config/theme/theme_extensions/employee_list_item_theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EmployeeListItemThemeData extends ThemeExtension { 4 | final TextStyle? nameTextStyle; 5 | final TextStyle? subtitleTextStyle; 6 | final IconThemeData? iconTheme; 7 | final Color? backgroundColor; 8 | 9 | const EmployeeListItemThemeData({ 10 | this.nameTextStyle, 11 | this.subtitleTextStyle, 12 | this.iconTheme, 13 | this.backgroundColor, 14 | }); 15 | 16 | @override 17 | ThemeExtension copyWith() { 18 | return EmployeeListItemThemeData( 19 | nameTextStyle: nameTextStyle, 20 | subtitleTextStyle: subtitleTextStyle, 21 | iconTheme: iconTheme, 22 | backgroundColor: backgroundColor, 23 | ); 24 | } 25 | 26 | @override 27 | EmployeeListItemThemeData lerp(ThemeExtension? other, double t) { 28 | if(other is! EmployeeListItemThemeData) { 29 | return this; 30 | } 31 | 32 | return EmployeeListItemThemeData( 33 | nameTextStyle: TextStyle.lerp( 34 | nameTextStyle, 35 | other.nameTextStyle, 36 | t, 37 | ), 38 | subtitleTextStyle: TextStyle.lerp( 39 | subtitleTextStyle, 40 | other.subtitleTextStyle, 41 | t, 42 | ), 43 | iconTheme: IconThemeData.lerp( 44 | iconTheme, 45 | other.iconTheme, 46 | t, 47 | ), 48 | backgroundColor: Color.lerp( 49 | backgroundColor, 50 | other.backgroundColor, 51 | t, 52 | ) 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/config/theme/theme_extensions/header_container_theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HeaderContainerThemeData extends ThemeExtension { 4 | final BoxDecoration? decoration; 5 | 6 | const HeaderContainerThemeData({ 7 | this.decoration, 8 | }); 9 | 10 | @override 11 | ThemeExtension copyWith() { 12 | return HeaderContainerThemeData( 13 | decoration: decoration, 14 | ); 15 | } 16 | 17 | @override 18 | ThemeExtension lerp(covariant ThemeExtension? other, double t) { 19 | if (other is! HeaderContainerThemeData) { 20 | return this; 21 | } 22 | 23 | return HeaderContainerThemeData( 24 | decoration: BoxDecoration.lerp(decoration, other.decoration, t) ?? BoxDecoration(borderRadius: BorderRadius.circular(8)), // If lerp returns null, use an empty BoxDecoration 25 | ); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /lib/config/translations/ar_AR/ar_ar_translation.dart: -------------------------------------------------------------------------------- 1 | import '../strings_enum.dart'; 2 | 3 | final Map arAR = 4 | { 5 | Strings.hello : 'مرحباً!', 6 | Strings.loading : 'جاري التحميل', 7 | 8 | Strings.changeTheme : 'تغيير الثيم', 9 | Strings.changeLanguage : 'تغيير اللغة', 10 | 11 | Strings.noInternetConnection : 'لا يوجد إتصال بالإنترنت', 12 | Strings.serverNotResponding : 'لا يوجد إستجابة من السيرفر!', 13 | Strings.someThingWentWorng : 'حدث خطأ غير متوقع!', 14 | Strings.apiNotFound : 'الرابط الذي تحاول الوصول اليه غير موجود!', 15 | Strings.serverError : 'مشكلة من السيرفر', 16 | Strings.urlNotFound : 'مشكلة في الرابط', 17 | 18 | Strings.goodMorning : 'صباح الخير', 19 | Strings.name : 'عماد البلتاجي', 20 | Strings.attendanceRegistration : 'تسجيل الحضور', 21 | Strings.time : '09:00 م', 22 | Strings.vocation : 'الإجازات', 23 | Strings.remainingTasks : 'المهام المتبقية', 24 | Strings.daysOfDelays : 'أيام التأخر', 25 | Strings.absentDays : 'أيام الغياب', 26 | Strings.days : 'ايام', 27 | Strings.tasks : 'المهام', 28 | Strings.vacationingEmployees : 'موظفين في اجازة', 29 | Strings.viewAll : 'عرض الكل', 30 | Strings.gaza : 'غزة', 31 | Strings.abdQader : 'عبد القادر الشريف', 32 | Strings.loai : 'لؤي عرفات', 33 | Strings.retry : 'اعادة المحاولة', 34 | Strings.internetError : 'خطأ في الاتصال بالانترنت ⚠️', 35 | }; -------------------------------------------------------------------------------- /lib/config/translations/en_US/en_us_translation.dart: -------------------------------------------------------------------------------- 1 | import '../strings_enum.dart'; 2 | 3 | const Map enUs = { 4 | Strings.hello : 'Hello!', 5 | Strings.loading : 'Loading', 6 | 7 | Strings.changeTheme : 'Change theme', 8 | Strings.changeLanguage : 'Change language', 9 | 10 | Strings.noInternetConnection : 'No internet connection!', 11 | Strings.serverNotResponding : 'Server is not responding!', 12 | Strings.someThingWentWorng : 'Something went wrong', 13 | Strings.apiNotFound : 'Route not found!', 14 | Strings.serverError : 'Server error', 15 | Strings.urlNotFound : 'Url not found', 16 | 17 | Strings.goodMorning : 'Good morning', 18 | Strings.name : 'Emad Beltaje', 19 | Strings.attendanceRegistration : 'Attendance Registration', 20 | Strings.time : '09:00 am', 21 | Strings.vocation : 'Vocation', 22 | Strings.remainingTasks : 'Remaining Tasks', 23 | Strings.daysOfDelays : 'Days of Delays', 24 | Strings.absentDays : 'Absent Days', 25 | Strings.days : 'Days', 26 | Strings.tasks : 'Tasks', 27 | Strings.vacationingEmployees : 'Vacationing Employees', 28 | Strings.viewAll : 'View All', 29 | Strings.gaza : 'Gaza', 30 | Strings.abdQader : 'Abd-Qader Shareef', 31 | Strings.loai : 'Loai Arafat', 32 | Strings.retry : 'Retry', 33 | Strings.internetError : 'Error Occurred Check Your Internet Connection! ⚠️', 34 | }; -------------------------------------------------------------------------------- /lib/config/translations/localization_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../app/data/local/my_shared_pref.dart'; 5 | import 'ar_AR/ar_ar_translation.dart'; 6 | import 'en_US/en_us_translation.dart'; 7 | 8 | class LocalizationService extends Translations { 9 | // prevent creating instance 10 | LocalizationService._(); 11 | 12 | static LocalizationService? _instance; 13 | 14 | static LocalizationService getInstance() { 15 | _instance ??= LocalizationService._(); 16 | return _instance!; 17 | } 18 | 19 | // default language 20 | // todo change the default language 21 | static Locale defaultLanguage = supportedLanguages['en']!; 22 | 23 | // supported languages 24 | static Map supportedLanguages = { 25 | 'en' : const Locale('en', 'US'), 26 | 'ar' : const Locale('ar', 'AR'), 27 | }; 28 | 29 | // supported languages fonts family (must be in assets & pubspec yaml) or you can use google fonts 30 | static Map supportedLanguagesFontsFamilies = { 31 | // todo add your English font families (add to assets/fonts, pubspec and name it here) default is poppins for english and cairo for arabic 32 | 'en' : const TextStyle(fontFamily: 'Poppins'), 33 | 'ar': const TextStyle(fontFamily: 'Cairo'), 34 | }; 35 | 36 | @override 37 | Map> get keys => { 38 | 'en_US': enUs, 39 | 'ar_AR': arAR, 40 | }; 41 | 42 | /// check if the language is supported 43 | static isLanguageSupported(String languageCode) => 44 | supportedLanguages.keys.contains(languageCode); 45 | 46 | 47 | /// update app language by code language for example (en,ar..etc) 48 | static updateLanguage(String languageCode) async { 49 | // check if the language is supported 50 | if(!isLanguageSupported(languageCode)) return; 51 | // update current language in shared pref 52 | await MySharedPref.setCurrentLanguage(languageCode); 53 | if(!Get.testMode) { 54 | Get.updateLocale(supportedLanguages[languageCode]!); 55 | } 56 | } 57 | 58 | /// check if the language is english 59 | static bool isItEnglish() => 60 | MySharedPref.getCurrentLocal().languageCode.toLowerCase().contains('en'); 61 | 62 | /// get current locale 63 | static Locale getCurrentLocal () => MySharedPref.getCurrentLocal(); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /lib/config/translations/strings_enum.dart: -------------------------------------------------------------------------------- 1 | class Strings { 2 | static const String hello = 'hello'; 3 | static const String loading = 'loading'; 4 | 5 | 6 | static const String changeTheme = 'change_theme'; 7 | static const String changeLanguage = 'change_language'; 8 | 9 | 10 | static const String noInternetConnection = 'no internet connection'; 11 | static const String serverNotResponding = 'server not responding'; 12 | static const String someThingWentWorng = 'something went wrong'; 13 | static const String apiNotFound = 'api not found'; 14 | static const String serverError = 'Server error'; 15 | static const String urlNotFound = 'Url not found'; 16 | 17 | static const String goodMorning = 'good morning'; 18 | static const String name = 'name'; 19 | static const String attendanceRegistration = 'Attendance Registration'; 20 | static const String time = 'time'; 21 | static const String vocation = 'vocation'; 22 | static const String remainingTasks = 'Remaining Tasks'; 23 | static const String daysOfDelays = 'Days of Delays'; 24 | static const String absentDays = 'Absent Days'; 25 | static const String days = 'day'; 26 | static const String tasks = 'task'; 27 | static const String vacationingEmployees = 'vacationing employees'; 28 | static const String viewAll = 'view all'; 29 | static const String gaza = 'gaza'; 30 | static const String abdQader = 'adb'; 31 | static const String loai = 'Loai'; 32 | static const String retry = 'retry'; 33 | static const String internetError = 'internet error'; 34 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | import 'package:get/get.dart'; 5 | import 'package:getx_skeleton/utils/awesome_notifications_helper.dart'; 6 | 7 | import 'app/data/local/my_hive.dart'; 8 | import 'app/data/local/my_shared_pref.dart'; 9 | import 'app/data/models/user_model.dart'; 10 | import 'app/routes/app_pages.dart'; 11 | import 'config/theme/my_theme.dart'; 12 | import 'config/translations/localization_service.dart'; 13 | import 'utils/fcm_helper.dart'; 14 | 15 | Future main() async { 16 | // wait for bindings 17 | WidgetsFlutterBinding.ensureInitialized(); 18 | 19 | // initialize local db (hive) and register our custom adapters 20 | await MyHive.init( 21 | registerAdapters: (hive) { 22 | hive.registerAdapter(UserModelAdapter()); 23 | //myHive.registerAdapter(OtherAdapter()); 24 | } 25 | ); 26 | 27 | // init shared preference 28 | await MySharedPref.init(); 29 | 30 | // inti fcm services 31 | await FcmHelper.initFcm(); 32 | 33 | // initialize local notifications service 34 | await AwesomeNotificationsHelper.init(); 35 | 36 | runApp( 37 | ScreenUtilInit( 38 | // todo add your (Xd / Figma) artboard size 39 | designSize: const Size(375, 812), 40 | minTextAdapt: true, 41 | splitScreenMode: true, 42 | useInheritedMediaQuery: true, 43 | rebuildFactor: (old, data) => true, 44 | builder: (context, widget) { 45 | return GetMaterialApp( 46 | // todo add your app name 47 | title: "GetXSkeleton", 48 | useInheritedMediaQuery: true, 49 | debugShowCheckedModeBanner: false, 50 | builder: (context,widget) { 51 | bool themeIsLight = MySharedPref.getThemeIsLight(); 52 | return Theme( 53 | data: MyTheme.getThemeData(isLight: themeIsLight), 54 | child: MediaQuery( 55 | // prevent font from scalling (some people use big/small device fonts) 56 | // but we want our app font to still the same and dont get affected 57 | data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), 58 | child: widget!, 59 | ), 60 | ); 61 | }, 62 | initialRoute: AppPages.INITIAL, // first screen to show when app is running 63 | getPages: AppPages.routes, // app screens 64 | locale: MySharedPref.getCurrentLocal(), // app language 65 | translations: LocalizationService.getInstance(), // localization services in app (controller app language) 66 | ); 67 | }, 68 | ), 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /lib/utils/awesome_notifications_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_notifications/awesome_notifications.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '../app/routes/app_pages.dart'; 6 | 7 | class AwesomeNotificationsHelper { 8 | // prevent making instance 9 | AwesomeNotificationsHelper._(); 10 | 11 | // Notification lib 12 | static AwesomeNotifications awesomeNotifications = AwesomeNotifications(); 13 | 14 | /// initialize local notifications service, create channels and groups 15 | /// setup notifications button actions handlers 16 | static init() async { 17 | // initialize local notifications 18 | await _initNotification(); 19 | 20 | // request permission to show notifications 21 | awesomeNotifications.requestPermissionToSendNotifications(); 22 | 23 | // list when user click on notifications 24 | listenToActionButtons(); 25 | } 26 | 27 | 28 | /// when user click on notification or click on button on the notification 29 | static listenToActionButtons() { 30 | // Only after at least the action method is set, the notification events are delivered 31 | awesomeNotifications.setListeners( 32 | onActionReceivedMethod: NotificationController.onActionReceivedMethod, 33 | onNotificationCreatedMethod: NotificationController.onNotificationCreatedMethod, 34 | onNotificationDisplayedMethod: NotificationController.onNotificationDisplayedMethod, 35 | onDismissActionReceivedMethod: NotificationController.onDismissActionReceivedMethod 36 | ); 37 | } 38 | 39 | 40 | ///init notifications channels 41 | static _initNotification() async { 42 | await awesomeNotifications.initialize( 43 | null, // null mean it will show app icon on the notification (status bar) 44 | [ 45 | NotificationChannel( 46 | channelGroupKey: NotificationChannels.generalChannelGroupKey, 47 | channelKey: NotificationChannels.generalChannelKey, 48 | channelName: NotificationChannels.generalChannelName, 49 | groupKey: NotificationChannels.generalGroupKey, 50 | channelDescription: NotificationChannels.generalChannelDescription, 51 | defaultColor: Colors.green, 52 | ledColor: Colors.white, 53 | channelShowBadge: true, 54 | playSound: true, 55 | importance: NotificationImportance.Max, 56 | ), 57 | NotificationChannel( 58 | channelGroupKey: NotificationChannels.chatChannelGroupKey, 59 | channelKey: NotificationChannels.chatChannelKey, 60 | channelName: NotificationChannels.chatChannelName, 61 | groupKey: NotificationChannels.chatGroupKey, 62 | channelDescription: NotificationChannels.chatChannelDescription, 63 | defaultColor: Colors.green, 64 | ledColor: Colors.white, 65 | channelShowBadge: true, 66 | playSound: true, 67 | importance: NotificationImportance.Max) 68 | ], 69 | 70 | channelGroups: [ 71 | NotificationChannelGroup( 72 | channelGroupKey: NotificationChannels.generalChannelGroupKey, 73 | channelGroupName: NotificationChannels.generalChannelGroupName, 74 | ), 75 | NotificationChannelGroup( 76 | channelGroupKey: NotificationChannels.chatChannelGroupKey, 77 | channelGroupName: NotificationChannels.chatChannelGroupName, 78 | ) 79 | ]); 80 | } 81 | 82 | 83 | //display notification for user with sound 84 | static showNotification( 85 | {required String title, 86 | required String body, 87 | required int id, 88 | String? channelKey, 89 | String? groupKey, 90 | NotificationLayout? notificationLayout, 91 | String? summary, 92 | List? actionButtons, 93 | Map? payload, 94 | String? largeIcon}) async { 95 | awesomeNotifications.isNotificationAllowed().then((isAllowed) { 96 | if (!isAllowed) { 97 | awesomeNotifications.requestPermissionToSendNotifications(); 98 | } else { 99 | // u can show notification 100 | awesomeNotifications.createNotification( 101 | content: NotificationContent( 102 | id: id, 103 | title: title, 104 | body: body, 105 | groupKey: groupKey ?? NotificationChannels.generalGroupKey, 106 | channelKey: channelKey ?? NotificationChannels.generalChannelKey, 107 | showWhen: true, // Hide/show the time elapsed since notification was displayed 108 | payload: payload, // data of the notification (it will be used when user clicks on notification) 109 | notificationLayout: notificationLayout ?? NotificationLayout.Default, // notification shape (message,media player..etc) For ex => NotificationLayout.Messaging 110 | autoDismissible: true, // dismiss notification when user clicks on it 111 | summary: summary, // for ex: New message (it will be shown on status bar before notificaiton shows up) 112 | largeIcon: largeIcon, // image of sender for ex (when someone send you message his image will be shown) 113 | ), 114 | actionButtons: actionButtons, 115 | ); 116 | } 117 | }); 118 | } 119 | } 120 | 121 | 122 | class NotificationController { 123 | /// Use this method to detect when a new notification or a schedule is created 124 | @pragma("vm:entry-point") 125 | static Future onNotificationCreatedMethod(ReceivedNotification receivedNotification) async { 126 | // Your code goes here 127 | } 128 | 129 | /// Use this method to detect every time that a new notification is displayed 130 | @pragma("vm:entry-point") 131 | static Future onNotificationDisplayedMethod(ReceivedNotification receivedNotification) async { 132 | // Your code goes here 133 | } 134 | 135 | /// Use this method to detect if the user dismissed a notification 136 | @pragma("vm:entry-point") 137 | static Future onDismissActionReceivedMethod(ReceivedAction receivedAction) async { 138 | // Your code goes here 139 | } 140 | 141 | /// Use this method to detect when the user taps on a notification or action button 142 | @pragma("vm:entry-point") 143 | static Future onActionReceivedMethod(ReceivedAction receivedAction) async { 144 | Map? payload = receivedAction.payload; 145 | // TODO handle clicking on notification 146 | // example 147 | // String routeToGetTo = payload['route']; 148 | // normal navigation (Get.toNamed) will throw error 149 | Get.key.currentState?.pushNamed(Routes.HOME); 150 | } 151 | } 152 | 153 | 154 | class NotificationChannels { 155 | // chat channel (for messages only) 156 | static String get chatChannelKey => "chat_channel"; 157 | static String get chatChannelName => "Chat channel"; 158 | static String get chatGroupKey => "chat group key"; 159 | static String get chatChannelGroupKey => "chat_channel_group"; 160 | static String get chatChannelGroupName => "Chat notifications channels"; 161 | static String get chatChannelDescription => "Chat notifications channels"; 162 | 163 | // general channel (for all other notifications) 164 | static String get generalChannelKey => "general_channel"; 165 | static String get generalGroupKey => "general group key"; 166 | static String get generalChannelGroupKey => "general_channel_group"; 167 | static String get generalChannelGroupName => "general notifications channel"; 168 | static String get generalChannelName => "general notifications channels"; 169 | static String get generalChannelDescription => "Notification channel for general notifications"; 170 | } 171 | -------------------------------------------------------------------------------- /lib/utils/constants.dart: -------------------------------------------------------------------------------- 1 | class Constants { 2 | static const baseUrl = 'https://jsonplaceholder.typicode.com'; 3 | static const todosApiUrl = baseUrl + '/todos'; 4 | } -------------------------------------------------------------------------------- /lib/utils/fcm_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_core/firebase_core.dart'; 2 | import 'package:firebase_messaging/firebase_messaging.dart'; 3 | import 'package:logger/logger.dart'; 4 | import '../app/data/local/my_shared_pref.dart'; 5 | import 'awesome_notifications_helper.dart'; 6 | 7 | class FcmHelper { 8 | // prevent making instance 9 | FcmHelper._(); 10 | 11 | // FCM Messaging 12 | static late FirebaseMessaging messaging; 13 | 14 | /// this function will initialize firebase and fcm instance 15 | static Future initFcm() async { 16 | try { 17 | // initialize fcm and firebase core 18 | await Firebase.initializeApp( 19 | // TODO: uncomment this line if you connected to firebase via cli 20 | //options: DefaultFirebaseOptions.currentPlatform, 21 | ); 22 | 23 | // initialize firebase 24 | messaging = FirebaseMessaging.instance; 25 | 26 | // notification settings handler 27 | await _setupFcmNotificationSettings(); 28 | 29 | // generate token if it not already generated and store it on shared pref 30 | await _generateFcmToken(); 31 | 32 | // background and foreground handlers 33 | FirebaseMessaging.onMessage.listen(_fcmForegroundHandler); 34 | FirebaseMessaging.onBackgroundMessage(_fcmBackgroundHandler); 35 | } catch (error) { 36 | // if you are connected to firebase and still get error 37 | // check the todo up in the function else ignore the error 38 | // or stop fcm service from main.dart class 39 | Logger().e(error); 40 | } 41 | } 42 | 43 | ///handle fcm notification settings (sound,badge..etc) 44 | static Future _setupFcmNotificationSettings() async { 45 | //show notification with sound and badge 46 | messaging.setForegroundNotificationPresentationOptions( 47 | alert: true, 48 | sound: true, 49 | badge: true, 50 | ); 51 | 52 | //NotificationSettings settings 53 | await messaging.requestPermission( 54 | alert: true, 55 | badge: true, 56 | sound: true, 57 | provisional: true, 58 | ); 59 | } 60 | 61 | /// generate and save fcm token if its not already generated (generate only for 1 time) 62 | static Future _generateFcmToken() async { 63 | try { 64 | var token = await messaging.getToken(); 65 | if(token != null){ 66 | MySharedPref.setFcmToken(token); 67 | _sendFcmTokenToServer(); 68 | }else { 69 | // retry generating token 70 | await Future.delayed(const Duration(seconds: 5)); 71 | _generateFcmToken(); 72 | } 73 | } catch (error) { 74 | Logger().e(error); 75 | } 76 | } 77 | 78 | /// this method will be triggered when the app generate fcm 79 | /// token successfully 80 | static _sendFcmTokenToServer(){ 81 | var token = MySharedPref.getFcmToken(); 82 | // TODO SEND FCM TOKEN TO SERVER 83 | } 84 | 85 | ///handle fcm notification when app is closed/terminated 86 | /// if you are wondering about this annotation read the following 87 | /// https://stackoverflow.com/a/67083337 88 | @pragma('vm:entry-point') 89 | static Future _fcmBackgroundHandler(RemoteMessage message) async { 90 | AwesomeNotificationsHelper.showNotification( 91 | id: 1, 92 | title: message.notification?.title ?? 'Tittle', 93 | body: message.notification?.body ?? 'Body', 94 | payload: message.data.cast(), // pass payload to the notification card so you can use it (when user click on notification) 95 | ); 96 | } 97 | 98 | //handle fcm notification when app is open 99 | static Future _fcmForegroundHandler(RemoteMessage message) async { 100 | AwesomeNotificationsHelper.showNotification( 101 | id: 1, 102 | title: message.notification?.title ?? 'Tittle', 103 | body: message.notification?.body ?? 'Body', 104 | payload: message.data.cast(), // pass payload to the notification card so you can use it (when user click on notification) 105 | ); 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /preview_images/fail_snackbar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/preview_images/fail_snackbar.jpg -------------------------------------------------------------------------------- /preview_images/fail_toast.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/preview_images/fail_toast.jpg -------------------------------------------------------------------------------- /preview_images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/preview_images/github.png -------------------------------------------------------------------------------- /preview_images/success_snackbar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/preview_images/success_snackbar.jpg -------------------------------------------------------------------------------- /preview_images/success_toast.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmadBeltaje/flutter_getx_template/33dd67a39a65e5b91cf675520a82c8a1bca178a2/preview_images/success_toast.jpg -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: getx_skeleton 2 | version: 1.0.0+1 3 | publish_to: none 4 | description: start your project in the fastest way 5 | environment: 6 | sdk: '>=3.3.4 <4.0.0' 7 | 8 | dependencies: 9 | cupertino_icons: ^1.0.8 10 | get: ^4.6.6 # for state management and context less stuff 11 | 12 | logger: ^2.3.0 # log events in clear way insted of (print) 13 | 14 | flutter_screenutil: ^5.9.3 # make app responsive 15 | 16 | dio: ^5.4.3+1 # for api requests 17 | http_mock_adapter: ^0.6.1 # mocking dio for testing 18 | pretty_dio_logger: ^1.3.1 # for dio api logging and debugging 19 | 20 | hive: ^2.2.3 # local database (SQFlite replacement) 21 | hive_flutter: ^1.1.0 # make it easy to use hive (no need for path provider) 22 | shared_preferences: ^2.2.3 # shared preferences persistence key value store 23 | 24 | firebase_core: ^3.0.0 # to initialize firebase services 25 | firebase_messaging: ^15.0.0 # fcm services 26 | awesome_notifications: ^0.9.3+1 # notifications services (will be used for fcm) 27 | 28 | flutter_launcher_icons: # change app icon 29 | change_app_package_name: # change package name 30 | rename_app: 1.4.0 # rename app 31 | 32 | flutter_svg: # display svg vectors 33 | 34 | flutter: 35 | sdk: flutter 36 | 37 | dev_dependencies: 38 | mockito: 39 | flutter_lints: 40 | build_runner: # generate dart files 41 | hive_generator: # generate hive files 42 | pub_semver: ^2.1.4 # helper to generate hive models 43 | 44 | integration_test: 45 | sdk: flutter 46 | 47 | flutter_test: 48 | sdk: flutter 49 | 50 | flutter_icons: 51 | android: true 52 | ios: true 53 | image_path: "assets/images/app_icon.png" 54 | 55 | flutter: 56 | uses-material-design: true 57 | 58 | assets: 59 | - assets/images/ 60 | - assets/vectors/ 61 | - assets/fonts/ 62 | 63 | fonts: 64 | - family: Poppins 65 | fonts: 66 | - asset: assets/fonts/Poppins-Regular.ttf 67 | weight: 300 68 | - asset: assets/fonts/Poppins-Medium.ttf 69 | weight: 500 70 | - asset: assets/fonts/Poppins-SemiBold.ttf 71 | weight: 700 72 | - family: Cairo 73 | fonts: 74 | - asset: assets/fonts/Cairo-Regular.ttf 75 | weight: 300 76 | - asset: assets/fonts/Cairo-Medium.ttf 77 | weight: 500 78 | - asset: assets/fonts/Cairo-SemiBold.ttf 79 | weight: 900 80 | 81 | -------------------------------------------------------------------------------- /test/baseclient_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:get/get.dart' hide Response; 4 | import 'package:getx_skeleton/app/services/api_exceptions.dart'; 5 | import 'package:getx_skeleton/app/services/base_client.dart'; 6 | import 'package:getx_skeleton/config/translations/strings_enum.dart'; 7 | import 'package:http_mock_adapter/http_mock_adapter.dart'; 8 | 9 | 10 | /// the main point of the test is to make sure callbacks and function work 11 | /// as it should with returning the right values handling errors correctly 12 | /// and returning the right message for every different situation..etc 13 | /// tests for api call 14 | /// Successful (get,post,put,delete) requests 15 | /// bad request 16 | /// unauthorized 17 | /// url not found 18 | 19 | void main() { 20 | // adapter to mock dio 21 | final dioAdapter = DioAdapter(dio: BaseClient.dio); 22 | 23 | // dummy url 24 | String url = 'https://www.facebook.com/emadbeltaje'; 25 | 26 | 27 | group('success api cases', () { 28 | /// test successful GET request 29 | test('successful GET api call', () async { 30 | Response? response; 31 | ApiException? exception; 32 | 33 | // simulate successful get request 34 | dioAdapter.onGet(url, (server) { 35 | server.reply(200, {'test' : 'Passed ✅'}); 36 | }); 37 | 38 | // perform api call 39 | await BaseClient.safeApiCall( 40 | url, 41 | RequestType.get, 42 | onSuccess: (res) { 43 | response = res; 44 | }, 45 | onError: (e) { 46 | exception = e; 47 | } 48 | ); 49 | 50 | // in successful get request case we expect 3 things 51 | // 1- response must not be null 52 | // 2- exception must be null (bcz there was no error) and api made successfully 53 | // 3- status code must be 200 54 | expect(response, isNotNull,reason: 'api response must not be null'); 55 | expect(exception, isNull,reason: 'api error must be null'); 56 | expect(response?.statusCode, 200,reason: 'status code must be 200'); 57 | }); 58 | 59 | 60 | 61 | /// test successful POST request 62 | test('successful POST api call', () async { 63 | Response? response; 64 | ApiException? exception; 65 | 66 | // simulate successful post request 67 | dioAdapter.onPost(url, (server) { 68 | server.reply(201, {'test' : 'Passed ✅'}); 69 | }); 70 | 71 | // perform api request 72 | await BaseClient.safeApiCall( 73 | url, 74 | RequestType.post, 75 | onSuccess: (res) { 76 | response = res; 77 | }, 78 | onError: (e) { 79 | exception = e; 80 | } 81 | ); 82 | 83 | // in successful post case we expect 3 things 84 | // 1- response must not be null 85 | // 2- exception must be null (bcz there was no error) and api made successfully 86 | // 3- status code must be 200/201 87 | expect(response, isNotNull,reason: 'api response must not be null'); 88 | expect(exception, isNull,reason: 'api error must be null'); 89 | expect(response?.statusCode, anyOf([200,201]),reason: 'status code must be 200 or 201'); 90 | }); 91 | 92 | 93 | 94 | /// test successful PUT request 95 | test('successful PUT api call', () async { 96 | Response? response; 97 | ApiException? exception; 98 | 99 | // simulate successful get request 100 | dioAdapter.onPut(url, (server) { 101 | server.reply(201, {'test' : 'Passed ✅'}); 102 | }); 103 | 104 | // perform api request 105 | await BaseClient.safeApiCall( 106 | url, 107 | RequestType.put, 108 | onSuccess: (res) { 109 | response = res; 110 | }, 111 | onError: (e) { 112 | exception = e; 113 | } 114 | ); 115 | 116 | // in successful PUT case we expect 3 things 117 | // 1- response must not be null 118 | // 2- exception must be null (bcz there was no error) and api made successfully 119 | // 3- status code must be 200/201 120 | expect(response, isNotNull,reason: 'api response must not be null'); 121 | expect(exception, isNull,reason: 'api error must be null'); 122 | expect(response?.statusCode, anyOf([200,201]),reason: 'status code must be 200 or 201'); 123 | }); 124 | 125 | 126 | 127 | /// test successful delete request 128 | test('successful DELETE api call', () async { 129 | Response? response; 130 | ApiException? exception; 131 | 132 | // simulate successful get request 133 | dioAdapter.onDelete(url, (server) { 134 | server.reply(204, {'test' : 'Passed ✅'}); 135 | }); 136 | 137 | // perform api request 138 | await BaseClient.safeApiCall( 139 | url, 140 | RequestType.delete, 141 | onSuccess: (res) { 142 | response = res; 143 | }, 144 | onError: (e) { 145 | exception = e; 146 | } 147 | ); 148 | 149 | // in successful delete case we expect 3 things 150 | // 1- response must not be null 151 | // 2- exception must be null (bcz there was no error) and api made successfully 152 | // 3- status code must be 201/204 153 | expect(response, isNotNull,reason: 'api response must not be null'); 154 | expect(exception, isNull,reason: 'api error must be null'); 155 | expect(response?.statusCode, anyOf([200,204]),reason: 'status code must be 200 or 204'); 156 | }); 157 | }); 158 | 159 | 160 | group('fail api cases', () { 161 | /// 400 bad request test 162 | test('test bad request api call 400', () async { 163 | Response? response; 164 | ApiException? exception; 165 | 166 | // simulate successful get request 167 | String apiErrorMessage = 'Bad request'; 168 | dioAdapter.onPost(url, (server) { 169 | server.reply(400, { 170 | 'test' : 'Passed ✅', 171 | 'error': apiErrorMessage, 172 | }); 173 | }); 174 | 175 | // perform api request 176 | await BaseClient.safeApiCall( 177 | url, 178 | RequestType.post, 179 | onSuccess: (res) { 180 | response = res; 181 | }, 182 | onError: (e) { 183 | exception = e; 184 | } 185 | ); 186 | 187 | // in bad request we expect 3 things 188 | // 1- response must be null 189 | // 2- exception must not be null 190 | // 3- status code must be 400 191 | // 4- exception.toString() should be the message from api 192 | expect(response, isNull,reason: 'response must be null bcz onSuccess will not be triggered'); 193 | expect(exception, isNotNull,reason: 'api error must not be null'); 194 | expect(exception?.statusCode, 400,reason: 'status code must be 400'); 195 | expect(exception?.toString(), apiErrorMessage,reason: 'should take the error message from api'); 196 | }); 197 | 198 | 199 | 200 | /// test 401 un authorized request test 201 | test('test un authorized api request 401', () async { 202 | Response? response; 203 | ApiException? exception; 204 | 205 | // simulate successful get request 206 | String apiErrorMessage = 'Unauthorized Request'; 207 | dioAdapter.onPost(url, (server) { 208 | server.reply(401, { 209 | 'test' : 'Passed ✅', 210 | 'error' : apiErrorMessage, 211 | }); 212 | }); 213 | 214 | // perform api request 215 | await BaseClient.safeApiCall( 216 | url, 217 | RequestType.post, 218 | onSuccess: (res) { 219 | response = res; 220 | }, 221 | onError: (e) { 222 | exception = e; 223 | } 224 | ); 225 | 226 | // in un authorized request we expect 3 things 227 | // 1- response must be null 228 | // 2- exception must not be null 229 | // 3- status code must be 401 230 | // 4- exception.toString() should be the message from api 231 | expect(response, isNull,reason: 'response must be null bcz onSuccess will not be triggered'); 232 | expect(exception, isNotNull,reason: 'api error must not be null'); 233 | expect(exception?.statusCode, 401,reason: 'status code must be 401'); 234 | expect(exception?.toString(), apiErrorMessage,reason: 'should take the error message from api'); 235 | }); 236 | 237 | 238 | 239 | /// test url not found 404 240 | test('test url not found api call 404', () async { 241 | Response? response; 242 | ApiException? exception; 243 | 244 | // simulate successful get request 245 | dioAdapter.onPost(url, (server) { 246 | server.reply(404, null); 247 | }); 248 | 249 | // perform api request 250 | await BaseClient.safeApiCall( 251 | url, // we miss with the url 252 | RequestType.post, 253 | onSuccess: (res) { 254 | response = res; 255 | }, 256 | onError: (e) { 257 | exception = e; 258 | } 259 | ); 260 | 261 | // in url not found request we expect 3 things 262 | // 1- response must be null 263 | // 2- exception must not be null 264 | // 3- status code must be 404 265 | // 3- exception.toString() = 'url not found' 266 | expect(response, isNull,reason: 'response must be null bcz onSuccess will not be triggered'); 267 | expect(exception, isNotNull,reason: 'api exception must not be null'); 268 | expect(exception?.statusCode, 404,reason: 'status code must be 404'); 269 | expect(exception?.toString(), Strings.urlNotFound.tr,reason: 'message must be (url not found)'); 270 | }); 271 | 272 | 273 | /// test internal server error 274 | test('test internal server error 500', () async { 275 | Response? response; 276 | ApiException? exception; 277 | 278 | // simulate successful get request 279 | dioAdapter.onPost(url, (server) { 280 | server.reply(500, null); 281 | }); 282 | 283 | // perform api request 284 | await BaseClient.safeApiCall( 285 | url, // we miss with the url 286 | RequestType.post, 287 | onSuccess: (res) { 288 | response = res; 289 | }, 290 | onError: (e) { 291 | exception = e; 292 | } 293 | ); 294 | 295 | // in url not found request we expect 3 things 296 | // 1- response must be null 297 | // 2- exception must not be null 298 | // 3- status code must be 500 299 | // 4- status error 300 | expect(response, isNull,reason: 'response must be null bcz onSuccess will not be triggered'); 301 | expect(exception, isNotNull,reason: 'api exception must not be null'); 302 | expect(exception?.statusCode, 500,reason: 'status code must be 404'); 303 | expect(exception?.toString(), Strings.serverError.tr,reason: 'message must be (server error)'); 304 | }); 305 | }); 306 | } 307 | -------------------------------------------------------------------------------- /test/localization_service_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:getx_skeleton/app/data/local/my_shared_pref.dart'; 5 | import 'package:getx_skeleton/config/translations/localization_service.dart'; 6 | import 'package:getx_skeleton/config/translations/strings_enum.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | 9 | main() async { 10 | TestWidgetsFlutterBinding.ensureInitialized(); 11 | 12 | setUp(() { 13 | Get.testMode = true; 14 | }); 15 | 16 | // mock initial data 17 | Map values = {}; 18 | SharedPreferences.setMockInitialValues(values); 19 | 20 | await MySharedPref.init(); 21 | 22 | test('check if language is supported', (){ 23 | // check if English is supported 24 | bool isEnSupported = LocalizationService.isLanguageSupported('en'); 25 | expect(isEnSupported, true); 26 | 27 | // check if French supported 28 | bool isFrSupported = LocalizationService.isLanguageSupported('fr'); 29 | expect(isFrSupported, false); 30 | }); 31 | 32 | 33 | test('Check getting/updating current local', () async { 34 | await LocalizationService.updateLanguage('en'); 35 | Locale currentLocale = LocalizationService.getCurrentLocal(); 36 | expect(currentLocale.languageCode, 'en'); 37 | 38 | await LocalizationService.updateLanguage('ar'); 39 | Locale currentLocaleAfterUpdate = LocalizationService.getCurrentLocal(); 40 | expect(currentLocaleAfterUpdate.languageCode, 'ar'); 41 | }); 42 | 43 | 44 | test('Check if current language is English', () async { 45 | await LocalizationService.updateLanguage('en'); 46 | bool isCurrentLangIsEnglish = LocalizationService.getCurrentLocal().languageCode.contains('en'); 47 | expect(isCurrentLangIsEnglish, true); 48 | 49 | await LocalizationService.updateLanguage('ar'); 50 | bool isCurrentLangEnglishAfterUpdate = LocalizationService.getCurrentLocal().languageCode.contains('ar'); 51 | expect(isCurrentLangEnglishAfterUpdate, true); 52 | }); 53 | 54 | 55 | testWidgets('Check translation', (tester) async { 56 | Get.testMode = false; 57 | await tester.pumpWidget(GetMaterialApp( 58 | locale: MySharedPref.getCurrentLocal(), 59 | translations: LocalizationService.getInstance(), 60 | home: const Scaffold( 61 | body: Center(child: Text('Testing..')), 62 | ), 63 | )); 64 | await tester.pumpAndSettle(); 65 | 66 | // make language english and test the word value 67 | await LocalizationService.updateLanguage('en'); 68 | 69 | await tester.pumpAndSettle(); 70 | String helloWord = Strings.hello.tr; 71 | expect(helloWord, 'Hello!'); 72 | 73 | // make language english and test the word value 74 | await LocalizationService.updateLanguage('ar'); 75 | await tester.pumpAndSettle(); 76 | String helloWordAfterChangingLanguage = Strings.hello.tr; 77 | expect(helloWordAfterChangingLanguage, 'مرحباً!'); 78 | }); 79 | } -------------------------------------------------------------------------------- /test/my_hive_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:getx_skeleton/app/data/local/my_hive.dart'; 3 | import 'package:getx_skeleton/app/data/models/user_model.dart'; 4 | 5 | 6 | import 'dart:io'; 7 | import 'dart:math'; 8 | 9 | import 'package:path/path.dart' as path; 10 | 11 | // temp path to initialize hive somewhere 12 | String _tempPath = path.join(Directory.current.path, '.dart_tool', 'test', 'tmp'); 13 | 14 | /// Returns a temporary directory in which a Hive can be initialized 15 | Future getTempDir() async { 16 | var name = Random().nextInt(pow(2, 32) as int); 17 | var dir = Directory(path.join(_tempPath, '${name}_tmp')); 18 | 19 | if (await dir.exists()) await dir.delete(recursive: true); 20 | 21 | await dir.create(recursive: true); 22 | return dir; 23 | } 24 | 25 | main() async { 26 | TestWidgetsFlutterBinding.ensureInitialized(); 27 | 28 | // temp path to initialize hive 29 | // !) in case you wonder why we need path with testing only 30 | // !) its because hive use path_provider which is platform plugin 31 | // !) but in test there is no platform (running device) so we must 32 | // !) use normal path 33 | String path = (await getTempDir()).path; 34 | 35 | // initialize hive 36 | await MyHive.init( 37 | registerAdapters: (hive) { 38 | hive.registerAdapter(UserModelAdapter()); 39 | }, 40 | testPath: path, // pass test path 41 | ); 42 | 43 | 44 | group('hive save, read, and delete test', () { 45 | test('write on hive', () async { 46 | // save user 47 | bool saved = await MyHive.saveUserToHive(UserModel.fromData(age: 23, phoneNumber: '+972595195630', username: 'Emad Beltaje')); 48 | expect(saved, true); 49 | }); 50 | 51 | test('read from hive', () async { 52 | // get user and test if saving worked fine 53 | UserModel? user = MyHive.getCurrentUser(); 54 | expect(user, isNotNull,reason: 'user must not be null'); 55 | }); 56 | 57 | 58 | test('delete from hive', () async { 59 | // delete user and check if delete work fine 60 | bool deleted = await MyHive.deleteCurrentUser(); 61 | expect(deleted, true); 62 | 63 | // load user after delete and check (ofc it should be deleted) 64 | UserModel? userAfterDelete = MyHive.getCurrentUser(); 65 | expect(userAfterDelete, isNull,reason: 'user must be null bcz we deleted it'); 66 | }); 67 | }); 68 | } -------------------------------------------------------------------------------- /test/sharedpref_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:getx_skeleton/app/data/local/my_shared_pref.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | /// test shared pref (read & write) 6 | 7 | 8 | Future main() async { 9 | TestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | // mock initial data 12 | Map values = {}; 13 | SharedPreferences.setMockInitialValues(values); 14 | 15 | await MySharedPref.init(); 16 | 17 | test('clear all the data from storage',() async { 18 | // set new token in shared pref 19 | await MySharedPref.setFcmToken('token'); 20 | 21 | // check if the token stored 22 | String? token = MySharedPref.getFcmToken(); 23 | 24 | // token must be set correctly 25 | expect(token, isNotNull); 26 | 27 | // clear all data 28 | await MySharedPref.clear(); 29 | 30 | // token must be null now after clearing data 31 | String? tokenAfterClearing = MySharedPref.getFcmToken(); 32 | 33 | // token must be null 34 | expect(tokenAfterClearing, isNull); 35 | }); 36 | 37 | 38 | test('test read and write', () async { 39 | // set theme is light to false (write operation) 40 | await MySharedPref.setThemeIsLight(false); 41 | 42 | // get the value and test if the saving went fine (read operation) 43 | bool themeIsLight = MySharedPref.getThemeIsLight(); 44 | 45 | // make sure write and read went fine 46 | expect(themeIsLight, false); 47 | }); 48 | } --------------------------------------------------------------------------------