├── .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 |
4 |
--------------------------------------------------------------------------------
/assets/vectors/alarm.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/assets/vectors/calendar.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/assets/vectors/language.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/vectors/moon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/vectors/profile.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/vectors/sun.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/vectors/tasks.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/vectors/vocation.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 | }
--------------------------------------------------------------------------------