├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── hellohasan │ │ │ │ └── flutter_getx_template │ │ │ │ └── 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.properties └── settings.gradle ├── fonts ├── Roboto-Bold.ttf ├── Roboto-Italic.ttf ├── Roboto-Light.ttf ├── Roboto-Medium.ttf ├── Roboto-Regular.ttf └── Roboto-Thin.ttf ├── images ├── arrow_forward.svg ├── ic_favorite.svg ├── ic_font_size.svg ├── ic_fork.svg ├── ic_home.svg ├── ic_language.svg ├── ic_settings.svg ├── ic_test.png ├── ic_theme.png ├── launcher_icon.png ├── test.jpg └── test_jpeg.jpeg ├── 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 │ │ ├── dev.xcscheme │ │ └── prod.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 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-Debug.plist │ ├── Info-Release.plist │ └── Runner-Bridging-Header.h ├── l10n.yaml ├── lib ├── app │ ├── bindings │ │ ├── initial_binding.dart │ │ ├── local_source_bindings.dart │ │ ├── remote_source_bindings.dart │ │ └── repository_bindings.dart │ ├── core │ │ ├── base │ │ │ ├── base_controller.dart │ │ │ ├── base_remote_source.dart │ │ │ ├── base_view.dart │ │ │ ├── base_widget_mixin.dart │ │ │ └── paging_controller.dart │ │ ├── model │ │ │ ├── github_search_query_param.dart │ │ │ ├── page_state.dart │ │ │ └── page_status.dart │ │ ├── utils │ │ │ └── debouncer.dart │ │ ├── values │ │ │ ├── app_colors.dart │ │ │ ├── app_values.dart │ │ │ └── text_styles.dart │ │ └── widget │ │ │ ├── app_bar_title.dart │ │ │ ├── asset_image_view.dart │ │ │ ├── custom_app_bar.dart │ │ │ ├── elevated_container.dart │ │ │ ├── icon_text_widgets.dart │ │ │ ├── loading.dart │ │ │ ├── paging_view.dart │ │ │ └── ripple.dart │ ├── data │ │ ├── local │ │ │ ├── db │ │ │ │ └── db.txt │ │ │ └── preference │ │ │ │ ├── preference_manager.dart │ │ │ │ └── preference_manager_impl.dart │ │ ├── model │ │ │ └── github_project_search_response.dart │ │ ├── remote │ │ │ ├── github_remote_data_source.dart │ │ │ └── github_remote_data_source_impl.dart │ │ └── repository │ │ │ ├── github_repository.dart │ │ │ └── github_repository_impl.dart │ ├── modules │ │ ├── favorite │ │ │ ├── bindings │ │ │ │ └── favorite_binding.dart │ │ │ ├── controllers │ │ │ │ └── favorite_controller.dart │ │ │ └── views │ │ │ │ └── favorite_view.dart │ │ ├── home │ │ │ ├── bindings │ │ │ │ └── home_binding.dart │ │ │ ├── controllers │ │ │ │ └── home_controller.dart │ │ │ ├── model │ │ │ │ └── github_project_ui_data.dart │ │ │ ├── views │ │ │ │ └── home_view.dart │ │ │ └── widget │ │ │ │ └── item_github_project.dart │ │ ├── main │ │ │ ├── bindings │ │ │ │ └── main_binding.dart │ │ │ ├── controllers │ │ │ │ ├── bottom_nav_controller.dart │ │ │ │ └── main_controller.dart │ │ │ ├── model │ │ │ │ ├── menu_code.dart │ │ │ │ └── menu_item.dart │ │ │ └── views │ │ │ │ ├── bottom_nav_bar.dart │ │ │ │ └── main_view.dart │ │ ├── other │ │ │ ├── bindings │ │ │ │ └── other_binding.dart │ │ │ ├── controllers │ │ │ │ └── other_controller.dart │ │ │ └── views │ │ │ │ └── other_view.dart │ │ ├── project_details │ │ │ ├── bindings │ │ │ │ └── project_details_binding.dart │ │ │ ├── controllers │ │ │ │ └── project_details_controller.dart │ │ │ └── views │ │ │ │ └── project_details_view.dart │ │ └── settings │ │ │ ├── bindings │ │ │ └── settings_binding.dart │ │ │ ├── controllers │ │ │ └── settings_controller.dart │ │ │ ├── views │ │ │ └── settings_view.dart │ │ │ └── widgets │ │ │ └── item_settings_widgets.dart │ ├── my_app.dart │ ├── network │ │ ├── dio_provider.dart │ │ ├── dio_request_retrier.dart │ │ ├── error_handlers.dart │ │ ├── exceptions │ │ │ ├── api_exception.dart │ │ │ ├── app_exception.dart │ │ │ ├── base_api_exception.dart │ │ │ ├── base_exception.dart │ │ │ ├── json_format_exception.dart │ │ │ ├── network_exception.dart │ │ │ ├── not_found_exception.dart │ │ │ ├── service_unavailable_exception.dart │ │ │ ├── timeout_exception.dart │ │ │ └── unauthorize_exception.dart │ │ ├── pretty_dio_logger.dart │ │ └── request_headers.dart │ └── routes │ │ ├── app_pages.dart │ │ └── app_routes.dart ├── flavors │ ├── build_config.dart │ ├── env_config.dart │ └── environment.dart ├── l10n │ ├── app_bn.arb │ └── app_en.arb ├── main_dev.dart └── main_prod.dart ├── pubspec.lock ├── pubspec.yaml ├── pull_request_template.md ├── readme_configuration_guideline.md ├── repo_data ├── flutter_getx_template_1.png ├── flutter_getx_template_2.png ├── flutter_getx_template_3.png ├── flutter_getx_template_4.png └── mvvm_flow.png ├── test └── widget_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png └── Icon-512.png ├── index.html └── manifest.json /.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 | # VS Code 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | **/ios/Flutter/.last_build_id 24 | .dart_tool/ 25 | .flutter-plugins 26 | .flutter-plugins-dependencies 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Web related 33 | lib/generated_plugin_registrant.dart 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android related 42 | **/android/**/gradle-wrapper.jar 43 | **/android/.gradle 44 | **/android/captures/ 45 | **/android/gradlew 46 | **/android/gradlew.bat 47 | **/android/local.properties 48 | **/android/**/GeneratedPluginRegistrant.java 49 | 50 | # iOS/XCode related 51 | **/ios/**/*.mode1v3 52 | **/ios/**/*.mode2v3 53 | **/ios/**/*.moved-aside 54 | **/ios/**/*.pbxuser 55 | **/ios/**/*.perspectivev3 56 | **/ios/**/*sync/ 57 | **/ios/**/.sconsign.dblite 58 | **/ios/**/.tags* 59 | **/ios/**/.vagrant/ 60 | **/ios/**/DerivedData/ 61 | **/ios/**/Icon? 62 | **/ios/**/Pods/ 63 | **/ios/**/.symlinks/ 64 | **/ios/**/profile 65 | **/ios/**/xcuserdata 66 | **/ios/.generated/ 67 | **/ios/Flutter/App.framework 68 | **/ios/Flutter/Flutter.framework 69 | **/ios/Flutter/Generated.xcconfig 70 | **/ios/Flutter/app.flx 71 | **/ios/Flutter/app.zip 72 | **/ios/Flutter/flutter_assets/ 73 | **/ios/ServiceDefinitions.json 74 | **/ios/Runner/GeneratedPluginRegistrant.* 75 | 76 | ### Android ### 77 | # Built application files 78 | *.apk 79 | *.aar 80 | *.ap_ 81 | *.aab 82 | 83 | # Files for the ART/Dalvik VM 84 | *.dex 85 | 86 | # Generated files 87 | bin/ 88 | gen/ 89 | out/ 90 | release/ 91 | 92 | # Gradle files 93 | .gradle/ 94 | build/ 95 | 96 | # Local configuration file (sdk path, etc) 97 | local.properties 98 | 99 | # Flutter version manager related 100 | .fvm/ 101 | .fvm/* 102 | 103 | *.env -------------------------------------------------------------------------------- /.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: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hasan Abdullah 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter GetX Template (GetX, Dio, MVVM) 2 | 3 | This Flutter Template using [GetX](https://pub.dev/packages/get) package for State management, routing and Dependency Injection (bindings). We are using [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) (Model View ViewModel) architectural pattern here. For network call we are using [Dio](https://pub.dev/packages/dio) package. We followed the recommended folder structure of GetX and used [Get CLI](https://pub.dev/packages/get_cli) command line tool for creating the folder structure. 4 | 5 | # Architecture of this project: MVVM 6 | 7 | MVVM (Model View ViewModel) is one of the most popular architectural pattern for Android App development. Basically 8 | this pattern separates User interface from business-logic and data-logic. So that it's divided into three layers: Model layer, 9 | View layer and View model layer. Let's explore it more deeply. 10 | 11 | ![mvvm](https://user-images.githubusercontent.com/3769029/137336079-1f3384d0-b9d6-4462-a2c4-4a3d2cc77e8a.png) 12 | 13 | ViewModel: At first let's talk about ViewModel. Actually view model is a controller where we 14 | implement our business logics. It receives the data from the model and process the data according to 15 | business logic and pushed into the live data observers which is observing by view. 16 | 17 | View: View is the collections of widgets like Text, Image, Dropdown etc. Which will be displayed 18 | to the users. Even it controls the user input. When it needs any data it command the view model (In this project it's controller) 19 | for data and observe the response. Till then it may display a loader to the user. 20 | 21 | Model: Model is basically backend logic. It controls the data source. 22 | 23 | # To configure and run this project [check here](readme_configuration_guideline.md) 24 | You will find at above link step by step instructions with screenshots. 25 | 26 | # run this project by command line 27 | Dev: `flutter run --flavor dev lib/main_dev.dart` 28 | 29 | Prod: `flutter run --flavor prod lib/main_prod.dart` 30 | 31 | # How to update app information and continue development for your own project? 32 | 33 | 1. Rename root folder name 34 | 2. Update project name and description from pubspec.yaml. 35 | 3. Update app launcher name and icon. [Reference](https://medium.com/@vaibhavi.rana99/change-application-name-and-icon-in-flutter-bebbec297c57) 36 | 4. Update your app's package name by [running this command](https://pub.dev/packages/change_app_package_name): 37 | 38 | `flutter pub run change_app_package_name:main your_package_name` 39 | 40 | # How was this project developed? 41 | - Run [get cli](https://pub.dev/packages/get_cli) command to create project in the required directory: `get create project` 42 | - Create `main_view` by running this command: `get create page:main` and so on... 43 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | exclude: [ ios/**, android/**, test/** ] 5 | plugins: 6 | - dart_code_metrics 7 | 8 | errors: 9 | empty_constructor_bodies: error 10 | missing_required_param: error 11 | always_use_package_imports: error 12 | avoid_types_as_parameter_names: error 13 | close_sinks: error 14 | unnecessary_statements: warning 15 | non_constant_identifier_names: warning 16 | 17 | linter: 18 | rules: 19 | camel_case_types: true 20 | # prefer_double_quotes: true 21 | avoid_empty_else: true 22 | constant_identifier_names: false 23 | use_key_in_widget_constructors: false 24 | 25 | dart_code_metrics: 26 | anti-patterns: 27 | - long-method 28 | - long-parameter-list 29 | metrics: 30 | cyclomatic-complexity: 20 31 | maximum-nesting-level: 5 32 | number-of-parameters: 8 33 | source-lines-of-code: 50 34 | metrics-exclude: 35 | - test/** 36 | - ios/** 37 | - android/** 38 | rules: 39 | newline-before-return: 40 | severity: error 41 | no-boolean-literal-compare: 42 | severity: warning 43 | no-empty-block: 44 | severity: error 45 | # avoid-returning-widgets: 46 | # severity: none 47 | prefer-conditional-expressions: 48 | severity: warning 49 | no-magic-number: 50 | severity: error 51 | allowed: [ 0,1,2,3,4,5,6,7,8,9 ] 52 | # avoid-unused-parameters: 53 | # severity: warning 54 | 55 | 56 | 57 | # severity -> none, style, performance, warning, error. -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | import java.util.regex.Matcher 2 | import java.util.regex.Pattern 3 | 4 | def localProperties = loadPropertiesFile('local.properties') 5 | def keystoreProperties = loadPropertiesFile('key.properties') 6 | 7 | def flutterRoot = localProperties.getProperty('flutter.sdk') 8 | if (flutterRoot == null) { 9 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 10 | } 11 | 12 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 13 | if (flutterVersionCode == null) { 14 | flutterVersionCode = '1' 15 | } 16 | 17 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 18 | if (flutterVersionName == null) { 19 | flutterVersionName = '1.0' 20 | } 21 | 22 | def loadPropertiesFile(String fileName) { 23 | println(fileName) 24 | def properties = new Properties() 25 | def propertiesFile = rootProject.file(fileName) 26 | if (propertiesFile.exists()) { 27 | propertiesFile.withReader('UTF-8') { reader -> 28 | properties.load(reader) 29 | } 30 | } 31 | return properties 32 | } 33 | 34 | def getCurrentFlavor() { 35 | Gradle gradle = getGradle() 36 | String tskReqStr = gradle.getStartParameter().getTaskRequests().toString() 37 | 38 | Pattern pattern 39 | println(tskReqStr) 40 | if( tskReqStr.contains( "assemble" ) ) 41 | pattern = Pattern.compile("assemble(\\w+)(Release|Debug)") 42 | else if( tskReqStr.contains("bundle") ) 43 | pattern = Pattern.compile("bundle(\\w+)(Release|Debug)") 44 | else 45 | pattern = Pattern.compile("generate(\\w+)(Release|Debug)") 46 | 47 | Matcher matcher = pattern.matcher( tskReqStr ) 48 | 49 | if( matcher.find() ) 50 | return matcher.group(1).toLowerCase() 51 | else 52 | { 53 | println "NO MATCH FOUND" 54 | return "" 55 | } 56 | } 57 | 58 | apply plugin: 'com.android.application' 59 | apply plugin: 'kotlin-android' 60 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 61 | 62 | android { 63 | 64 | sourceSets { 65 | main.java.srcDirs += 'src/main/kotlin' 66 | } 67 | 68 | defaultConfig { 69 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 70 | applicationId "com.hellohasan.flutter_getx_template" 71 | minSdkVersion 23 72 | targetSdkVersion 33 73 | compileSdk 33 74 | versionCode flutterVersionCode.toInteger() 75 | versionName flutterVersionName 76 | } 77 | 78 | lintOptions { 79 | checkReleaseBuilds false 80 | } 81 | 82 | signingConfigs { 83 | release { 84 | keyAlias keystoreProperties['keyAlias'] 85 | keyPassword keystoreProperties['password'] 86 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 87 | storePassword keystoreProperties['password'] 88 | } 89 | } 90 | 91 | buildTypes { 92 | release { 93 | signingConfig signingConfigs.release 94 | } 95 | } 96 | 97 | flavorDimensions "default" 98 | productFlavors { 99 | dev { 100 | dimension "default" 101 | applicationIdSuffix ".dev" 102 | } 103 | prod { 104 | dimension "default" 105 | } 106 | } 107 | } 108 | 109 | flutter { 110 | source '../..' 111 | target "lib/main_" + getCurrentFlavor() + ".dart" 112 | } 113 | 114 | dependencies { 115 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 116 | } 117 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 9 | 17 | 21 | 25 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/hellohasan/flutter_getx_template/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.hellohasan.flutter_getx_template 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 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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.9.0' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.0.4' 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 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | tasks.register("clean", Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Dec 12 12:34:09 BDT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /images/arrow_forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /images/ic_favorite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /images/ic_font_size.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /images/ic_fork.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /images/ic_home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 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 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /images/ic_language.svg: -------------------------------------------------------------------------------- 1 | Language -------------------------------------------------------------------------------- /images/ic_settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /images/ic_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/images/ic_test.png -------------------------------------------------------------------------------- /images/ic_theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/images/ic_theme.png -------------------------------------------------------------------------------- /images/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/images/launcher_icon.png -------------------------------------------------------------------------------- /images/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/images/test.jpg -------------------------------------------------------------------------------- /images/test_jpeg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/images/test_jpeg.jpeg -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /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 | 11.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, '11.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 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - fluttertoast (0.0.2): 4 | - Flutter 5 | - Toast 6 | - FMDB (2.7.5): 7 | - FMDB/standard (= 2.7.5) 8 | - FMDB/standard (2.7.5) 9 | - path_provider_foundation (0.0.1): 10 | - Flutter 11 | - FlutterMacOS 12 | - shared_preferences_foundation (0.0.1): 13 | - Flutter 14 | - FlutterMacOS 15 | - sqflite (0.0.3): 16 | - Flutter 17 | - FMDB (>= 2.7.5) 18 | - Toast (4.0.0) 19 | 20 | DEPENDENCIES: 21 | - Flutter (from `Flutter`) 22 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 23 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 24 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 25 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 26 | 27 | SPEC REPOS: 28 | trunk: 29 | - FMDB 30 | - Toast 31 | 32 | EXTERNAL SOURCES: 33 | Flutter: 34 | :path: Flutter 35 | fluttertoast: 36 | :path: ".symlinks/plugins/fluttertoast/ios" 37 | path_provider_foundation: 38 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 39 | shared_preferences_foundation: 40 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 41 | sqflite: 42 | :path: ".symlinks/plugins/sqflite/ios" 43 | 44 | SPEC CHECKSUMS: 45 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 46 | fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c 47 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 48 | path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 49 | shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 50 | sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a 51 | Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 52 | 53 | PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d 54 | 55 | COCOAPODS: 1.12.1 56 | -------------------------------------------------------------------------------- /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 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/dev.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/prod.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /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 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/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 | -------------------------------------------------------------------------------- /ios/Runner/Info-Debug.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_getx_template 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSBonjourServices 26 | 27 | _dartobservatory._tcp 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Info-Release.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_getx_template 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart -------------------------------------------------------------------------------- /lib/app/bindings/initial_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'local_source_bindings.dart'; 4 | import 'remote_source_bindings.dart'; 5 | import 'repository_bindings.dart'; 6 | 7 | class InitialBinding implements Bindings { 8 | @override 9 | void dependencies() { 10 | RepositoryBindings().dependencies(); 11 | RemoteSourceBindings().dependencies(); 12 | LocalSourceBindings().dependencies(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/app/bindings/local_source_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/data/local/preference/preference_manager.dart'; 4 | import '/app/data/local/preference/preference_manager_impl.dart'; 5 | 6 | class LocalSourceBindings implements Bindings { 7 | @override 8 | void dependencies() { 9 | Get.lazyPut( 10 | () => PreferenceManagerImpl(), 11 | tag: (PreferenceManager).toString(), 12 | fenix: true, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/app/bindings/remote_source_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/data/remote/github_remote_data_source.dart'; 4 | import '/app/data/remote/github_remote_data_source_impl.dart'; 5 | 6 | class RemoteSourceBindings implements Bindings { 7 | @override 8 | void dependencies() { 9 | Get.lazyPut( 10 | () => GithubRemoteDataSourceImpl(), 11 | tag: (GithubRemoteDataSource).toString(), 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/app/bindings/repository_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/data/repository/github_repository.dart'; 4 | import '/app/data/repository/github_repository_impl.dart'; 5 | 6 | class RepositoryBindings implements Bindings { 7 | @override 8 | void dependencies() { 9 | Get.lazyPut( 10 | () => GithubRepositoryImpl(), 11 | tag: (GithubRepository).toString(), 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/app/core/base/base_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:logger/logger.dart'; 6 | 7 | import '/app/core/model/page_state.dart'; 8 | import '/app/network/exceptions/api_exception.dart'; 9 | import '/app/network/exceptions/app_exception.dart'; 10 | import '/app/network/exceptions/json_format_exception.dart'; 11 | import '/app/network/exceptions/network_exception.dart'; 12 | import '/app/network/exceptions/not_found_exception.dart'; 13 | import '/app/network/exceptions/service_unavailable_exception.dart'; 14 | import '/app/network/exceptions/unauthorize_exception.dart'; 15 | import '/flavors/build_config.dart'; 16 | 17 | abstract class BaseController extends GetxController { 18 | final Logger logger = BuildConfig.instance.config.logger; 19 | 20 | AppLocalizations get appLocalization => AppLocalizations.of(Get.context!)!; 21 | 22 | final logoutController = false.obs; 23 | 24 | //Reload the page 25 | final _refreshController = false.obs; 26 | 27 | refreshPage(bool refresh) => _refreshController(refresh); 28 | 29 | //Controls page state 30 | final _pageSateController = PageState.DEFAULT.obs; 31 | 32 | PageState get pageState => _pageSateController.value; 33 | 34 | updatePageState(PageState state) => _pageSateController(state); 35 | 36 | resetPageState() => _pageSateController(PageState.DEFAULT); 37 | 38 | showLoading() => updatePageState(PageState.LOADING); 39 | 40 | hideLoading() => resetPageState(); 41 | 42 | final _messageController = ''.obs; 43 | 44 | String get message => _messageController.value; 45 | 46 | showMessage(String msg) => _messageController(msg); 47 | 48 | final _errorMessageController = ''.obs; 49 | 50 | String get errorMessage => _errorMessageController.value; 51 | 52 | showErrorMessage(String msg) { 53 | _errorMessageController(msg); 54 | } 55 | 56 | final _successMessageController = ''.obs; 57 | 58 | String get successMessage => _messageController.value; 59 | 60 | showSuccessMessage(String msg) => _successMessageController(msg); 61 | 62 | // ignore: long-parameter-list 63 | dynamic callDataService( 64 | Future future, { 65 | Function(Exception exception)? onError, 66 | Function(T response)? onSuccess, 67 | Function? onStart, 68 | Function? onComplete, 69 | }) async { 70 | Exception? _exception; 71 | 72 | onStart == null ? showLoading() : onStart(); 73 | 74 | try { 75 | final T response = await future; 76 | 77 | if (onSuccess != null) onSuccess(response); 78 | 79 | onComplete == null ? hideLoading() : onComplete(); 80 | 81 | return response; 82 | } on ServiceUnavailableException catch (exception) { 83 | _exception = exception; 84 | showErrorMessage(exception.message); 85 | } on UnauthorizedException catch (exception) { 86 | _exception = exception; 87 | showErrorMessage(exception.message); 88 | } on TimeoutException catch (exception) { 89 | _exception = exception; 90 | showErrorMessage(exception.message ?? 'Timeout exception'); 91 | } on NetworkException catch (exception) { 92 | _exception = exception; 93 | showErrorMessage(exception.message); 94 | } on JsonFormatException catch (exception) { 95 | _exception = exception; 96 | showErrorMessage(exception.message); 97 | } on NotFoundException catch (exception) { 98 | _exception = exception; 99 | showErrorMessage(exception.message); 100 | } on ApiException catch (exception) { 101 | _exception = exception; 102 | } on AppException catch (exception) { 103 | _exception = exception; 104 | showErrorMessage(exception.message); 105 | } catch (error) { 106 | _exception = AppException(message: "$error"); 107 | logger.e("Controller>>>>>> error $error"); 108 | } 109 | 110 | if (onError != null) onError(_exception); 111 | 112 | onComplete == null ? hideLoading() : onComplete(); 113 | } 114 | 115 | @override 116 | void onClose() { 117 | _messageController.close(); 118 | _refreshController.close(); 119 | _pageSateController.close(); 120 | super.onClose(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/app/core/base/base_remote_source.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:get/get_connect/http/src/status/http_status.dart'; 3 | 4 | import '/app/network/dio_provider.dart'; 5 | import '/app/network/error_handlers.dart'; 6 | import '/app/network/exceptions/base_exception.dart'; 7 | import '/flavors/build_config.dart'; 8 | 9 | abstract class BaseRemoteSource { 10 | Dio get dioClient => DioProvider.dioWithHeaderToken; 11 | 12 | final logger = BuildConfig.instance.config.logger; 13 | 14 | Future> callApiWithErrorParser(Future> api) async { 15 | try { 16 | Response response = await api; 17 | 18 | if (response.statusCode != HttpStatus.ok || 19 | (response.data as Map)['statusCode'] != 20 | HttpStatus.ok) { 21 | // TODO 22 | } 23 | 24 | return response; 25 | } on DioError catch (dioError) { 26 | Exception exception = handleDioError(dioError); 27 | logger.e( 28 | "Throwing error from repository: >>>>>>> $exception : ${(exception as BaseException).message}"); 29 | throw exception; 30 | } catch (error) { 31 | logger.e("Generic error: >>>>>>> $error"); 32 | 33 | if (error is BaseException) { 34 | rethrow; 35 | } 36 | 37 | throw handleError("$error"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/app/core/base/base_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 4 | import 'package:fluttertoast/fluttertoast.dart'; 5 | import 'package:get/get.dart'; 6 | import 'package:logger/logger.dart'; 7 | 8 | import '/app/core/base/base_controller.dart'; 9 | import '/app/core/model/page_state.dart'; 10 | import '/app/core/values/app_colors.dart'; 11 | import '/app/core/widget/loading.dart'; 12 | import '/flavors/build_config.dart'; 13 | 14 | abstract class BaseView 15 | extends GetView { 16 | final GlobalKey globalKey = GlobalKey(); 17 | 18 | AppLocalizations get appLocalization => AppLocalizations.of(Get.context!)!; 19 | 20 | final Logger logger = BuildConfig.instance.config.logger; 21 | 22 | Widget body(BuildContext context); 23 | 24 | PreferredSizeWidget? appBar(BuildContext context); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GestureDetector( 29 | child: Stack( 30 | children: [ 31 | annotatedRegion(context), 32 | Obx(() => controller.pageState == PageState.LOADING 33 | ? _showLoading() 34 | : Container()), 35 | Obx(() => controller.errorMessage.isNotEmpty 36 | ? showErrorSnackBar(controller.errorMessage) 37 | : Container()), 38 | Container(), 39 | ], 40 | ), 41 | ); 42 | } 43 | 44 | Widget annotatedRegion(BuildContext context) { 45 | return AnnotatedRegion( 46 | value: SystemUiOverlayStyle( 47 | //Status bar color for android 48 | statusBarColor: statusBarColor(), 49 | statusBarIconBrightness: Brightness.dark, 50 | ), 51 | child: Material( 52 | color: Colors.transparent, 53 | child: pageScaffold(context), 54 | ), 55 | ); 56 | } 57 | 58 | Widget pageScaffold(BuildContext context) { 59 | return Scaffold( 60 | //sets ios status bar color 61 | backgroundColor: pageBackgroundColor(), 62 | key: globalKey, 63 | appBar: appBar(context), 64 | floatingActionButton: floatingActionButton(), 65 | body: pageContent(context), 66 | bottomNavigationBar: bottomNavigationBar(), 67 | drawer: drawer(), 68 | ); 69 | } 70 | 71 | Widget pageContent(BuildContext context) { 72 | return SafeArea( 73 | child: body(context), 74 | ); 75 | } 76 | 77 | Widget showErrorSnackBar(String message) { 78 | final snackBar = SnackBar(content: Text(message)); 79 | WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { 80 | ScaffoldMessenger.of(Get.context!).showSnackBar(snackBar); 81 | }); 82 | 83 | return Container(); 84 | } 85 | 86 | void showToast(String message) { 87 | Fluttertoast.showToast( 88 | msg: message, 89 | toastLength: Toast.LENGTH_SHORT, 90 | timeInSecForIosWeb: 1 91 | ); 92 | } 93 | 94 | Color pageBackgroundColor() { 95 | return AppColors.pageBackground; 96 | } 97 | 98 | Color statusBarColor() { 99 | return AppColors.pageBackground; 100 | } 101 | 102 | Widget? floatingActionButton() { 103 | return null; 104 | } 105 | 106 | Widget? bottomNavigationBar() { 107 | return null; 108 | } 109 | 110 | Widget? drawer() { 111 | return null; 112 | } 113 | 114 | Widget _showLoading() { 115 | return const Loading(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/app/core/base/base_widget_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:logger/logger.dart'; 5 | 6 | import '/flavors/build_config.dart'; 7 | 8 | mixin BaseWidgetMixin on StatelessWidget { 9 | AppLocalizations get appLocalization => AppLocalizations.of(Get.context!)!; 10 | final Logger logger = BuildConfig.instance.config.logger; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | child: body(context), 16 | ); 17 | } 18 | 19 | Widget body(BuildContext context); 20 | } 21 | -------------------------------------------------------------------------------- /lib/app/core/base/paging_controller.dart: -------------------------------------------------------------------------------- 1 | import '/app/core/values/app_values.dart'; 2 | 3 | class PagingController { 4 | List listItems = []; 5 | int pageNumber = AppValues.defaultPageNumber; 6 | bool isLoadingPage = false; 7 | bool endOfList = false; 8 | 9 | initRefresh() { 10 | listItems = []; 11 | pageNumber = AppValues.defaultPageNumber; 12 | isLoadingPage = false; 13 | endOfList = false; 14 | } 15 | 16 | bool canLoadNextPage() { 17 | return !isLoadingPage && !endOfList; 18 | } 19 | 20 | appendPage(List items) { 21 | listItems.addAll(items); 22 | pageNumber++; 23 | } 24 | 25 | appendLastPage(List items) { 26 | listItems.addAll(items); 27 | endOfList = true; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/app/core/model/github_search_query_param.dart: -------------------------------------------------------------------------------- 1 | import '/app/core/values/app_values.dart'; 2 | 3 | class GithubSearchQueryParam { 4 | String searchKeyWord; 5 | int perPage; 6 | int pageNumber; 7 | 8 | GithubSearchQueryParam({ 9 | required this.searchKeyWord, 10 | this.perPage = AppValues.defaultPageSize, 11 | this.pageNumber = AppValues.defaultPageNumber, 12 | }); 13 | 14 | Map toJson() { 15 | final Map data = {}; 16 | data['q'] = searchKeyWord; 17 | data['per_page'] = perPage; 18 | data['page'] = pageNumber; 19 | 20 | return data; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/core/model/page_state.dart: -------------------------------------------------------------------------------- 1 | enum PageState { 2 | DEFAULT, 3 | LOADING, 4 | SUCCESS, 5 | FAILED, 6 | UPDATED, 7 | CREATED, 8 | NO_INTERNET, 9 | MESSAGE, 10 | UNAUTHORIZED, 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/core/model/page_status.dart: -------------------------------------------------------------------------------- 1 | import '/app/core/model/page_state.dart'; 2 | 3 | class PageStatus { 4 | final bool isSuccess; 5 | final String message; 6 | final String title; 7 | final String nextRoute; 8 | final PageState pageState; 9 | 10 | PageStatus({ 11 | this.isSuccess = false, 12 | this.title = "", 13 | this.message = "", 14 | this.nextRoute = "", 15 | this.pageState = PageState.DEFAULT, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/core/utils/debouncer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '/app/core/values/app_values.dart'; 6 | 7 | class Debouncer { 8 | final int milliseconds; 9 | VoidCallback? action; 10 | Timer? _timer; 11 | 12 | Debouncer({this.milliseconds = AppValues.defaultDebounceTimeInMilliSeconds}); 13 | 14 | run(VoidCallback action) { 15 | if (_timer != null) { 16 | _timer!.cancel(); 17 | } 18 | _timer = Timer(Duration(milliseconds: milliseconds), action); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/app/core/values/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class AppColors { 4 | static const Color pageBackground = Color(0xFFFAFBFD); 5 | static const Color statusBarColor = Color(0xFF38686A); 6 | static const Color appBarColor = Color(0xFF38686A); 7 | static const Color appBarIconColor = Color(0xFFFFFFFF); 8 | static const Color appBarTextColor = Color(0xFFFFFFFF); 9 | 10 | static const Color centerTextColor = Colors.grey; 11 | static const MaterialColor colorPrimarySwatch = Colors.cyan; 12 | static const Color colorPrimary = Color(0xFF38686A); 13 | static const Color colorAccent = Color(0xFF38686A); 14 | static const Color colorLightGreen = Color(0xFF00EFA7); 15 | static const Color colorWhite = Color(0xFFFFFFFF); 16 | static const Color lightGreyColor = Color(0xFFC4C4C4); 17 | static const Color errorColor = Color(0xFFAB0B0B); 18 | static const Color colorDark = Color(0xFF323232); 19 | 20 | static const Color buttonBgColor = colorPrimary; 21 | static const Color disabledButtonBgColor = Color(0xFFBFBFC0); 22 | static const Color defaultRippleColor = Color(0x0338686A); 23 | 24 | static const Color textColorPrimary = Color(0xFF323232); 25 | static const Color textColorSecondary = Color(0xFF9FA4B0); 26 | static const Color textColorTag = colorPrimary; 27 | static const Color textColorGreyLight = Color(0xFFABABAB); 28 | static const Color textColorGreyDark = Color(0xFF979797); 29 | static const Color textColorBlueGreyDark = Color(0xFF939699); 30 | static const Color textColorCyan = Color(0xFF38686A); 31 | static const Color textColorWhite = Color(0xFFFFFFFF); 32 | static Color searchFieldTextColor = const Color(0xFF323232).withOpacity(0.5); 33 | 34 | static const Color iconColorDefault = Colors.grey; 35 | 36 | static Color barrierColor = const Color(0xFF000000).withOpacity(0.5); 37 | 38 | static Color timelineDividerColor = const Color(0x5438686A); 39 | 40 | static const Color gradientStartColor = Colors.black87; 41 | static const Color gradientEndColor = Colors.transparent; 42 | static const Color silverAppBarOverlayColor = Color(0x80323232); 43 | 44 | static const Color switchActiveColor = colorPrimary; 45 | static const Color switchInactiveColor = Color(0xFFABABAB); 46 | static Color elevatedContainerColorOpacity = Colors.grey.withOpacity(0.5); 47 | static const Color suffixImageColor = Colors.grey; 48 | } 49 | -------------------------------------------------------------------------------- /lib/app/core/values/app_values.dart: -------------------------------------------------------------------------------- 1 | abstract class AppValues { 2 | static const double padding = 16; 3 | static const double padding_zero = 0; 4 | static const double halfPadding = 8; 5 | static const double smallPadding = 10; 6 | static const double extraSmallPadding = 6; 7 | static const double largePadding = 24; 8 | static const double extraLargePadding = 32; 9 | static const double padding_4 = 4; 10 | static const double padding_2 = 2; 11 | static const double padding_3 = 3; 12 | static const double buttonVerticalPadding = 12; 13 | 14 | static const double margin = 16; 15 | static const double margin_zero = 0; 16 | static const double smallMargin = 8; 17 | static const double extraSmallMargin = 6; 18 | static const double largeMargin = 24; 19 | static const double margin_40 = 40; 20 | static const double margin_32 = 32; 21 | static const double margin_18 = 18; 22 | static const double margin_2 = 2; 23 | static const double margin_4 = 4; 24 | static const double margin_6 = 6; 25 | static const double margin_12 = 12; 26 | static const double margin_10 = 10; 27 | static const double margin_30 = 30; 28 | static const double margin_20 = 20; 29 | static const double extraLargeMargin = 36; 30 | static const double marginBelowVerticalLine = 64; 31 | static const double extraLargeSpacing = 96; 32 | 33 | static const double radius = 16; 34 | static const double radius_zero = 0; 35 | static const double smallRadius = 8; 36 | static const double radius_6 = 6; 37 | static const double radius_12 = 12; 38 | static const double largeRadius = 24; 39 | static const double roundedButtonRadius = 24; 40 | static const double extraLargeRadius = 36; 41 | 42 | static const double elevation = 16; 43 | static const double smallElevation = 8; 44 | static const double extraSmallElevation = 4; 45 | static const double largeElevation = 24; 46 | 47 | static const double circularImageDefaultSize = 90; 48 | static const double circularImageSize_30 = 30; 49 | static const double circularImageDefaultBorderSize = 0; 50 | static const double circularImageDefaultElevation = 0; 51 | static const double momentThumbnailDefaultSize = 80; 52 | static const double momentSmallThumbnailDefaultSize = 32; 53 | static const double collectionThumbnailDefaultSize = 150; 54 | static const double defaultViewPortFraction = 0.9; 55 | static const int defaultAnimationDuration = 300; 56 | static const double listBottomEmptySpace = 200; 57 | static const double maxButtonWidth = 496; 58 | static const double stackedImageDefaultBorderSize = 4; 59 | static const double stackedImageDefaultSpaceFactor = 0.4; 60 | static const double stackedImageDefaultSize = 30; 61 | 62 | static const double iconDefaultSize = 24; 63 | static const double emoticonDefaultSize = 22; 64 | static const double iconSize_20 = 20; 65 | static const double iconSize_22 = 22; 66 | static const double iconSize_18 = 18; 67 | static const double iconSmallSize = 16; 68 | static const double iconSmallerSize = 12; 69 | static const double iconSize_14 = 14; 70 | static const double iconSize_28 = 28; 71 | static const double iconLargeSize = 36; 72 | static const double iconExtraLargerSize = 96; 73 | static const double appBarIconSize = 32; 74 | 75 | static const double customAppBarSize = 144.0; 76 | static const double collapsedAppBarSize = 70.0; 77 | 78 | static const int loggerLineLength = 120; 79 | static const int loggerErrorMethodCount = 8; 80 | static const int loggerMethodCount = 2; 81 | 82 | static const double fullViewPort = 1; 83 | static const double indicatorDefaultSize = 8; 84 | static const double indicatorShadowBlurRadius = 1; 85 | static const double indicatorShadowSpreadRadius = 0; 86 | static const double appbarActionRippleRadius = 50; 87 | static const double activeIndicatorSize = 8; 88 | static const double inactiveIndicatorSize = 10; 89 | static const double datePickerHeightOnIos = 270; 90 | static const int maxCharacterCountOfQuote = 108; 91 | static const double barrierColorOpacity = 0.4; 92 | 93 | static const int defaultPageSize = 10; 94 | static const int defaultPageNumber = 1; 95 | static const int defaultDebounceTimeInMilliSeconds = 1000; 96 | static const int defaultThrottleTimeInMilliSeconds = 500; 97 | 98 | static const double height_16 = 16; 99 | } 100 | -------------------------------------------------------------------------------- /lib/app/core/values/text_styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/core/values/app_colors.dart'; 4 | 5 | const centerTextStyle = TextStyle( 6 | fontSize: 28, 7 | fontWeight: FontWeight.bold, 8 | color: AppColors.centerTextColor, 9 | ); 10 | 11 | const errorTextStyle = TextStyle( 12 | fontSize: 12, 13 | fontWeight: FontWeight.w400, 14 | color: AppColors.errorColor, 15 | ); 16 | 17 | const greyDarkTextStyle = TextStyle( 18 | fontSize: 14, 19 | fontWeight: FontWeight.w400, 20 | color: AppColors.textColorGreyDark, 21 | height: 1.45); 22 | 23 | const primaryColorSubtitleStyle = TextStyle( 24 | fontSize: 14, 25 | fontWeight: FontWeight.w400, 26 | color: AppColors.colorPrimary, 27 | height: 1.45); 28 | 29 | const whiteText16 = TextStyle( 30 | fontSize: 16, 31 | fontWeight: FontWeight.w400, 32 | color: Colors.white, 33 | ); 34 | 35 | const whiteText18 = TextStyle( 36 | fontSize: 18, 37 | fontWeight: FontWeight.w400, 38 | color: Colors.white, 39 | ); 40 | 41 | const whiteText32 = TextStyle( 42 | fontSize: 32, 43 | fontWeight: FontWeight.w400, 44 | color: Colors.white, 45 | ); 46 | 47 | const cyanText16 = TextStyle( 48 | fontSize: 16, 49 | fontWeight: FontWeight.w400, 50 | color: AppColors.textColorCyan, 51 | ); 52 | 53 | const cyanText32 = TextStyle( 54 | fontSize: 32, 55 | fontWeight: FontWeight.w400, 56 | color: AppColors.textColorCyan, 57 | ); 58 | 59 | const dialogSubtitle = TextStyle( 60 | fontSize: 16, 61 | fontWeight: FontWeight.w400, 62 | color: AppColors.textColorPrimary, 63 | ); 64 | 65 | const labelStyle = TextStyle( 66 | fontSize: 18, 67 | fontWeight: FontWeight.w400, 68 | height: 1.8, 69 | ); 70 | 71 | final labelStylePrimaryTextColor = labelStyle.copyWith( 72 | color: AppColors.textColorPrimary, 73 | height: 1, 74 | ); 75 | 76 | final labelStyleAppPrimaryColor = labelStyle.copyWith( 77 | color: AppColors.colorPrimary, 78 | height: 1, 79 | ); 80 | 81 | final labelStyleGrey = 82 | labelStyle.copyWith(color: const Color(0xFF323232).withOpacity(0.5)); 83 | 84 | final labelCyanStyle = labelStyle.copyWith(color: AppColors.textColorCyan); 85 | 86 | const labelStyleWhite = TextStyle( 87 | fontSize: 18, 88 | fontWeight: FontWeight.w400, 89 | height: 1.8, 90 | color: Colors.white, 91 | ); 92 | 93 | const appBarSubtitleStyle = TextStyle( 94 | fontSize: 16, 95 | fontWeight: FontWeight.w500, 96 | height: 1.25, 97 | color: AppColors.colorWhite); 98 | 99 | const cardTitleStyle = TextStyle( 100 | fontSize: 20, 101 | fontWeight: FontWeight.w500, 102 | height: 1.2, 103 | color: AppColors.textColorPrimary); 104 | 105 | const cardTitleCyanStyle = TextStyle( 106 | fontSize: 20, 107 | fontWeight: FontWeight.w500, 108 | color: AppColors.colorPrimary, 109 | ); 110 | 111 | const cardSubtitleStyle = TextStyle( 112 | fontSize: 14, 113 | fontWeight: FontWeight.w500, 114 | height: 1.2, 115 | color: AppColors.textColorGreyLight); 116 | 117 | const titleStyle = TextStyle( 118 | fontSize: 18, 119 | fontWeight: FontWeight.w500, 120 | height: 1.34, 121 | ); 122 | 123 | const settingsItemStyle = TextStyle( 124 | fontSize: 16, 125 | fontWeight: FontWeight.w400, 126 | ); 127 | 128 | final cardTagStyle = titleStyle.copyWith(color: AppColors.textColorGreyDark); 129 | 130 | const titleStyleWhite = TextStyle( 131 | fontSize: 18, fontWeight: FontWeight.w500, color: AppColors.colorWhite); 132 | 133 | const inputFieldLabelStyle = TextStyle( 134 | fontSize: 18, 135 | fontWeight: FontWeight.w500, 136 | height: 1.34, 137 | color: AppColors.textColorPrimary, 138 | ); 139 | 140 | const cardSmallTagStyle = TextStyle( 141 | fontSize: 14, 142 | fontWeight: FontWeight.w500, 143 | height: 1.2, 144 | color: AppColors.textColorGreyDark); 145 | 146 | const pageTitleStyle = TextStyle( 147 | fontSize: 18, 148 | fontWeight: FontWeight.w600, 149 | height: 1.15, 150 | color: AppColors.appBarTextColor); 151 | 152 | final pageTitleBlackStyle = 153 | pageTitleStyle.copyWith(color: AppColors.textColorPrimary); 154 | 155 | const appBarActionTextStyle = TextStyle( 156 | fontSize: 16, 157 | fontWeight: FontWeight.w600, 158 | color: AppColors.colorPrimary, 159 | ); 160 | 161 | const pageTitleWhiteStyle = TextStyle( 162 | fontSize: 28, 163 | fontWeight: FontWeight.w600, 164 | height: 1.15, 165 | color: AppColors.colorWhite); 166 | 167 | const extraBigTitleStyle = TextStyle( 168 | fontSize: 40, 169 | fontWeight: FontWeight.w600, 170 | height: 1.12, 171 | ); 172 | 173 | final extraBigTitleCyanStyle = 174 | extraBigTitleStyle.copyWith(color: AppColors.textColorCyan); 175 | 176 | const bigTitleStyle = TextStyle( 177 | fontSize: 28, 178 | fontWeight: FontWeight.w700, 179 | height: 1.15, 180 | ); 181 | 182 | const mediumTitleStyle = TextStyle( 183 | fontSize: 24, 184 | fontWeight: FontWeight.w500, 185 | height: 1.15, 186 | ); 187 | 188 | const descriptionTextStyle = TextStyle( 189 | fontSize: 16, 190 | ); 191 | 192 | final bigTitleCyanStyle = 193 | bigTitleStyle.copyWith(color: AppColors.textColorCyan); 194 | 195 | const bigTitleWhiteStyle = TextStyle( 196 | fontSize: 28, 197 | fontWeight: FontWeight.w700, 198 | height: 1.15, 199 | color: Colors.white, 200 | ); 201 | 202 | const boldTitleStyle = TextStyle( 203 | fontSize: 18, 204 | fontWeight: FontWeight.w700, 205 | height: 1.34, 206 | ); 207 | final boldTitleWhiteStyle = 208 | boldTitleStyle.copyWith(color: AppColors.textColorWhite); 209 | 210 | final boldTitleCyanStyle = 211 | boldTitleStyle.copyWith(color: AppColors.textColorCyan); 212 | 213 | final boldTitleSecondaryColorStyle = 214 | boldTitleStyle.copyWith(color: AppColors.textColorSecondary); 215 | 216 | final boldTitlePrimaryColorStyle = 217 | boldTitleStyle.copyWith(color: AppColors.colorPrimary); 218 | -------------------------------------------------------------------------------- /lib/app/core/widget/app_bar_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/core/values/text_styles.dart'; 4 | 5 | class AppBarTitle extends StatelessWidget { 6 | final String text; 7 | 8 | const AppBarTitle({Key? key, required this.text}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Text( 13 | text, 14 | style: pageTitleStyle, 15 | textAlign: TextAlign.center, 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/app/core/widget/asset_image_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | 4 | class AssetImageView extends StatelessWidget { 5 | const AssetImageView({ 6 | Key? key, 7 | required this.fileName, 8 | this.height, 9 | this.width, 10 | this.color, 11 | this.scale, 12 | this.fit, 13 | }) : super(key: key); 14 | 15 | final String fileName; 16 | final double? height; 17 | final double? width; 18 | final Color? color; 19 | final double? scale; 20 | final BoxFit? fit; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return _getView(); 25 | } 26 | 27 | Widget _getView() { 28 | String mimType = fileName.split(".").last; 29 | String path = "images/$fileName"; 30 | 31 | if (mimType.isEmpty) { 32 | return Icon( 33 | Icons.image_not_supported_outlined, 34 | size: width, 35 | color: color, 36 | ); 37 | } 38 | 39 | switch (mimType) { 40 | case "svg": 41 | return SvgPicture.asset( 42 | path, 43 | height: height, 44 | width: width, 45 | colorFilter: color == null? null: ColorFilter.mode(color ?? Colors.black, BlendMode.srcIn), 46 | fit: fit ?? BoxFit.contain, 47 | ); 48 | case "png": 49 | case "jpg": 50 | case "jpeg": 51 | return Image.asset( 52 | path, 53 | height: height, 54 | width: width, 55 | color: color, 56 | scale: scale, 57 | ); 58 | default: 59 | return Icon( 60 | Icons.image_not_supported_outlined, 61 | size: width, 62 | color: color, 63 | ); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/app/core/widget/custom_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/core/values/app_colors.dart'; 4 | import '/app/core/widget/app_bar_title.dart'; 5 | 6 | //Default appbar customized with the design of our app 7 | class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { 8 | final String appBarTitleText; 9 | final List? actions; 10 | final bool isBackButtonEnabled; 11 | 12 | CustomAppBar({ 13 | Key? key, 14 | required this.appBarTitleText, 15 | this.actions, 16 | this.isBackButtonEnabled = true, 17 | }) : super(key: key); 18 | 19 | @override 20 | Size get preferredSize => AppBar().preferredSize; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return AppBar( 25 | backgroundColor: AppColors.appBarColor, 26 | centerTitle: true, 27 | elevation: 0, 28 | automaticallyImplyLeading: isBackButtonEnabled, 29 | actions: actions, 30 | iconTheme: const IconThemeData(color: AppColors.appBarIconColor), 31 | title: AppBarTitle(text: appBarTitleText), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/app/core/widget/elevated_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/core/values/app_colors.dart'; 4 | import '/app/core/values/app_values.dart'; 5 | 6 | class ElevatedContainer extends StatelessWidget { 7 | final Widget child; 8 | final Color bgColor; 9 | final EdgeInsetsGeometry? padding; 10 | final double borderRadius; 11 | 12 | const ElevatedContainer({ 13 | Key? key, 14 | required this.child, 15 | this.bgColor = AppColors.pageBackground, 16 | this.padding, 17 | this.borderRadius = AppValues.smallRadius, 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Container( 23 | padding: padding, 24 | decoration: BoxDecoration( 25 | borderRadius: BorderRadius.circular(borderRadius), 26 | boxShadow: [ 27 | BoxShadow( 28 | color: AppColors.elevatedContainerColorOpacity, 29 | spreadRadius: 3, 30 | blurRadius: 8, 31 | offset: const Offset(0, 3), // changes position of shadow 32 | ), 33 | ], 34 | color: AppColors.pageBackground), 35 | child: child, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/app/core/widget/icon_text_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/core/values/app_values.dart'; 4 | import '/app/core/widget/asset_image_view.dart'; 5 | 6 | class IconTextWidget extends StatelessWidget { 7 | const IconTextWidget({ 8 | Key? key, 9 | this.fileName, 10 | this.icon, 11 | this.value, 12 | this.height, 13 | this.width, 14 | this.size, 15 | this.color, 16 | }) : super(key: key); 17 | 18 | final String? fileName; 19 | final IconData? icon; 20 | final String? value; 21 | final double? height; 22 | final double? width; 23 | final double? size; 24 | final Color? color; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | if (fileName != null) { 29 | return _getImage(fileName!); 30 | } else if (icon != null) { 31 | return _getIcon(icon!); 32 | } else { 33 | return _getIcon(Icons.image_not_supported_outlined); 34 | } 35 | } 36 | 37 | Widget _getIcon(IconData iconData) { 38 | return Expanded( 39 | child: Row( 40 | children: [ 41 | Icon(icon, size: size, color: color), 42 | const SizedBox(width: AppValues.margin_2), 43 | if (value != null) Text(value!, style: TextStyle(color: color)), 44 | ], 45 | ), 46 | ); 47 | } 48 | 49 | Widget _getImage(String fileName) { 50 | return Expanded( 51 | child: Row( 52 | children: [ 53 | AssetImageView( 54 | fileName: fileName, 55 | height: height, 56 | width: width, 57 | color: color, 58 | ), 59 | const SizedBox(width: AppValues.margin_2), 60 | if (value != null) Text(value!, style: TextStyle(color: color)), 61 | ], 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/app/core/widget/loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/core/values/app_colors.dart'; 4 | import '/app/core/values/app_values.dart'; 5 | import '/app/core/widget/elevated_container.dart'; 6 | 7 | class Loading extends StatelessWidget { 8 | const Loading({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return const Center( 13 | child: ElevatedContainer( 14 | padding: EdgeInsets.all(AppValues.margin), 15 | child: CircularProgressIndicator( 16 | color: AppColors.colorPrimary, 17 | ), 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/core/widget/paging_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | 4 | import '/app/core/utils/debouncer.dart'; 5 | import '/app/core/values/app_values.dart'; 6 | 7 | ///ignore: must_be_immutable 8 | class PagingView extends StatelessWidget { 9 | final Widget child; 10 | final Function() onLoadNextPage; 11 | final Future Function()? onRefresh; 12 | 13 | ScrollController? scrollController; 14 | 15 | late final _debouncer = Debouncer(milliseconds: 500); 16 | 17 | PagingView({ 18 | Key? key, 19 | required this.child, 20 | required this.onLoadNextPage, 21 | this.onRefresh, 22 | this.scrollController, 23 | }) : super(key: key) { 24 | scrollController ??= ScrollController(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return NotificationListener( 30 | onNotification: (ScrollNotification scrollInfo) { 31 | if (scrollController != null) { 32 | var triggerFetchMoreSize = 33 | 0.75 * scrollController!.position.maxScrollExtent; 34 | 35 | if (scrollController!.position.pixels >= triggerFetchMoreSize && 36 | (scrollController!.position.userScrollDirection == 37 | ScrollDirection.reverse)) { 38 | _debouncer.run(() { 39 | onLoadNextPage(); 40 | }); 41 | } 42 | } 43 | 44 | return true; 45 | }, 46 | child: onRefresh == null 47 | ? _getScrollableView() 48 | : RefreshIndicator( 49 | child: _getScrollableView(), 50 | onRefresh: onRefresh!, 51 | ), 52 | ); 53 | } 54 | 55 | _getScrollableView() { 56 | return SingleChildScrollView( 57 | controller: scrollController, 58 | child: Column( 59 | children: [ 60 | child, 61 | const SizedBox(height: AppValues.listBottomEmptySpace), 62 | ], 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/app/core/widget/ripple.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_getx_template/app/core/values/app_colors.dart'; 3 | import 'package:flutter_getx_template/app/core/values/app_values.dart'; 4 | 5 | class Ripple extends StatelessWidget { 6 | final Function()? onTap; 7 | final Widget? child; 8 | final Color rippleColor; 9 | final double rippleRadius; 10 | 11 | const Ripple({ 12 | Key? key, 13 | this.child, 14 | required this.onTap, 15 | this.rippleColor = AppColors.defaultRippleColor, 16 | this.rippleRadius = AppValues.smallRadius, 17 | }) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Material( 22 | color: Colors.transparent, 23 | child: InkWell( 24 | borderRadius: BorderRadius.circular(rippleRadius), 25 | highlightColor: rippleColor, 26 | onTap: onTap, 27 | child: child, 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/app/data/local/db/db.txt: -------------------------------------------------------------------------------- 1 | DB related files will be placed in this package -------------------------------------------------------------------------------- /lib/app/data/local/preference/preference_manager.dart: -------------------------------------------------------------------------------- 1 | abstract class PreferenceManager { 2 | static const keyToken = "token"; 3 | 4 | Future getString(String key, {String defaultValue = ""}); 5 | 6 | Future setString(String key, String value); 7 | 8 | Future getInt(String key, {int defaultValue = 0}); 9 | 10 | Future setInt(String key, int value); 11 | 12 | Future getDouble(String key, {double defaultValue = 0.0}); 13 | 14 | Future setDouble(String key, double value); 15 | 16 | Future getBool(String key, {bool defaultValue = false}); 17 | 18 | Future setBool(String key, bool value); 19 | 20 | Future> getStringList(String key, 21 | {List defaultValue = const []}); 22 | 23 | Future setStringList(String key, List value); 24 | 25 | Future remove(String key); 26 | 27 | Future clear(); 28 | } 29 | -------------------------------------------------------------------------------- /lib/app/data/local/preference/preference_manager_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | import '/app/data/local/preference/preference_manager.dart'; 4 | 5 | class PreferenceManagerImpl implements PreferenceManager { 6 | final _preference = SharedPreferences.getInstance(); 7 | 8 | @override 9 | Future getString(String key, {String defaultValue = ""}) { 10 | return _preference 11 | .then((preference) => preference.getString(key) ?? defaultValue); 12 | } 13 | 14 | @override 15 | Future setString(String key, String value) { 16 | return _preference.then((preference) => preference.setString(key, value)); 17 | } 18 | 19 | @override 20 | Future getInt(String key, {int defaultValue = 0}) { 21 | return _preference 22 | .then((preference) => preference.getInt(key) ?? defaultValue); 23 | } 24 | 25 | @override 26 | Future setInt(String key, int value) { 27 | return _preference.then((preference) => preference.setInt(key, value)); 28 | } 29 | 30 | @override 31 | Future getDouble(String key, {double defaultValue = 0.0}) { 32 | return _preference 33 | .then((preference) => preference.getDouble(key) ?? defaultValue); 34 | } 35 | 36 | @override 37 | Future setDouble(String key, double value) { 38 | return _preference.then((preference) => preference.setDouble(key, value)); 39 | } 40 | 41 | @override 42 | Future getBool(String key, {bool defaultValue = false}) { 43 | return _preference 44 | .then((preference) => preference.getBool(key) ?? defaultValue); 45 | } 46 | 47 | @override 48 | Future setBool(String key, bool value) { 49 | return _preference.then((preference) => preference.setBool(key, value)); 50 | } 51 | 52 | @override 53 | Future> getStringList(String key, 54 | {List defaultValue = const []}) { 55 | return _preference 56 | .then((preference) => preference.getStringList(key) ?? defaultValue); 57 | } 58 | 59 | @override 60 | Future setStringList(String key, List value) { 61 | return _preference 62 | .then((preference) => preference.setStringList(key, value)); 63 | } 64 | 65 | @override 66 | Future remove(String key) { 67 | return _preference.then((preference) => preference.remove(key)); 68 | } 69 | 70 | @override 71 | Future clear() { 72 | return _preference.then((preference) => preference.clear()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/app/data/model/github_project_search_response.dart: -------------------------------------------------------------------------------- 1 | class GithubProjectSearchResponse { 2 | GithubProjectSearchResponse({ 3 | this.totalCount, 4 | this.incompleteResults, 5 | this.items, 6 | }); 7 | 8 | GithubProjectSearchResponse.fromJson(dynamic json) { 9 | totalCount = json['total_count']; 10 | incompleteResults = json['incomplete_results']; 11 | if (json['items'] != null) { 12 | items = []; 13 | json['items'].forEach((v) { 14 | items?.add(Item.fromJson(v)); 15 | }); 16 | } 17 | } 18 | 19 | int? totalCount; 20 | bool? incompleteResults; 21 | List? items; 22 | 23 | Map toJson() { 24 | final map = {}; 25 | map['total_count'] = totalCount; 26 | map['incomplete_results'] = incompleteResults; 27 | if (items != null) { 28 | map['items'] = items?.map((v) => v.toJson()).toList(); 29 | } 30 | 31 | return map; 32 | } 33 | } 34 | 35 | class Item { 36 | Item({ 37 | this.name, 38 | this.owner, 39 | this.description, 40 | this.stargazersCount, 41 | this.forks, 42 | this.watchers, 43 | this.score, 44 | }); 45 | 46 | Item.fromJson(dynamic json) { 47 | name = json['name']; 48 | owner = json['owner'] != null ? Owner.fromJson(json['owner']) : null; 49 | description = json['description']; 50 | stargazersCount = json['stargazers_count']; 51 | forks = json['forks']; 52 | watchers = json['watchers']; 53 | score = json['score']; 54 | } 55 | 56 | String? name; 57 | Owner? owner; 58 | int? stargazersCount; 59 | int? forks; 60 | int? watchers; 61 | double? score; 62 | String? description; 63 | 64 | Map toJson() { 65 | final map = {}; 66 | map['name'] = name; 67 | if (owner != null) { 68 | map['owner'] = owner?.toJson(); 69 | } 70 | map['description'] = description; 71 | map['stargazers_count'] = stargazersCount; 72 | map['forks'] = forks; 73 | map['watchers'] = watchers; 74 | map['score'] = score; 75 | 76 | return map; 77 | } 78 | } 79 | 80 | class Owner { 81 | Owner({ 82 | this.login, 83 | this.avatarUrl, 84 | }); 85 | 86 | Owner.fromJson(dynamic json) { 87 | login = json['login']; 88 | avatarUrl = json['avatar_url']; 89 | } 90 | 91 | String? login; 92 | String? avatarUrl; 93 | 94 | Map toJson() { 95 | final map = {}; 96 | map['login'] = login; 97 | map['avatar_url'] = avatarUrl; 98 | 99 | return map; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/app/data/remote/github_remote_data_source.dart: -------------------------------------------------------------------------------- 1 | import '/app/core/model/github_search_query_param.dart'; 2 | import '/app/data/model/github_project_search_response.dart'; 3 | 4 | abstract class GithubRemoteDataSource { 5 | Future searchGithubProject( 6 | GithubSearchQueryParam queryParam); 7 | 8 | Future getGithubProjectDetails(String userName, String repositoryName); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/data/remote/github_remote_data_source_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | import '/app/core/base/base_remote_source.dart'; 4 | import '/app/core/model/github_search_query_param.dart'; 5 | import '/app/data/model/github_project_search_response.dart'; 6 | import '/app/data/remote/github_remote_data_source.dart'; 7 | import '/app/network/dio_provider.dart'; 8 | 9 | class GithubRemoteDataSourceImpl extends BaseRemoteSource 10 | implements GithubRemoteDataSource { 11 | @override 12 | Future searchGithubProject( 13 | GithubSearchQueryParam queryParam) { 14 | var endpoint = "${DioProvider.baseUrl}/search/repositories"; 15 | var dioCall = dioClient.get(endpoint, queryParameters: queryParam.toJson()); 16 | 17 | try { 18 | return callApiWithErrorParser(dioCall) 19 | .then((response) => _parseGithubProjectSearchResponse(response)); 20 | } catch (e) { 21 | rethrow; 22 | } 23 | } 24 | 25 | @override 26 | Future getGithubProjectDetails(String userName, String repositoryName) { 27 | var endpoint = "${DioProvider.baseUrl}/repos/$userName/$repositoryName"; 28 | var dioCall = dioClient.get(endpoint); 29 | 30 | try { 31 | return callApiWithErrorParser(dioCall) 32 | .then((response) => _parseGithubProjectResponse(response)); 33 | } catch (e) { 34 | rethrow; 35 | } 36 | } 37 | 38 | GithubProjectSearchResponse _parseGithubProjectSearchResponse( 39 | Response response) { 40 | return GithubProjectSearchResponse.fromJson(response.data); 41 | } 42 | 43 | Item _parseGithubProjectResponse(Response response) { 44 | return Item.fromJson(response.data); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/app/data/repository/github_repository.dart: -------------------------------------------------------------------------------- 1 | import '/app/core/model/github_search_query_param.dart'; 2 | import '/app/data/model/github_project_search_response.dart'; 3 | 4 | abstract class GithubRepository { 5 | Future searchProject( 6 | GithubSearchQueryParam queryParam); 7 | 8 | Future getProject(String userName, String repositoryName); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/data/repository/github_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/core/model/github_search_query_param.dart'; 4 | import '/app/data/model/github_project_search_response.dart'; 5 | import '/app/data/remote/github_remote_data_source.dart'; 6 | import '/app/data/repository/github_repository.dart'; 7 | 8 | class GithubRepositoryImpl implements GithubRepository { 9 | final GithubRemoteDataSource _remoteSource = 10 | Get.find(tag: (GithubRemoteDataSource).toString()); 11 | 12 | @override 13 | Future searchProject( 14 | GithubSearchQueryParam queryParam) { 15 | return _remoteSource.searchGithubProject(queryParam); 16 | } 17 | 18 | @override 19 | Future getProject(String userName, String repositoryName) { 20 | return _remoteSource.getGithubProjectDetails(userName, repositoryName); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/modules/favorite/bindings/favorite_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/modules/favorite/controllers/favorite_controller.dart'; 4 | 5 | class FavoriteBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => FavoriteController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/favorite/controllers/favorite_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/core/base/base_controller.dart'; 4 | 5 | class FavoriteController extends BaseController { 6 | final count = 0.obs; 7 | 8 | void increment() => count.value++; 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/modules/favorite/views/favorite_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/core/base/base_view.dart'; 4 | import '/app/core/values/text_styles.dart'; 5 | import '/app/core/widget/custom_app_bar.dart'; 6 | import '/app/modules/favorite/controllers/favorite_controller.dart'; 7 | 8 | class FavoriteView extends BaseView { 9 | @override 10 | PreferredSizeWidget? appBar(BuildContext context) { 11 | return CustomAppBar( 12 | appBarTitleText: 'Favorite', 13 | ); 14 | } 15 | 16 | @override 17 | Widget body(BuildContext context) { 18 | return const Center( 19 | child: Text( 20 | 'FavoriteView is working', 21 | style: titleStyle, 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/app/modules/home/bindings/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/modules/home/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 '/app/core/base/base_controller.dart'; 4 | import '/app/core/base/paging_controller.dart'; 5 | import '/app/core/model/github_search_query_param.dart'; 6 | import '/app/data/model/github_project_search_response.dart'; 7 | import '/app/data/repository/github_repository.dart'; 8 | import '/app/modules/home/model/github_project_ui_data.dart'; 9 | 10 | class HomeController extends BaseController { 11 | final GithubRepository _repository = 12 | Get.find(tag: (GithubRepository).toString()); 13 | 14 | final RxList _githubProjectListController = 15 | RxList.empty(); 16 | 17 | List get projectList => 18 | _githubProjectListController.toList(); 19 | 20 | final pagingController = PagingController(); 21 | 22 | void getGithubGetxProjectList() { 23 | if (!pagingController.canLoadNextPage()) return; 24 | 25 | pagingController.isLoadingPage = true; 26 | 27 | var queryParam = GithubSearchQueryParam( 28 | searchKeyWord: 'flutter getx template', 29 | pageNumber: pagingController.pageNumber, 30 | ); 31 | 32 | var githubRepoSearchService = _repository.searchProject(queryParam); 33 | 34 | callDataService( 35 | githubRepoSearchService, 36 | onSuccess: _handleProjectListResponseSuccess, 37 | ); 38 | 39 | pagingController.isLoadingPage = false; 40 | } 41 | 42 | onRefreshPage() { 43 | pagingController.initRefresh(); 44 | getGithubGetxProjectList(); 45 | } 46 | 47 | onLoadNextPage() { 48 | logger.i("On load next"); 49 | 50 | getGithubGetxProjectList(); 51 | } 52 | 53 | void _handleProjectListResponseSuccess(GithubProjectSearchResponse response) { 54 | List? repoList = response.items 55 | ?.map((e) => GithubProjectUiData( 56 | repositoryName: e.name != null ? e.name! : "Null", 57 | ownerLoginName: e.owner != null ? e.owner!.login! : "Null", 58 | ownerAvatar: e.owner != null ? e.owner!.avatarUrl! : "", 59 | numberOfStar: e.stargazersCount ?? 0, 60 | numberOfFork: e.forks ?? 0, 61 | score: e.score ?? 0.0, 62 | watchers: e.watchers ?? 0, 63 | description: e.description ?? "", 64 | )) 65 | .toList(); 66 | 67 | if (_isLastPage(repoList!.length, response.totalCount!)) { 68 | pagingController.appendLastPage(repoList); 69 | } else { 70 | pagingController.appendPage(repoList); 71 | } 72 | 73 | var newList = [...pagingController.listItems]; 74 | 75 | _githubProjectListController(newList); 76 | } 77 | 78 | bool _isLastPage(int newListItemCount, int totalCount) { 79 | return (projectList.length + newListItemCount) >= totalCount; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/app/modules/home/model/github_project_ui_data.dart: -------------------------------------------------------------------------------- 1 | class GithubProjectUiData { 2 | String repositoryName; 3 | String ownerLoginName; 4 | String ownerAvatar; 5 | int numberOfStar; 6 | int numberOfFork; 7 | String description; 8 | double score; 9 | int watchers; 10 | 11 | GithubProjectUiData({ 12 | this.repositoryName = "", 13 | this.ownerLoginName = "", 14 | this.ownerAvatar = "", 15 | this.numberOfStar = 0, 16 | this.numberOfFork = 0, 17 | this.description = "", 18 | this.score = 0.0, 19 | this.watchers = 0, 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /lib/app/modules/home/views/home_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '/app/core/base/base_view.dart'; 5 | import '/app/core/values/app_values.dart'; 6 | import '/app/core/widget/custom_app_bar.dart'; 7 | import '/app/core/widget/paging_view.dart'; 8 | import '/app/modules/home/controllers/home_controller.dart'; 9 | import '/app/modules/home/widget/item_github_project.dart'; 10 | 11 | class HomeView extends BaseView { 12 | HomeView() { 13 | controller.getGithubGetxProjectList(); 14 | } 15 | 16 | @override 17 | PreferredSizeWidget? appBar(BuildContext context) { 18 | return CustomAppBar( 19 | appBarTitleText: 'GetX Templates on GitHub', 20 | ); 21 | } 22 | 23 | @override 24 | Widget body(BuildContext context) { 25 | return PagingView( 26 | onRefresh: () async { 27 | controller.onRefreshPage(); 28 | }, 29 | onLoadNextPage: () { 30 | controller.onLoadNextPage(); 31 | }, 32 | child: Padding( 33 | padding: const EdgeInsets.all(AppValues.padding), 34 | child: Obx( 35 | () => ListView.separated( 36 | shrinkWrap: true, 37 | itemCount: controller.projectList.length, 38 | primary: false, 39 | physics: const NeverScrollableScrollPhysics(), 40 | itemBuilder: (context, index) { 41 | var model = controller.projectList[index]; 42 | 43 | return ItemGithubProject(dataModel: model); 44 | }, 45 | separatorBuilder: (BuildContext context, int index) => 46 | const SizedBox(height: AppValues.smallMargin), 47 | ), 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/app/modules/home/widget/item_github_project.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_getx_template/app/core/widget/ripple.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '/app/core/base/base_widget_mixin.dart'; 6 | import '/app/core/values/app_colors.dart'; 7 | import '/app/core/values/app_values.dart'; 8 | import '/app/core/values/text_styles.dart'; 9 | import '/app/core/widget/elevated_container.dart'; 10 | import '/app/core/widget/icon_text_widgets.dart'; 11 | import '/app/modules/home/model/github_project_ui_data.dart'; 12 | import '/app/routes/app_pages.dart'; 13 | 14 | class ItemGithubProject extends StatelessWidget with BaseWidgetMixin { 15 | final GithubProjectUiData dataModel; 16 | 17 | ItemGithubProject({ 18 | Key? key, 19 | required this.dataModel, 20 | }) : super(key: key); 21 | 22 | @override 23 | Widget body(BuildContext context) { 24 | return ElevatedContainer( 25 | child: Ripple( 26 | onTap: _onTap, 27 | child: Padding( 28 | padding: const EdgeInsets.all(AppValues.padding), 29 | child: Row( 30 | mainAxisAlignment: MainAxisAlignment.start, 31 | crossAxisAlignment: CrossAxisAlignment.center, 32 | children: [ 33 | CircleAvatar( 34 | backgroundImage: NetworkImage(dataModel.ownerAvatar), 35 | radius: AppValues.circularImageSize_30, 36 | ), 37 | const SizedBox(width: AppValues.margin_10), 38 | _getDetailsView(), 39 | ], 40 | ), 41 | ), 42 | ), 43 | ); 44 | } 45 | 46 | Widget _getDetailsView() { 47 | return Expanded( 48 | child: Column( 49 | crossAxisAlignment: CrossAxisAlignment.start, 50 | children: [ 51 | Text( 52 | dataModel.repositoryName, 53 | style: cardTitleStyle, 54 | overflow: TextOverflow.ellipsis, 55 | maxLines: 1, 56 | ), 57 | const SizedBox(height: AppValues.margin_4), 58 | Text( 59 | dataModel.ownerLoginName, 60 | style: cardSubtitleStyle, 61 | maxLines: 1, 62 | overflow: TextOverflow.ellipsis, 63 | ), 64 | const SizedBox(height: AppValues.margin_4), 65 | _getForkStarWatcherView(), 66 | ], 67 | ), 68 | ); 69 | } 70 | 71 | Widget _getForkStarWatcherView() { 72 | return Row( 73 | children: [ 74 | IconTextWidget( 75 | fileName: "ic_fork.svg", 76 | value: dataModel.numberOfFork.toString(), 77 | height: AppValues.iconSize_20, 78 | width: AppValues.iconSize_20, 79 | color: AppColors.iconColorDefault, 80 | ), 81 | IconTextWidget( 82 | icon: Icons.star_border, 83 | value: dataModel.numberOfStar.toString(), 84 | size: AppValues.iconSize_20, 85 | color: AppColors.iconColorDefault, 86 | ), 87 | IconTextWidget( 88 | icon: Icons.visibility_outlined, 89 | value: dataModel.watchers.toString(), 90 | size: AppValues.iconSize_20, 91 | color: AppColors.iconColorDefault, 92 | ), 93 | ], 94 | ); 95 | } 96 | 97 | void _onTap() { 98 | Get.toNamed(Routes.PROJECT_DETAILS, arguments: dataModel); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/app/modules/main/bindings/main_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/modules/favorite/controllers/favorite_controller.dart'; 4 | import '/app/modules/home/controllers/home_controller.dart'; 5 | import '/app/modules/main/controllers/main_controller.dart'; 6 | import '/app/modules/other/controllers/other_controller.dart'; 7 | import '/app/modules/settings/controllers/settings_controller.dart'; 8 | 9 | class MainBinding extends Bindings { 10 | @override 11 | void dependencies() { 12 | Get.lazyPut( 13 | () => MainController(), 14 | fenix: true, 15 | ); 16 | Get.lazyPut( 17 | () => OtherController(), 18 | fenix: true, 19 | ); 20 | Get.lazyPut( 21 | () => HomeController(), 22 | fenix: true, 23 | ); 24 | Get.lazyPut( 25 | () => FavoriteController(), 26 | ); 27 | Get.lazyPut( 28 | () => SettingsController(), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/app/modules/main/controllers/bottom_nav_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class BottomNavController extends GetxController { 4 | final _selectedIndexController = 0.obs; 5 | 6 | updateSelectedIndex(int index) => _selectedIndexController(index); 7 | 8 | int get selectedIndex => _selectedIndexController.value; 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/modules/main/controllers/main_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/core/base/base_controller.dart'; 4 | import '/app/modules/main/model/menu_code.dart'; 5 | 6 | class MainController extends BaseController { 7 | final _selectedMenuCodeController = MenuCode.HOME.obs; 8 | 9 | MenuCode get selectedMenuCode => _selectedMenuCodeController.value; 10 | 11 | final lifeCardUpdateController = false.obs; 12 | 13 | onMenuSelected(MenuCode menuCode) async { 14 | _selectedMenuCodeController(menuCode); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/app/modules/main/model/menu_code.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 2 | import 'package:flutter_getx_template/app/modules/main/model/menu_item.dart'; 3 | 4 | enum MenuCode { HOME, FAVORITE, SETTINGS } 5 | 6 | extension MenuCodeExtensions on MenuCode { 7 | BottomNavItem toBottomNavItem(AppLocalizations appLocalization) { 8 | switch (this) { 9 | case MenuCode.HOME: 10 | return BottomNavItem( 11 | navTitle: appLocalization.bottomNavHome, 12 | iconSvgName: "ic_home.svg", 13 | menuCode: MenuCode.HOME, 14 | ); 15 | case MenuCode.FAVORITE: 16 | return BottomNavItem( 17 | navTitle: appLocalization.bottomNavFavorite, 18 | iconSvgName: "ic_favorite.svg", 19 | menuCode: MenuCode.FAVORITE); 20 | case MenuCode.SETTINGS: 21 | return BottomNavItem( 22 | navTitle: appLocalization.bottomNavSettings, 23 | iconSvgName: "ic_settings.svg", 24 | menuCode: MenuCode.SETTINGS); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/app/modules/main/model/menu_item.dart: -------------------------------------------------------------------------------- 1 | import '/app/modules/main/model/menu_code.dart'; 2 | 3 | class BottomNavItem { 4 | final String navTitle; 5 | final String iconSvgName; 6 | final MenuCode menuCode; 7 | 8 | const BottomNavItem( 9 | {required this.navTitle, 10 | required this.iconSvgName, 11 | required this.menuCode}); 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/main/views/bottom_nav_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_getx_template/app/core/base/base_widget_mixin.dart'; 3 | import 'package:flutter_getx_template/app/core/widget/asset_image_view.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | import '/app/core/values/app_colors.dart'; 7 | import '/app/core/values/app_values.dart'; 8 | import '/app/modules/main/controllers/bottom_nav_controller.dart'; 9 | import '/app/modules/main/model/menu_code.dart'; 10 | import '/app/modules/main/model/menu_item.dart'; 11 | 12 | typedef OnBottomNavItemSelected = Function(MenuCode menuCode); 13 | 14 | class BottomNavBar extends StatelessWidget with BaseWidgetMixin { 15 | BottomNavBar({ 16 | Key? key, 17 | required this.onItemSelected, 18 | }) : super(key: key); 19 | 20 | final OnBottomNavItemSelected onItemSelected; 21 | final navController = BottomNavController(); 22 | final Key bottomNavKey = GlobalKey(); 23 | final Color selectedItemColor = Colors.white; 24 | final Color unselectedItemColor = Colors.grey; 25 | 26 | @override 27 | Widget body(BuildContext context) { 28 | List navItems = _getNavItems(); 29 | 30 | return Obx( 31 | () => BottomNavigationBar( 32 | key: bottomNavKey, 33 | items: _navItemBuilder(navItems), 34 | showSelectedLabels: true, 35 | showUnselectedLabels: true, 36 | type: BottomNavigationBarType.fixed, 37 | backgroundColor: AppColors.colorAccent, 38 | selectedItemColor: selectedItemColor, 39 | unselectedItemColor: unselectedItemColor, 40 | currentIndex: navController.selectedIndex, 41 | onTap: (index) { 42 | navController.updateSelectedIndex(index); 43 | onItemSelected(navItems[index].menuCode); 44 | }, 45 | ), 46 | ); 47 | } 48 | 49 | List _navItemBuilder(List navItems) { 50 | return navItems 51 | .map( 52 | (BottomNavItem navItem) => _getBottomNavigationBarItem( 53 | navItem, 54 | navItems.indexOf(navItem) == navController.selectedIndex, 55 | ), 56 | ) 57 | .toList(); 58 | } 59 | 60 | BottomNavigationBarItem _getBottomNavigationBarItem( 61 | BottomNavItem navItem, 62 | bool isSelected, 63 | ) { 64 | return BottomNavigationBarItem( 65 | icon: AssetImageView( 66 | fileName: navItem.iconSvgName, 67 | height: AppValues.iconDefaultSize, 68 | width: AppValues.iconDefaultSize, 69 | color: isSelected ? selectedItemColor : unselectedItemColor, 70 | ), 71 | label: navItem.navTitle, 72 | tooltip: navItem.navTitle, 73 | ); 74 | } 75 | 76 | List _getNavItems() { 77 | return MenuCode.values 78 | .map((e) => e.toBottomNavItem(appLocalization)) 79 | .toList(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/app/modules/main/views/main_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '/app/core/base/base_view.dart'; 6 | import '/app/modules/favorite/views/favorite_view.dart'; 7 | import '/app/modules/home/views/home_view.dart'; 8 | import '/app/modules/main/controllers/main_controller.dart'; 9 | import '/app/modules/main/model/menu_code.dart'; 10 | import '/app/modules/main/views/bottom_nav_bar.dart'; 11 | import '/app/modules/other/views/other_view.dart'; 12 | import '/app/modules/settings/views/settings_view.dart'; 13 | 14 | // ignore: must_be_immutable 15 | class MainView extends BaseView { 16 | @override 17 | PreferredSizeWidget? appBar(BuildContext context) => null; 18 | 19 | @override 20 | Widget body(BuildContext context) { 21 | return Container( 22 | key: UniqueKey(), 23 | child: Obx( 24 | () => getPageOnSelectedMenu(controller.selectedMenuCode), 25 | ), 26 | ); 27 | } 28 | 29 | @override 30 | Widget? bottomNavigationBar() { 31 | return BottomNavBar(onItemSelected: controller.onMenuSelected); 32 | } 33 | 34 | final HomeView homeView = HomeView(); 35 | FavoriteView? favoriteView; 36 | SettingsView? settingsView; 37 | 38 | Widget getPageOnSelectedMenu(MenuCode menuCode) { 39 | switch (menuCode) { 40 | case MenuCode.HOME: 41 | return homeView; 42 | case MenuCode.FAVORITE: 43 | favoriteView ??= FavoriteView(); 44 | return favoriteView!; 45 | case MenuCode.SETTINGS: 46 | settingsView ??= SettingsView(); 47 | return settingsView!; 48 | default: 49 | return OtherView( 50 | viewParam: describeEnum(menuCode), 51 | ); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/app/modules/other/bindings/other_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/modules/other/controllers/other_controller.dart'; 4 | 5 | class OtherBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => OtherController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/other/controllers/other_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/core/base/base_controller.dart'; 4 | 5 | class OtherController extends BaseController { 6 | final count = 0.obs; 7 | 8 | void increment() => count.value++; 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/modules/other/views/other_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/core/base/base_view.dart'; 4 | import '/app/core/widget/custom_app_bar.dart'; 5 | import '../controllers/other_controller.dart'; 6 | 7 | class OtherView extends BaseView { 8 | final String viewParam; 9 | 10 | OtherView({this.viewParam = ""}); 11 | 12 | @override 13 | PreferredSizeWidget? appBar(BuildContext context) { 14 | return CustomAppBar(appBarTitleText: viewParam); 15 | } 16 | 17 | @override 18 | Widget body(BuildContext context) { 19 | return const Center( 20 | child: Text( 21 | 'OtherView is working', 22 | style: TextStyle(fontSize: 20), 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/app/modules/project_details/bindings/project_details_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/modules/project_details/controllers/project_details_controller.dart'; 4 | 5 | class ProjectDetailsBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => ProjectDetailsController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/project_details/controllers/project_details_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/core/base/base_controller.dart'; 4 | import '/app/data/model/github_project_search_response.dart'; 5 | import '/app/data/repository/github_repository.dart'; 6 | import '/app/modules/home/model/github_project_ui_data.dart'; 7 | 8 | class ProjectDetailsController extends BaseController { 9 | final GithubRepository _repository = 10 | Get.find(tag: (GithubRepository).toString()); 11 | 12 | final Rx _projectUiData = GithubProjectUiData().obs; 13 | 14 | GithubProjectUiData get projectUiData => _projectUiData.value; 15 | 16 | @override 17 | void onInit() { 18 | var dataModel = Get.arguments; 19 | if (dataModel is GithubProjectUiData) { 20 | getGithubRepository(dataModel.ownerLoginName, dataModel.repositoryName); 21 | } 22 | super.onInit(); 23 | } 24 | 25 | void getGithubRepository(userName, repositoryName) { 26 | callDataService( 27 | _repository.getProject(userName, repositoryName), 28 | onSuccess: _handleProjectDetailsResponseSuccess, 29 | ); 30 | } 31 | 32 | void _handleProjectDetailsResponseSuccess(Item project) { 33 | _projectUiData(GithubProjectUiData( 34 | repositoryName: project.name != null ? project.name! : "", 35 | ownerLoginName: project.owner != null ? project.owner!.login! : "", 36 | ownerAvatar: project.owner != null ? project.owner!.avatarUrl! : "", 37 | numberOfStar: project.stargazersCount ?? 0, 38 | numberOfFork: project.forks ?? 0, 39 | score: project.score ?? 0.0, 40 | watchers: project.watchers ?? 0, 41 | description: project.description ?? "", 42 | )); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/app/modules/project_details/views/project_details_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '/app/core/base/base_view.dart'; 5 | import '/app/core/values/app_colors.dart'; 6 | import '/app/core/values/app_values.dart'; 7 | import '/app/core/values/text_styles.dart'; 8 | import '/app/core/widget/custom_app_bar.dart'; 9 | import '/app/core/widget/icon_text_widgets.dart'; 10 | import '/app/modules/project_details/controllers/project_details_controller.dart'; 11 | 12 | class ProjectDetailsView extends BaseView { 13 | @override 14 | PreferredSizeWidget? appBar(BuildContext context) { 15 | return CustomAppBar( 16 | appBarTitleText: 'Repository details', 17 | isBackButtonEnabled: true, 18 | ); 19 | } 20 | 21 | @override 22 | Widget body(BuildContext context) { 23 | return Scaffold( 24 | body: Center( 25 | child: Obx(() => _getView()), 26 | ), 27 | ); 28 | } 29 | 30 | Widget _getView() { 31 | return controller.projectUiData.repositoryName.isEmpty 32 | ? Container() 33 | : Container( 34 | margin: const EdgeInsets.all(AppValues.margin_20), 35 | child: Column( 36 | crossAxisAlignment: CrossAxisAlignment.start, 37 | children: [ 38 | Text( 39 | controller.projectUiData.repositoryName, 40 | style: cardTitleStyle, 41 | overflow: TextOverflow.ellipsis, 42 | maxLines: 1, 43 | ), 44 | _getAuthor(), 45 | const SizedBox(height: AppValues.margin_4), 46 | _getForkStarWatcherView(), 47 | const SizedBox(height: AppValues.margin_30), 48 | _getDescription() 49 | ], 50 | ), 51 | ); 52 | } 53 | 54 | Widget _getAuthor() { 55 | return Row( 56 | children: [ 57 | CircleAvatar( 58 | backgroundImage: NetworkImage(controller.projectUiData.ownerAvatar), 59 | radius: AppValues.iconSmallSize, 60 | ), 61 | const SizedBox(width: AppValues.margin_6), 62 | Text( 63 | controller.projectUiData.ownerLoginName, 64 | style: cardSubtitleStyle, 65 | maxLines: 1, 66 | overflow: TextOverflow.ellipsis, 67 | ), 68 | ], 69 | ); 70 | } 71 | 72 | Widget _getForkStarWatcherView() { 73 | return Container( 74 | margin: const EdgeInsets.only(left: AppValues.margin_40), 75 | child: Row( 76 | children: [ 77 | IconTextWidget( 78 | fileName: "ic_fork.svg", 79 | value: controller.projectUiData.numberOfFork.toString(), 80 | height: AppValues.iconSize_20, 81 | width: AppValues.iconSize_20, 82 | color: AppColors.iconColorDefault, 83 | ), 84 | IconTextWidget( 85 | icon: Icons.star_border, 86 | value: controller.projectUiData.numberOfStar.toString(), 87 | size: AppValues.iconSize_20, 88 | color: AppColors.iconColorDefault, 89 | ), 90 | IconTextWidget( 91 | icon: Icons.visibility_outlined, 92 | value: controller.projectUiData.watchers.toString(), 93 | size: AppValues.iconSize_20, 94 | color: AppColors.iconColorDefault, 95 | ), 96 | ], 97 | ), 98 | ); 99 | } 100 | 101 | Widget _getDescription() { 102 | return Expanded( 103 | child: SingleChildScrollView( 104 | scrollDirection: Axis.vertical, 105 | child: Text(controller.projectUiData.description, 106 | style: descriptionTextStyle), 107 | ), 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/app/modules/settings/bindings/settings_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/modules/settings/controllers/settings_controller.dart'; 4 | 5 | class SettingsBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => SettingsController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/modules/settings/controllers/settings_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/core/base/base_controller.dart'; 4 | 5 | class SettingsController extends BaseController { 6 | final count = 0.obs; 7 | 8 | void increment() => count.value++; 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/modules/settings/views/settings_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/app/modules/settings/widgets/item_settings_widgets.dart'; 3 | import '/app/core/base/base_view.dart'; 4 | import '/app/core/widget/custom_app_bar.dart'; 5 | import '/app/modules/settings/controllers/settings_controller.dart'; 6 | 7 | class SettingsView extends BaseView { 8 | @override 9 | PreferredSizeWidget? appBar(BuildContext context) { 10 | return CustomAppBar( 11 | appBarTitleText: appLocalization.bottomNavSettings, 12 | isBackButtonEnabled: false, 13 | ); 14 | } 15 | 16 | @override 17 | Widget body(BuildContext context) { 18 | return Column( 19 | children: [ 20 | ItemSettings( 21 | title: appLocalization.settingsTheme, 22 | prefixImage: 'ic_theme.png', 23 | suffixImage: 'arrow_forward.svg', 24 | onTap: _onThemeItemClicked, 25 | ), 26 | _getHorizontalDivider(), 27 | ItemSettings( 28 | title: appLocalization.settingsLanguage, 29 | prefixImage: 'ic_language.svg', 30 | suffixImage: 'arrow_forward.svg', 31 | onTap: _onLanguageItemClicked, 32 | ), 33 | _getHorizontalDivider(), 34 | ItemSettings( 35 | title: appLocalization.settingsFontSize, 36 | prefixImage: 'ic_font_size.svg', 37 | suffixImage: 'arrow_forward.svg', 38 | onTap: _onFontSizeItemClicked, 39 | ), 40 | _getHorizontalDivider(), 41 | ], 42 | ); 43 | } 44 | 45 | Widget _getHorizontalDivider() { 46 | return const Divider(height: 1); 47 | } 48 | 49 | void _onThemeItemClicked() { 50 | showToast('Theme: Development in progress'); 51 | } 52 | 53 | void _onLanguageItemClicked() { 54 | showToast('Language: Development in progress'); 55 | } 56 | 57 | void _onFontSizeItemClicked() { 58 | showToast('Font Size: Development in progress'); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /lib/app/modules/settings/widgets/item_settings_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/app/core/base/base_widget_mixin.dart'; 3 | import '/app/core/values/app_colors.dart'; 4 | import '/app/core/values/app_values.dart'; 5 | import '/app/core/values/text_styles.dart'; 6 | import '/app/core/widget/asset_image_view.dart'; 7 | import '/app/core/widget/ripple.dart'; 8 | 9 | class ItemSettings extends StatelessWidget with BaseWidgetMixin { 10 | final String prefixImage; 11 | final String suffixImage; 12 | final String title; 13 | final Function()? onTap; 14 | 15 | ItemSettings({ 16 | required this.prefixImage, 17 | required this.suffixImage, 18 | required this.title, 19 | required this.onTap, 20 | }); 21 | 22 | @override 23 | Widget body(BuildContext context) { 24 | return Ripple( 25 | onTap: onTap, 26 | child: Padding( 27 | padding: const EdgeInsets.all(AppValues.padding), 28 | child: Row( 29 | children: [ 30 | AssetImageView( 31 | fileName: prefixImage, 32 | height: AppValues.iconSize_20, 33 | width: AppValues.iconSize_20, 34 | ), 35 | const SizedBox(width: AppValues.smallPadding), 36 | Text(title, style: settingsItemStyle), 37 | const Spacer(), 38 | AssetImageView( 39 | fileName: suffixImage, 40 | color: AppColors.suffixImageColor, 41 | height: AppValues.iconSize_20, 42 | width: AppValues.iconSize_20, 43 | ), 44 | ], 45 | ), 46 | ), 47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /lib/app/my_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import '/app/bindings/initial_binding.dart'; 6 | import '/app/core/values/app_colors.dart'; 7 | import '/app/routes/app_pages.dart'; 8 | import '/flavors/build_config.dart'; 9 | import '/flavors/env_config.dart'; 10 | 11 | class MyApp extends StatefulWidget { 12 | const MyApp({Key? key}) : super(key: key); 13 | 14 | @override 15 | _MyAppState createState() => _MyAppState(); 16 | } 17 | 18 | class _MyAppState extends State { 19 | final EnvConfig _envConfig = BuildConfig.instance.config; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return GetMaterialApp( 24 | title: _envConfig.appName, 25 | initialRoute: AppPages.INITIAL, 26 | initialBinding: InitialBinding(), 27 | getPages: AppPages.routes, 28 | localizationsDelegates: AppLocalizations.localizationsDelegates, 29 | supportedLocales: _getSupportedLocal(), 30 | theme: ThemeData( 31 | primarySwatch: AppColors.colorPrimarySwatch, 32 | visualDensity: VisualDensity.adaptivePlatformDensity, 33 | brightness: Brightness.light, 34 | primaryColor: AppColors.colorPrimary, 35 | textTheme: const TextTheme( 36 | labelLarge: TextStyle( 37 | color: Colors.white, 38 | fontSize: 20.0, 39 | fontWeight: FontWeight.bold, 40 | ), 41 | ), 42 | fontFamily: 'Roboto', 43 | ), 44 | debugShowCheckedModeBanner: false, 45 | ); 46 | } 47 | 48 | List _getSupportedLocal() { 49 | return [ 50 | const Locale('en', ''), 51 | const Locale('bn', ''), 52 | ]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/app/network/dio_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_getx_template/flavors/environment.dart'; 3 | 4 | import '/app/network/pretty_dio_logger.dart'; 5 | import '/app/network/request_headers.dart'; 6 | import '/flavors/build_config.dart'; 7 | 8 | class DioProvider { 9 | static final String baseUrl = BuildConfig.instance.config.baseUrl; 10 | 11 | static Dio? _instance; 12 | 13 | static const int _maxLineWidth = 90; 14 | static final _prettyDioLogger = PrettyDioLogger( 15 | requestHeader: true, 16 | requestBody: true, 17 | responseBody: BuildConfig.instance.environment == Environment.DEVELOPMENT, 18 | responseHeader: false, 19 | error: true, 20 | compact: true, 21 | maxWidth: _maxLineWidth); 22 | 23 | static final BaseOptions _options = BaseOptions( 24 | baseUrl: baseUrl, 25 | connectTimeout: const Duration(seconds: 60), 26 | receiveTimeout: const Duration(seconds: 60), 27 | ); 28 | 29 | static Dio get httpDio { 30 | if (_instance == null) { 31 | _instance = Dio(_options); 32 | 33 | _instance!.interceptors.add(_prettyDioLogger); 34 | 35 | return _instance!; 36 | } else { 37 | _instance!.interceptors.clear(); 38 | _instance!.interceptors.add(_prettyDioLogger); 39 | 40 | return _instance!; 41 | } 42 | } 43 | 44 | ///returns a Dio client with Access token in header 45 | static Dio get tokenClient { 46 | _addInterceptors(); 47 | 48 | return _instance!; 49 | } 50 | 51 | ///returns a Dio client with Access token in header 52 | ///Also adds a token refresh interceptor which retry the request when it's unauthorized 53 | static Dio get dioWithHeaderToken { 54 | _addInterceptors(); 55 | 56 | return _instance!; 57 | } 58 | 59 | static _addInterceptors() { 60 | _instance ??= httpDio; 61 | _instance!.interceptors.clear(); 62 | _instance!.interceptors.add(RequestHeaderInterceptor()); 63 | _instance!.interceptors.add(_prettyDioLogger); 64 | } 65 | 66 | static String _buildContentType(String version) { 67 | return "user_defined_content_type+$version"; 68 | } 69 | 70 | DioProvider.setContentType(String version) { 71 | _instance?.options.contentType = _buildContentType(version); 72 | } 73 | 74 | DioProvider.setContentTypeApplicationJson() { 75 | _instance?.options.contentType = "application/json"; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/app/network/dio_request_retrier.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:get/get.dart' as getx; 3 | 4 | import '/app/data/local/preference/preference_manager.dart'; 5 | import '/app/network/dio_provider.dart'; 6 | 7 | class DioRequestRetrier { 8 | final dioClient = DioProvider.tokenClient; 9 | final RequestOptions requestOptions; 10 | 11 | final PreferenceManager _preferenceManager = 12 | getx.Get.find(tag: (PreferenceManager).toString()); 13 | 14 | DioRequestRetrier({required this.requestOptions}); 15 | 16 | Future> retry() async { 17 | var header = await getCustomHeaders(); 18 | 19 | return await dioClient.request( 20 | requestOptions.path, 21 | cancelToken: requestOptions.cancelToken, 22 | data: requestOptions.data, 23 | queryParameters: requestOptions.queryParameters, 24 | onReceiveProgress: requestOptions.onReceiveProgress, 25 | onSendProgress: requestOptions.onSendProgress, 26 | options: Options(headers: header, method: requestOptions.method), 27 | ); 28 | } 29 | 30 | Future> getCustomHeaders() async { 31 | final String accessToken = 32 | await _preferenceManager.getString(PreferenceManager.keyToken); 33 | var customHeaders = {'content-type': 'application/json'}; 34 | if (accessToken.trim().isNotEmpty) { 35 | customHeaders.addAll({ 36 | 'Authorization': "Bearer $accessToken", 37 | }); 38 | } 39 | 40 | return customHeaders; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/app/network/error_handlers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | 6 | import '/app/network/exceptions/api_exception.dart'; 7 | import '/app/network/exceptions/app_exception.dart'; 8 | import '/app/network/exceptions/network_exception.dart'; 9 | import '/app/network/exceptions/not_found_exception.dart'; 10 | import '/app/network/exceptions/service_unavailable_exception.dart'; 11 | import '/flavors/build_config.dart'; 12 | 13 | Exception handleError(String error) { 14 | final logger = BuildConfig.instance.config.logger; 15 | logger.e("Generic exception: $error"); 16 | 17 | return AppException(message: error); 18 | } 19 | 20 | Exception handleDioError(DioException dioError) { 21 | switch (dioError.type) { 22 | case DioExceptionType.cancel: 23 | return AppException(message: "Request to API server was cancelled"); 24 | case DioExceptionType.connectionTimeout: 25 | return AppException(message: "Connection timeout with API server"); 26 | case DioExceptionType.connectionError: 27 | return NetworkException("There is no internet connection"); 28 | case DioExceptionType.receiveTimeout: 29 | return TimeoutException("Receive timeout in connection with API server"); 30 | case DioExceptionType.sendTimeout: 31 | return TimeoutException("Send timeout in connection with API server"); 32 | case DioExceptionType.badResponse: 33 | return _parseDioErrorResponse(dioError); 34 | case DioExceptionType.badCertificate: 35 | return AppException(message: 'Bad certificate'); 36 | case DioExceptionType.unknown: 37 | return NetworkException("There is no internet connection"); 38 | } 39 | } 40 | 41 | Exception _parseDioErrorResponse(DioException dioError) { 42 | final logger = BuildConfig.instance.config.logger; 43 | 44 | int statusCode = dioError.response?.statusCode ?? -1; 45 | String? status; 46 | String? serverMessage; 47 | 48 | try { 49 | if (statusCode == -1 || statusCode == HttpStatus.ok) { 50 | statusCode = dioError.response?.data["statusCode"]; 51 | } 52 | status = dioError.response?.data["status"]; 53 | serverMessage = dioError.response?.data["message"]; 54 | } catch (e, s) { 55 | logger.i("$e"); 56 | logger.i(s.toString()); 57 | 58 | serverMessage = "Something went wrong. Please try again later."; 59 | } 60 | 61 | switch (statusCode) { 62 | case HttpStatus.serviceUnavailable: 63 | return ServiceUnavailableException("Service Temporarily Unavailable"); 64 | case HttpStatus.notFound: 65 | return NotFoundException(serverMessage ?? "", status ?? ""); 66 | default: 67 | return ApiException( 68 | httpCode: statusCode, 69 | status: status ?? "", 70 | message: serverMessage ?? ""); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/api_exception.dart: -------------------------------------------------------------------------------- 1 | import '/app/network/exceptions/base_api_exception.dart'; 2 | 3 | class ApiException extends BaseApiException { 4 | ApiException({ 5 | required int httpCode, 6 | required String status, 7 | String message = "", 8 | }) : super(httpCode: httpCode, status: status, message: message); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/app_exception.dart: -------------------------------------------------------------------------------- 1 | import '/app/network/exceptions/base_exception.dart'; 2 | 3 | class AppException extends BaseException { 4 | AppException({ 5 | String message = "", 6 | }) : super(message: message); 7 | } 8 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/base_api_exception.dart: -------------------------------------------------------------------------------- 1 | import '/app/network/exceptions/app_exception.dart'; 2 | 3 | abstract class BaseApiException extends AppException { 4 | final int httpCode; 5 | final String status; 6 | 7 | BaseApiException({this.httpCode = -1, this.status = "", String message = ""}) 8 | : super(message: message); 9 | } 10 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/base_exception.dart: -------------------------------------------------------------------------------- 1 | abstract class BaseException implements Exception { 2 | final String message; 3 | 4 | BaseException({this.message = ""}); 5 | } 6 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/json_format_exception.dart: -------------------------------------------------------------------------------- 1 | import '/app/network/exceptions/base_exception.dart'; 2 | 3 | class JsonFormatException extends BaseException { 4 | JsonFormatException(String message) : super(message: message); 5 | } 6 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/network_exception.dart: -------------------------------------------------------------------------------- 1 | import '/app/network/exceptions/base_exception.dart'; 2 | 3 | class NetworkException extends BaseException { 4 | NetworkException(String message) : super(message: message); 5 | } 6 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/not_found_exception.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import '/app/network/exceptions/api_exception.dart'; 4 | 5 | class NotFoundException extends ApiException { 6 | NotFoundException(String message, String status) 7 | : super(httpCode: HttpStatus.notFound, status: status, message: message); 8 | } 9 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/service_unavailable_exception.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import '/app/network/exceptions/base_api_exception.dart'; 4 | 5 | class ServiceUnavailableException extends BaseApiException { 6 | ServiceUnavailableException(String message) 7 | : super( 8 | httpCode: HttpStatus.serviceUnavailable, 9 | message: message, 10 | status: ""); 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/timeout_exception.dart: -------------------------------------------------------------------------------- 1 | import '/app/network/exceptions/base_exception.dart'; 2 | 3 | class TimeoutException extends BaseException { 4 | TimeoutException(String message) : super(message: message); 5 | } 6 | -------------------------------------------------------------------------------- /lib/app/network/exceptions/unauthorize_exception.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import '/app/network/exceptions/base_api_exception.dart'; 4 | 5 | class UnauthorizedException extends BaseApiException { 6 | UnauthorizedException(String message) 7 | : super( 8 | httpCode: HttpStatus.unauthorized, 9 | message: message, 10 | status: "unauthorized"); 11 | } 12 | -------------------------------------------------------------------------------- /lib/app/network/pretty_dio_logger.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | /// code copied from https://pub.dev/packages/pretty_dio_logger 6 | class PrettyDioLogger extends Interceptor { 7 | /// Print request [Options] 8 | final bool request; 9 | 10 | /// Print request header [Options.headers] 11 | final bool requestHeader; 12 | 13 | /// Print request data [Options.tribeCollectionData] 14 | final bool requestBody; 15 | 16 | /// Print [Response.data] 17 | final bool responseBody; 18 | 19 | /// Print [Response.headers] 20 | final bool responseHeader; 21 | 22 | /// Print error message 23 | final bool error; 24 | 25 | /// InitialTab count to logPrint json response 26 | static const int initialTab = 1; 27 | 28 | /// 1 tab length 29 | static const String tabStep = ' '; 30 | 31 | /// Print compact json response 32 | final bool compact; 33 | 34 | /// Width size per logPrint 35 | final int maxWidth; 36 | 37 | /// Log printer; defaults logPrint log to console. 38 | /// In flutter, you'd better use debugPrint. 39 | /// you can also write log in a file. 40 | void Function(Object object) logPrint; 41 | 42 | static const int defaultMaxWidth = 90; 43 | 44 | PrettyDioLogger({ 45 | this.request = true, 46 | this.requestHeader = false, 47 | this.requestBody = false, 48 | this.responseHeader = false, 49 | this.responseBody = true, 50 | this.error = true, 51 | this.maxWidth = defaultMaxWidth, 52 | this.compact = true, 53 | this.logPrint = print, 54 | }); 55 | 56 | @override 57 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 58 | if (request) { 59 | _printRequestHeader(options); 60 | } 61 | if (requestHeader) { 62 | _printMapAsTable(options.queryParameters, header: 'Query Parameters'); 63 | final requestHeaders = {}; 64 | requestHeaders.addAll(options.headers); 65 | requestHeaders['contentType'] = options.contentType?.toString(); 66 | requestHeaders['responseType'] = options.responseType.toString(); 67 | requestHeaders['followRedirects'] = options.followRedirects; 68 | requestHeaders['connectTimeout'] = options.connectTimeout; 69 | requestHeaders['receiveTimeout'] = options.receiveTimeout; 70 | _printMapAsTable(requestHeaders, header: 'Headers'); 71 | _printMapAsTable(options.extra, header: 'Extras'); 72 | } 73 | if (requestBody && options.method != 'GET') { 74 | final dynamic data = options.data; 75 | if (data != null) { 76 | if (data is Map) _printMapAsTable(options.data as Map?, header: 'Body'); 77 | if (data is FormData) { 78 | final formDataMap = {} 79 | ..addEntries(data.fields) 80 | ..addEntries(data.files); 81 | _printMapAsTable(formDataMap, header: 'Form data | ${data.boundary}'); 82 | } else { 83 | _printBlock(data.toString()); 84 | } 85 | } 86 | } 87 | super.onRequest(options, handler); 88 | } 89 | 90 | @override 91 | void onError(DioException err, ErrorInterceptorHandler handler) { 92 | if (error) { 93 | if (err.type == DioExceptionType.badResponse) { 94 | final uri = err.response?.requestOptions.uri; 95 | _printBoxed( 96 | header: 97 | 'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}', 98 | text: uri.toString()); 99 | if (err.response != null && err.response?.data != null) { 100 | logPrint('╔ ${err.type.toString()}'); 101 | _printResponse(err.response!); 102 | } 103 | _printLine('╚'); 104 | logPrint(''); 105 | } else { 106 | _printBoxed(header: 'DioError ║ ${err.type}', text: err.message); 107 | } 108 | } 109 | super.onError(err, handler); 110 | } 111 | 112 | @override 113 | void onResponse(Response response, ResponseInterceptorHandler handler) { 114 | _printResponseHeader(response); 115 | if (responseHeader) { 116 | final responseHeaders = {}; 117 | response.headers 118 | .forEach((k, list) => responseHeaders[k] = list.toString()); 119 | _printMapAsTable(responseHeaders, header: 'Headers'); 120 | } 121 | 122 | if (responseBody) { 123 | logPrint('╔ Body'); 124 | logPrint('║'); 125 | _printResponse(response); 126 | logPrint('║'); 127 | _printLine('╚'); 128 | } 129 | super.onResponse(response, handler); 130 | } 131 | 132 | void _printBoxed({String? header, String? text}) { 133 | logPrint(''); 134 | logPrint('╔╣ $header'); 135 | logPrint('║ $text'); 136 | _printLine('╚'); 137 | } 138 | 139 | void _printResponse(Response response) { 140 | if (response.data != null) { 141 | if (response.data is Map) { 142 | _printPrettyMap(response.data as Map); 143 | } else if (response.data is List) { 144 | logPrint('║${_indent()}['); 145 | _printList(response.data as List); 146 | logPrint('║${_indent()}['); 147 | } else { 148 | _printBlock(response.data.toString()); 149 | } 150 | } 151 | } 152 | 153 | void _printResponseHeader(Response response) { 154 | final uri = response.requestOptions.uri; 155 | final method = response.requestOptions.method; 156 | _printBoxed( 157 | header: 158 | 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}', 159 | text: uri.toString()); 160 | } 161 | 162 | void _printRequestHeader(RequestOptions options) { 163 | final uri = options.uri; 164 | final method = options.method; 165 | _printBoxed(header: 'Request ║ $method ', text: uri.toString()); 166 | } 167 | 168 | void _printLine([String pre = '', String suf = '╝']) => 169 | logPrint('$pre${'═' * maxWidth}$suf'); 170 | 171 | void _printKV(String? key, Object? v) { 172 | final pre = '╟ "$key": '; 173 | final msg = v.toString(); 174 | 175 | if (pre.length + msg.length > maxWidth) { 176 | logPrint(pre); 177 | _printBlock(msg); 178 | } else { 179 | logPrint('$pre$msg'); 180 | } 181 | } 182 | 183 | void _printBlock(String msg) { 184 | final lines = (msg.length / maxWidth).ceil(); 185 | for (var i = 0; i < lines; ++i) { 186 | logPrint((i >= 0 ? '║ ' : '') + 187 | msg.substring(i * maxWidth, 188 | math.min(i * maxWidth + maxWidth, msg.length))); 189 | } 190 | } 191 | 192 | String _indent([int tabCount = initialTab]) => tabStep * tabCount; 193 | 194 | void _printPrettyMap( 195 | Map data, { 196 | int tabs = initialTab, 197 | bool isListItem = false, 198 | bool isLast = false, 199 | }) { 200 | var _tabs = tabs; 201 | final isRoot = _tabs == initialTab; 202 | final initialIndent = _indent(_tabs); 203 | _tabs++; 204 | 205 | if (isRoot || isListItem) logPrint('║$initialIndent{'); 206 | 207 | data.keys.toList().asMap().forEach((index, dynamic key) { 208 | final isLast = index == data.length - 1; 209 | dynamic value = data[key]; 210 | if (value is String) { 211 | value = '"${value.toString().replaceAll(RegExp(r'(\r|\n)+'), " ")}"'; 212 | } 213 | if (value is Map) { 214 | if (compact && _canFlattenMap(value)) { 215 | logPrint('║${_indent(_tabs)} "$key": $value${!isLast ? ',' : ''}'); 216 | } else { 217 | logPrint('║${_indent(_tabs)} "$key": {'); 218 | _printPrettyMap(value, tabs: _tabs); 219 | } 220 | } else if (value is List) { 221 | if (compact && _canFlattenList(value)) { 222 | logPrint('║${_indent(_tabs)} "$key": ${value.toString()}'); 223 | } else { 224 | logPrint('║${_indent(_tabs)} "$key": ['); 225 | _printList(value, tabs: _tabs); 226 | logPrint('║${_indent(_tabs)} ]${isLast ? '' : ','}'); 227 | } 228 | } else { 229 | final msg = value.toString().replaceAll('\n', ''); 230 | final indent = _indent(_tabs); 231 | final linWidth = maxWidth - indent.length; 232 | if (msg.length + indent.length > linWidth) { 233 | final lines = (msg.length / linWidth).ceil(); 234 | for (var i = 0; i < lines; ++i) { 235 | logPrint( 236 | '║${_indent(_tabs)} ${msg.substring(i * linWidth, math.min(i * linWidth + linWidth, msg.length))}'); 237 | } 238 | } else { 239 | logPrint('║${_indent(_tabs)} "$key": $msg${!isLast ? ',' : ''}'); 240 | } 241 | } 242 | }); 243 | 244 | logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}'); 245 | } 246 | 247 | void _printList(List list, {int tabs = initialTab}) { 248 | int tabsCount = 2; 249 | list.asMap().forEach((i, dynamic e) { 250 | final isLast = i == list.length - 1; 251 | if (e is Map) { 252 | if (compact && _canFlattenMap(e)) { 253 | logPrint('║${_indent(tabs)} $e${!isLast ? ',' : ''}'); 254 | } else { 255 | _printPrettyMap(e, tabs: tabs + 1, isListItem: true, isLast: isLast); 256 | } 257 | } else { 258 | logPrint('║${_indent(tabs + tabsCount)} $e${isLast ? '' : ','}'); 259 | } 260 | }); 261 | } 262 | 263 | bool _canFlattenMap(Map map) { 264 | return map.values 265 | .where((dynamic val) => val is Map || val is List) 266 | .isEmpty && 267 | map.toString().length < maxWidth; 268 | } 269 | 270 | bool _canFlattenList(List list) { 271 | int maxListLength = 10; 272 | 273 | return list.length < maxListLength && list.toString().length < maxWidth; 274 | } 275 | 276 | void _printMapAsTable(Map? map, {String? header}) { 277 | if (map == null || map.isEmpty) return; 278 | logPrint('╔ $header '); 279 | map.forEach( 280 | (dynamic key, dynamic value) => _printKV(key.toString(), value)); 281 | _printLine('╚'); 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /lib/app/network/request_headers.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | class RequestHeaderInterceptor extends InterceptorsWrapper { 4 | @override 5 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 6 | getCustomHeaders().then((customHeaders) { 7 | options.headers.addAll(customHeaders); 8 | super.onRequest(options, handler); 9 | }); 10 | } 11 | 12 | Future> getCustomHeaders() async { 13 | var customHeaders = {'content-type': 'application/json'}; 14 | 15 | return customHeaders; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/routes/app_pages.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '/app/modules/favorite/bindings/favorite_binding.dart'; 4 | import '/app/modules/favorite/views/favorite_view.dart'; 5 | import '/app/modules/home/bindings/home_binding.dart'; 6 | import '/app/modules/home/views/home_view.dart'; 7 | import '/app/modules/main/bindings/main_binding.dart'; 8 | import '/app/modules/main/views/main_view.dart'; 9 | import '/app/modules/other/bindings/other_binding.dart'; 10 | import '/app/modules/other/views/other_view.dart'; 11 | import '/app/modules/project_details/bindings/project_details_binding.dart'; 12 | import '/app/modules/project_details/views/project_details_view.dart'; 13 | import '/app/modules/settings/bindings/settings_binding.dart'; 14 | import '/app/modules/settings/views/settings_view.dart'; 15 | 16 | part 'app_routes.dart'; 17 | 18 | class AppPages { 19 | AppPages._(); 20 | 21 | static const INITIAL = Routes.MAIN; 22 | 23 | static final routes = [ 24 | GetPage( 25 | name: _Paths.MAIN, 26 | page: () => MainView(), 27 | binding: MainBinding(), 28 | ), 29 | GetPage( 30 | name: _Paths.HOME, 31 | page: () => HomeView(), 32 | binding: HomeBinding(), 33 | ), 34 | GetPage( 35 | name: _Paths.FAVORITE, 36 | page: () => FavoriteView(), 37 | binding: FavoriteBinding(), 38 | ), 39 | GetPage( 40 | name: _Paths.SETTINGS, 41 | page: () => SettingsView(), 42 | binding: SettingsBinding(), 43 | ), 44 | GetPage( 45 | name: _Paths.OTHER, 46 | page: () => OtherView(), 47 | binding: OtherBinding(), 48 | ), 49 | GetPage( 50 | name: _Paths.PROJECT_DETAILS, 51 | page: () => ProjectDetailsView(), 52 | binding: ProjectDetailsBinding(), 53 | ), 54 | ]; 55 | } 56 | -------------------------------------------------------------------------------- /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 | 7 | static const MAIN = _Paths.MAIN; 8 | static const HOME = _Paths.HOME; 9 | static const FAVORITE = _Paths.FAVORITE; 10 | static const SETTINGS = _Paths.SETTINGS; 11 | static const OTHER = _Paths.OTHER; 12 | static const PROJECT_DETAILS = _Paths.PROJECT_DETAILS; 13 | } 14 | 15 | abstract class _Paths { 16 | static const MAIN = '/main'; 17 | static const HOME = '/home'; 18 | static const FAVORITE = '/favorite'; 19 | static const SETTINGS = '/settings'; 20 | static const OTHER = '/other'; 21 | static const PROJECT_DETAILS = '/project-details'; 22 | } 23 | -------------------------------------------------------------------------------- /lib/flavors/build_config.dart: -------------------------------------------------------------------------------- 1 | import '/flavors/env_config.dart'; 2 | import '/flavors/environment.dart'; 3 | 4 | class BuildConfig { 5 | late final Environment environment; 6 | late final EnvConfig config; 7 | bool _lock = false; 8 | 9 | static final BuildConfig instance = BuildConfig._internal(); 10 | 11 | BuildConfig._internal(); 12 | 13 | factory BuildConfig.instantiate({ 14 | required Environment envType, 15 | required EnvConfig envConfig, 16 | }) { 17 | if (instance._lock) return instance; 18 | 19 | instance.environment = envType; 20 | instance.config = envConfig; 21 | instance._lock = true; 22 | 23 | return instance; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/flavors/env_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:logger/logger.dart'; 2 | 3 | import '/app/core/values/app_values.dart'; 4 | 5 | class EnvConfig { 6 | final String appName; 7 | final String baseUrl; 8 | final bool shouldCollectCrashLog; 9 | 10 | late final Logger logger; 11 | 12 | EnvConfig({ 13 | required this.appName, 14 | required this.baseUrl, 15 | this.shouldCollectCrashLog = false, 16 | }) { 17 | logger = Logger( 18 | printer: PrettyPrinter( 19 | methodCount: AppValues.loggerMethodCount, 20 | // number of method calls to be displayed 21 | errorMethodCount: AppValues.loggerErrorMethodCount, 22 | // number of method calls if stacktrace is provided 23 | lineLength: AppValues.loggerLineLength, 24 | // width of the output 25 | colors: true, 26 | // Colorful log messages 27 | printEmojis: true, 28 | // Print an emoji for each log message 29 | printTime: false // Should each log print contain a timestamp 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/flavors/environment.dart: -------------------------------------------------------------------------------- 1 | enum Environment { DEVELOPMENT, PRODUCTION } 2 | -------------------------------------------------------------------------------- /lib/l10n/app_bn.arb: -------------------------------------------------------------------------------- 1 | { 2 | "bottomNavHome": "হোম", 3 | "bottomNavFavorite": "ফেভারিট", 4 | "bottomNavSettings": "সেটিংস", 5 | "titleOtherView": "অন্যান্য", 6 | "settingsTheme": "থিম", 7 | "settingsLanguage": "ভাষা", 8 | "settingsFontSize": "অক্ষরের আকার" 9 | } -------------------------------------------------------------------------------- /lib/l10n/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "bottomNavHome": "Home", 3 | "bottomNavFavorite": "Favorite", 4 | "bottomNavSettings": "Settings", 5 | "titleOtherView": "OtherView", 6 | "settingsTheme": "Theme", 7 | "settingsLanguage": "Language", 8 | "settingsFontSize": "Font Size" 9 | } -------------------------------------------------------------------------------- /lib/main_dev.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/my_app.dart'; 4 | import '/flavors/build_config.dart'; 5 | import '/flavors/env_config.dart'; 6 | import '/flavors/environment.dart'; 7 | 8 | void main() { 9 | EnvConfig devConfig = EnvConfig( 10 | appName: "Flutter GetX Template Dev", 11 | baseUrl: "https://api.github.com", 12 | shouldCollectCrashLog: true, 13 | ); 14 | 15 | BuildConfig.instantiate( 16 | envType: Environment.DEVELOPMENT, 17 | envConfig: devConfig, 18 | ); 19 | 20 | runApp(const MyApp()); 21 | } 22 | -------------------------------------------------------------------------------- /lib/main_prod.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '/app/my_app.dart'; 4 | import '/flavors/build_config.dart'; 5 | import '/flavors/env_config.dart'; 6 | import '/flavors/environment.dart'; 7 | 8 | void main() { 9 | EnvConfig prodConfig = EnvConfig( 10 | appName: "Flutter GetX Template Prod", 11 | baseUrl: "https://api.github.com", 12 | shouldCollectCrashLog: true, 13 | ); 14 | 15 | BuildConfig.instantiate( 16 | envType: Environment.PRODUCTION, 17 | envConfig: prodConfig, 18 | ); 19 | 20 | runApp(const MyApp()); 21 | } 22 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "61.0.0" 12 | analyzer: 13 | dependency: "direct dev" 14 | description: 15 | name: analyzer 16 | sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "5.13.0" 20 | analyzer_plugin: 21 | dependency: transitive 22 | description: 23 | name: analyzer_plugin 24 | sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "0.11.2" 28 | animations: 29 | dependency: "direct main" 30 | description: 31 | name: animations 32 | sha256: ef57563eed3620bd5d75ad96189846aca1e033c0c45fc9a7d26e80ab02b88a70 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.0.8" 36 | ansicolor: 37 | dependency: transitive 38 | description: 39 | name: ansicolor 40 | sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.0.2" 44 | args: 45 | dependency: transitive 46 | description: 47 | name: args 48 | sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "2.4.2" 52 | async: 53 | dependency: transitive 54 | description: 55 | name: async 56 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.11.0" 60 | boolean_selector: 61 | dependency: transitive 62 | description: 63 | name: boolean_selector 64 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "2.1.1" 68 | cached_network_image: 69 | dependency: "direct main" 70 | description: 71 | name: cached_network_image 72 | sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "3.3.0" 76 | cached_network_image_platform_interface: 77 | dependency: transitive 78 | description: 79 | name: cached_network_image_platform_interface 80 | sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "3.0.0" 84 | cached_network_image_web: 85 | dependency: transitive 86 | description: 87 | name: cached_network_image_web 88 | sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "1.1.0" 92 | change_app_package_name: 93 | dependency: "direct dev" 94 | description: 95 | name: change_app_package_name 96 | sha256: f9ebaf68a4b5a68c581492579bb68273c523ef325fbf9ce2f1b57fb136ad023b 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "1.1.0" 100 | characters: 101 | dependency: transitive 102 | description: 103 | name: characters 104 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "1.3.0" 108 | clock: 109 | dependency: transitive 110 | description: 111 | name: clock 112 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "1.1.1" 116 | collection: 117 | dependency: transitive 118 | description: 119 | name: collection 120 | sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "1.17.2" 124 | convert: 125 | dependency: transitive 126 | description: 127 | name: convert 128 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "3.1.1" 132 | crypto: 133 | dependency: transitive 134 | description: 135 | name: crypto 136 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "3.0.3" 140 | csslib: 141 | dependency: transitive 142 | description: 143 | name: csslib 144 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "1.0.0" 148 | cupertino_icons: 149 | dependency: "direct main" 150 | description: 151 | name: cupertino_icons 152 | sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "1.0.6" 156 | dart_code_metrics: 157 | dependency: "direct dev" 158 | description: 159 | name: dart_code_metrics 160 | sha256: "3dede3f7abc077a4181ec7445448a289a9ce08e2981e6a4d49a3fb5099d47e1f" 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "5.7.6" 164 | dart_code_metrics_presets: 165 | dependency: transitive 166 | description: 167 | name: dart_code_metrics_presets 168 | sha256: b71eadf02a3787ebd5c887623f83f6fdc204d45c75a081bd636c4104b3fd8b73 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "1.8.0" 172 | dart_style: 173 | dependency: transitive 174 | description: 175 | name: dart_style 176 | sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "2.3.2" 180 | dio: 181 | dependency: "direct main" 182 | description: 183 | name: dio 184 | sha256: "417e2a6f9d83ab396ec38ff4ea5da6c254da71e4db765ad737a42af6930140b7" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "5.3.3" 188 | fake_async: 189 | dependency: transitive 190 | description: 191 | name: fake_async 192 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "1.3.1" 196 | ffi: 197 | dependency: transitive 198 | description: 199 | name: ffi 200 | sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "2.1.0" 204 | file: 205 | dependency: transitive 206 | description: 207 | name: file 208 | sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "6.1.4" 212 | flutter: 213 | dependency: "direct main" 214 | description: flutter 215 | source: sdk 216 | version: "0.0.0" 217 | flutter_cache_manager: 218 | dependency: transitive 219 | description: 220 | name: flutter_cache_manager 221 | sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "3.3.1" 225 | flutter_lints: 226 | dependency: "direct dev" 227 | description: 228 | name: flutter_lints 229 | sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "2.0.2" 233 | flutter_localizations: 234 | dependency: "direct main" 235 | description: flutter 236 | source: sdk 237 | version: "0.0.0" 238 | flutter_svg: 239 | dependency: "direct main" 240 | description: 241 | name: flutter_svg 242 | sha256: bfc7cc3c75fe1282e8ce2e056d8fd1533f1a6848b65c379b4a5e7a9b623d3371 243 | url: "https://pub.dev" 244 | source: hosted 245 | version: "2.0.8" 246 | flutter_test: 247 | dependency: "direct dev" 248 | description: flutter 249 | source: sdk 250 | version: "0.0.0" 251 | flutter_web_plugins: 252 | dependency: transitive 253 | description: flutter 254 | source: sdk 255 | version: "0.0.0" 256 | fluttertoast: 257 | dependency: "direct main" 258 | description: 259 | name: fluttertoast 260 | sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" 261 | url: "https://pub.dev" 262 | source: hosted 263 | version: "8.2.2" 264 | font_awesome_flutter: 265 | dependency: "direct main" 266 | description: 267 | name: font_awesome_flutter 268 | sha256: "52671aea66da73b58d42ec6d0912b727a42248dd9a7c76d6c20f275783c48c08" 269 | url: "https://pub.dev" 270 | source: hosted 271 | version: "10.6.0" 272 | get: 273 | dependency: "direct main" 274 | description: 275 | name: get 276 | sha256: e4e7335ede17452b391ed3b2ede016545706c01a02292a6c97619705e7d2a85e 277 | url: "https://pub.dev" 278 | source: hosted 279 | version: "4.6.6" 280 | glob: 281 | dependency: transitive 282 | description: 283 | name: glob 284 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 285 | url: "https://pub.dev" 286 | source: hosted 287 | version: "2.1.2" 288 | html: 289 | dependency: transitive 290 | description: 291 | name: html 292 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" 293 | url: "https://pub.dev" 294 | source: hosted 295 | version: "0.15.4" 296 | http: 297 | dependency: transitive 298 | description: 299 | name: http 300 | sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" 301 | url: "https://pub.dev" 302 | source: hosted 303 | version: "0.13.6" 304 | http_parser: 305 | dependency: transitive 306 | description: 307 | name: http_parser 308 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 309 | url: "https://pub.dev" 310 | source: hosted 311 | version: "4.0.2" 312 | intl: 313 | dependency: "direct main" 314 | description: 315 | name: intl 316 | sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" 317 | url: "https://pub.dev" 318 | source: hosted 319 | version: "0.18.1" 320 | json_annotation: 321 | dependency: transitive 322 | description: 323 | name: json_annotation 324 | sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 325 | url: "https://pub.dev" 326 | source: hosted 327 | version: "4.8.1" 328 | lints: 329 | dependency: transitive 330 | description: 331 | name: lints 332 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 333 | url: "https://pub.dev" 334 | source: hosted 335 | version: "2.1.1" 336 | logger: 337 | dependency: "direct main" 338 | description: 339 | name: logger 340 | sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" 341 | url: "https://pub.dev" 342 | source: hosted 343 | version: "2.0.2+1" 344 | matcher: 345 | dependency: transitive 346 | description: 347 | name: matcher 348 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 349 | url: "https://pub.dev" 350 | source: hosted 351 | version: "0.12.16" 352 | material_color_utilities: 353 | dependency: transitive 354 | description: 355 | name: material_color_utilities 356 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 357 | url: "https://pub.dev" 358 | source: hosted 359 | version: "0.5.0" 360 | meta: 361 | dependency: transitive 362 | description: 363 | name: meta 364 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 365 | url: "https://pub.dev" 366 | source: hosted 367 | version: "1.9.1" 368 | octo_image: 369 | dependency: transitive 370 | description: 371 | name: octo_image 372 | sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" 373 | url: "https://pub.dev" 374 | source: hosted 375 | version: "2.0.0" 376 | package_config: 377 | dependency: transitive 378 | description: 379 | name: package_config 380 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" 381 | url: "https://pub.dev" 382 | source: hosted 383 | version: "2.1.0" 384 | path: 385 | dependency: transitive 386 | description: 387 | name: path 388 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 389 | url: "https://pub.dev" 390 | source: hosted 391 | version: "1.8.3" 392 | path_parsing: 393 | dependency: transitive 394 | description: 395 | name: path_parsing 396 | sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf 397 | url: "https://pub.dev" 398 | source: hosted 399 | version: "1.0.1" 400 | path_provider: 401 | dependency: transitive 402 | description: 403 | name: path_provider 404 | sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa 405 | url: "https://pub.dev" 406 | source: hosted 407 | version: "2.1.1" 408 | path_provider_android: 409 | dependency: transitive 410 | description: 411 | name: path_provider_android 412 | sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 413 | url: "https://pub.dev" 414 | source: hosted 415 | version: "2.2.1" 416 | path_provider_foundation: 417 | dependency: transitive 418 | description: 419 | name: path_provider_foundation 420 | sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" 421 | url: "https://pub.dev" 422 | source: hosted 423 | version: "2.3.1" 424 | path_provider_linux: 425 | dependency: transitive 426 | description: 427 | name: path_provider_linux 428 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 429 | url: "https://pub.dev" 430 | source: hosted 431 | version: "2.2.1" 432 | path_provider_platform_interface: 433 | dependency: transitive 434 | description: 435 | name: path_provider_platform_interface 436 | sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" 437 | url: "https://pub.dev" 438 | source: hosted 439 | version: "2.1.1" 440 | path_provider_windows: 441 | dependency: transitive 442 | description: 443 | name: path_provider_windows 444 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" 445 | url: "https://pub.dev" 446 | source: hosted 447 | version: "2.2.1" 448 | petitparser: 449 | dependency: transitive 450 | description: 451 | name: petitparser 452 | sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 453 | url: "https://pub.dev" 454 | source: hosted 455 | version: "5.4.0" 456 | platform: 457 | dependency: transitive 458 | description: 459 | name: platform 460 | sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" 461 | url: "https://pub.dev" 462 | source: hosted 463 | version: "3.1.3" 464 | plugin_platform_interface: 465 | dependency: transitive 466 | description: 467 | name: plugin_platform_interface 468 | sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d 469 | url: "https://pub.dev" 470 | source: hosted 471 | version: "2.1.6" 472 | pretty_dio_logger: 473 | dependency: "direct main" 474 | description: 475 | name: pretty_dio_logger 476 | sha256: "00b80053063935cf9a6190da344c5373b9d0e92da4c944c878ff2fbef0ef6dc2" 477 | url: "https://pub.dev" 478 | source: hosted 479 | version: "1.3.1" 480 | process: 481 | dependency: transitive 482 | description: 483 | name: process 484 | sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" 485 | url: "https://pub.dev" 486 | source: hosted 487 | version: "4.2.4" 488 | pub_semver: 489 | dependency: transitive 490 | description: 491 | name: pub_semver 492 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" 493 | url: "https://pub.dev" 494 | source: hosted 495 | version: "2.1.4" 496 | pub_updater: 497 | dependency: transitive 498 | description: 499 | name: pub_updater 500 | sha256: "05ae70703e06f7fdeb05f7f02dd680b8aad810e87c756a618f33e1794635115c" 501 | url: "https://pub.dev" 502 | source: hosted 503 | version: "0.3.0" 504 | rxdart: 505 | dependency: transitive 506 | description: 507 | name: rxdart 508 | sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" 509 | url: "https://pub.dev" 510 | source: hosted 511 | version: "0.27.7" 512 | shared_preferences: 513 | dependency: "direct main" 514 | description: 515 | name: shared_preferences 516 | sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" 517 | url: "https://pub.dev" 518 | source: hosted 519 | version: "2.2.2" 520 | shared_preferences_android: 521 | dependency: transitive 522 | description: 523 | name: shared_preferences_android 524 | sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" 525 | url: "https://pub.dev" 526 | source: hosted 527 | version: "2.2.1" 528 | shared_preferences_foundation: 529 | dependency: transitive 530 | description: 531 | name: shared_preferences_foundation 532 | sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" 533 | url: "https://pub.dev" 534 | source: hosted 535 | version: "2.3.4" 536 | shared_preferences_linux: 537 | dependency: transitive 538 | description: 539 | name: shared_preferences_linux 540 | sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" 541 | url: "https://pub.dev" 542 | source: hosted 543 | version: "2.3.2" 544 | shared_preferences_platform_interface: 545 | dependency: transitive 546 | description: 547 | name: shared_preferences_platform_interface 548 | sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a 549 | url: "https://pub.dev" 550 | source: hosted 551 | version: "2.3.1" 552 | shared_preferences_web: 553 | dependency: transitive 554 | description: 555 | name: shared_preferences_web 556 | sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf 557 | url: "https://pub.dev" 558 | source: hosted 559 | version: "2.2.1" 560 | shared_preferences_windows: 561 | dependency: transitive 562 | description: 563 | name: shared_preferences_windows 564 | sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" 565 | url: "https://pub.dev" 566 | source: hosted 567 | version: "2.3.2" 568 | sky_engine: 569 | dependency: transitive 570 | description: flutter 571 | source: sdk 572 | version: "0.0.99" 573 | source_span: 574 | dependency: transitive 575 | description: 576 | name: source_span 577 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 578 | url: "https://pub.dev" 579 | source: hosted 580 | version: "1.10.0" 581 | sqflite: 582 | dependency: transitive 583 | description: 584 | name: sqflite 585 | sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" 586 | url: "https://pub.dev" 587 | source: hosted 588 | version: "2.3.0" 589 | sqflite_common: 590 | dependency: transitive 591 | description: 592 | name: sqflite_common 593 | sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" 594 | url: "https://pub.dev" 595 | source: hosted 596 | version: "2.5.0" 597 | stack_trace: 598 | dependency: transitive 599 | description: 600 | name: stack_trace 601 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 602 | url: "https://pub.dev" 603 | source: hosted 604 | version: "1.11.0" 605 | stream_channel: 606 | dependency: transitive 607 | description: 608 | name: stream_channel 609 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 610 | url: "https://pub.dev" 611 | source: hosted 612 | version: "2.1.1" 613 | string_scanner: 614 | dependency: transitive 615 | description: 616 | name: string_scanner 617 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 618 | url: "https://pub.dev" 619 | source: hosted 620 | version: "1.2.0" 621 | synchronized: 622 | dependency: transitive 623 | description: 624 | name: synchronized 625 | sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" 626 | url: "https://pub.dev" 627 | source: hosted 628 | version: "3.1.0" 629 | term_glyph: 630 | dependency: transitive 631 | description: 632 | name: term_glyph 633 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 634 | url: "https://pub.dev" 635 | source: hosted 636 | version: "1.2.1" 637 | test_api: 638 | dependency: transitive 639 | description: 640 | name: test_api 641 | sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" 642 | url: "https://pub.dev" 643 | source: hosted 644 | version: "0.6.0" 645 | typed_data: 646 | dependency: transitive 647 | description: 648 | name: typed_data 649 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 650 | url: "https://pub.dev" 651 | source: hosted 652 | version: "1.3.2" 653 | uuid: 654 | dependency: transitive 655 | description: 656 | name: uuid 657 | sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" 658 | url: "https://pub.dev" 659 | source: hosted 660 | version: "3.0.7" 661 | vector_graphics: 662 | dependency: transitive 663 | description: 664 | name: vector_graphics 665 | sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" 666 | url: "https://pub.dev" 667 | source: hosted 668 | version: "1.1.9+1" 669 | vector_graphics_codec: 670 | dependency: transitive 671 | description: 672 | name: vector_graphics_codec 673 | sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" 674 | url: "https://pub.dev" 675 | source: hosted 676 | version: "1.1.9+1" 677 | vector_graphics_compiler: 678 | dependency: transitive 679 | description: 680 | name: vector_graphics_compiler 681 | sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 682 | url: "https://pub.dev" 683 | source: hosted 684 | version: "1.1.9+1" 685 | vector_math: 686 | dependency: transitive 687 | description: 688 | name: vector_math 689 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 690 | url: "https://pub.dev" 691 | source: hosted 692 | version: "2.1.4" 693 | watcher: 694 | dependency: transitive 695 | description: 696 | name: watcher 697 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 698 | url: "https://pub.dev" 699 | source: hosted 700 | version: "1.1.0" 701 | web: 702 | dependency: transitive 703 | description: 704 | name: web 705 | sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 706 | url: "https://pub.dev" 707 | source: hosted 708 | version: "0.1.4-beta" 709 | win32: 710 | dependency: transitive 711 | description: 712 | name: win32 713 | sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" 714 | url: "https://pub.dev" 715 | source: hosted 716 | version: "5.0.9" 717 | xdg_directories: 718 | dependency: transitive 719 | description: 720 | name: xdg_directories 721 | sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" 722 | url: "https://pub.dev" 723 | source: hosted 724 | version: "1.0.3" 725 | xml: 726 | dependency: transitive 727 | description: 728 | name: xml 729 | sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" 730 | url: "https://pub.dev" 731 | source: hosted 732 | version: "6.3.0" 733 | yaml: 734 | dependency: transitive 735 | description: 736 | name: yaml 737 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 738 | url: "https://pub.dev" 739 | source: hosted 740 | version: "3.1.2" 741 | sdks: 742 | dart: ">=3.1.0 <4.0.0" 743 | flutter: ">=3.13.0" 744 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_getx_template 2 | description: Boilarplate template of Flutter GetX. 3 | publish_to: none 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: '>=3.1.0 <4.0.0' 8 | flutter: "3.13.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | animations: 2.0.8 14 | cached_network_image: 3.3.0 15 | cupertino_icons: 1.0.6 16 | dio: 5.3.3 17 | font_awesome_flutter: 10.6.0 18 | flutter_localizations: 19 | sdk: flutter 20 | fluttertoast: 8.2.2 21 | flutter_svg: 2.0.8 22 | get: 4.6.6 23 | intl: 0.18.1 24 | logger: 2.0.2+1 25 | pretty_dio_logger: 1.3.1 26 | shared_preferences: 2.2.2 27 | 28 | dev_dependencies: 29 | flutter_test: 30 | sdk: flutter 31 | flutter_lints: 2.0.2 32 | analyzer: 5.13.0 33 | dart_code_metrics: any 34 | change_app_package_name: 1.1.0 35 | 36 | flutter: 37 | generate: true 38 | uses-material-design: true 39 | assets: 40 | - images/ 41 | fonts: 42 | - family: Roboto 43 | fonts: 44 | - asset: fonts/Roboto-Thin.ttf 45 | weight: 200 46 | - asset: fonts/Roboto-Light.ttf 47 | weight: 200 48 | - asset: fonts/Roboto-Regular.ttf 49 | weight: 200 50 | - asset: fonts/Roboto-Medium.ttf 51 | weight: 300 52 | - asset: fonts/Roboto-Bold.ttf 53 | weight: 600 54 | - asset: fonts/Roboto-Italic.ttf 55 | style: italic 56 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Why? (ticket link or issue description) 2 | 3 | 4 | ## What was done and how? 5 | 6 | 7 | ## Anything special? (optional) 8 | -------------------------------------------------------------------------------- /readme_configuration_guideline.md: -------------------------------------------------------------------------------- 1 | # How to configure and run this project? 2 | 3 | **Step 1:** First of all click "Add/Edit Configurations" as like image 1: 4 | 5 | 1 6 | 7 | **Step 2:** Choose "Flutter" from configuration list and select your Dart entrypoint path according 8 | to your flavor then give a name and allow dart support for this project. See on Image 2, 3 and 4: 9 | 10 | ![Select Flutter configuration option](repo_data/flutter_getx_template_2.png) 11 | 12 | ![Set 'dev' as build](https://user-images.githubusercontent.com/3769029/137329967-6a2421ce-98c7-4f42-9393-8817b2607678.png) 13 | 14 | ![Choose path to main_dev.dart](https://user-images.githubusercontent.com/3769029/137330462-303463b1-6b00-4755-9b96-a04d4546df4d.png) 15 | 16 | **Step 3:** Create another configuration for prod following two steps above: 17 | 18 | ![Prod configuration](https://user-images.githubusercontent.com/3769029/137330499-ff822737-943a-493d-932e-09eb8afa9414.png) 19 | 20 | N.B: In this step you may need to setup your dart sdk path. If you get warned for 21 | "dart sdk not found in specified location" then just click on "fix" and select your dart sdk path. 22 | 23 | # How to configure and make android release build for production 24 | Release build can be made using `flutter build apk` command. Before that create a **key.properties** file in the **android** folder. Add folloiwng lines to that file: 25 | ``` 26 | keyAlias= 27 | password= 28 | storeFile= 29 | ``` 30 | Then run `flutter build appbundle -t lib/main_prod.dart --flavor prod` to generate app bundle (.aab) file. To generate .apk file, run `flutter build apk -t lib/main_prod.dart --flavor prod`. 31 | 32 | For more details, please read through guide to build and relase android app documentation from [this link](https://docs.flutter.dev/deployment/android). -------------------------------------------------------------------------------- /repo_data/flutter_getx_template_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/repo_data/flutter_getx_template_1.png -------------------------------------------------------------------------------- /repo_data/flutter_getx_template_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/repo_data/flutter_getx_template_2.png -------------------------------------------------------------------------------- /repo_data/flutter_getx_template_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/repo_data/flutter_getx_template_3.png -------------------------------------------------------------------------------- /repo_data/flutter_getx_template_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/repo_data/flutter_getx_template_4.png -------------------------------------------------------------------------------- /repo_data/mvvm_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/repo_data/mvvm_flow.png -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/test/widget_test.dart -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasancse91/flutter_getx_template/f1ebf2f6584497ab22313e8b17993ebd0f7a0a49/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | flutter_getx_template 27 | 28 | 29 | 30 | 33 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_getx_template", 3 | "short_name": "flutter_getx_template", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | --------------------------------------------------------------------------------