diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 00000000..e603b910 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,141 @@ +on: + # action trigger + pull_request: + branches: + # - main + # - release/** + - develop + # action trigger + push: + branches: + # - main + # - release/** + - develop +name: "Build & Release" +jobs: +# build_ios: +# name: Build & Release IOS +# runs-on: macos-latest +# +# steps: +# - uses: actions/checkout@v2 +# +# # - name: Select Xcode version +# # run: sudo xcode-select -s '/Applications/Xcode_11.3.app/Contents/Developer' +# +# - name: Bundle install +# run: cd ./ios && bundle install +# +# - name: Set up JDK +# uses: actions/setup-java@v1 +# with: +# java-version: '12.x' +# +# - uses: subosito/flutter-action@v1 +# with: +# flutter-version: '2.10.4' +# - name: Install tools +# run: | +# flutter pub get +# cd ./ios && pod install +# +# - name: Setup SSH Keys and known_hosts for fastlane match +# run: | +# SSH_PATH="$HOME/.ssh" +# mkdir -p "$SSH_PATH" +# touch "$HOME/.ssh/known_hosts" +# +# echo "$PRIVATE_KEY" > "$HOME/.ssh/id_rsa" +# +# chmod 700 "$HOME/.ssh" +# ssh-keyscan github.com >> ~/.ssh/known_hosts +# chmod 600 "$HOME/.ssh/known_hosts" +# chmod 600 "$HOME/.ssh/id_rsa" +# +# eval $(ssh-agent) +# ssh-add -K ~/.ssh/id_rsa +# env: +# PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} +# +# - name: Deploy to TestFlight +# run: | +# cd ./ios && bundle exec fastlane beta +# env: +# TEAM_ID: ${{ secrets.TEAM_ID }} +# ITC_TEAM_ID: ${{ secrets.ITC_TEAM_ID }} +# FASTLANE_USER: ${{ secrets.FASTLANE_USER }} +# FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} +# FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }} +# FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }} +# MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} +# MATCH_KEYCHAIN_NAME: ${{ secrets.MATCH_KEYCHAIN_NAME }} +# MATCH_KEYCHAIN_PASSWORD: ${{ secrets.MATCH_KEYCHAIN_PASSWORD }} +# DELIVER_ITMSTRANSPORTER_ADDITIONAL_UPLOAD_PARAMETERS: ${{ secrets.DELIVER_ITMSTRANSPORTER_ADDITIONAL_UPLOAD_PARAMETERS }} + + build_web: + name: Build Flutter (Web) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: subosito/flutter-action@v1 + with: + flutter-version: '2.10.4' + - run: flutter pub get + - run: flutter config --enable-web + - run: flutter build web + - name: Deploy to Firebase + uses: w9jds/firebase-action@master + with: + args: deploy --only hosting --public build/web + env: + FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + PROJECT_ID: default + + build_android: + name: Build & Release Android + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: '12.x' + - uses: subosito/flutter-action@v1 + with: + flutter-version: '2.10.4' + - run: flutter clean + - name: Cache pub dependencies + uses: actions/cache@v2 + with: + path: ${{ env.FLUTTER_HOME }}/.pub-cache + key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} + restore-keys: ${{ runner.os }}-pub- + - run: flutter pub get + - name: Download Android keystore + id: android_keystore + uses: timheuer/base64-to-file@v1.0.3 + with: + fileName: key.jks + encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + + - name: Create key.properties + run: | + echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties + echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> android/key.properties + echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties + echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties + - run: flutter test // when have unit test + + + - run: flutter build appbundle + - name: Deploy to Play Store + uses: r0adkll/upload-google-play@v1.0.17 + with: + serviceAccountJsonPlainText: ${{secrets.GOOGLE_SERVICE_ACCOUNT_KEY}} + packageName: ${{ secrets.ANDROID_PACKAGE_NAME }} + releaseFiles: build/app/outputs/bundle/release/app-release.aab + track: internal + status: draft + whatsNewDirectory: lib/whatsnew/ + +#push code to develop +#pull request on develop \ No newline at end of file diff --git a/.gitignore b/.gitignore index 933b40e2..84987eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,8 @@ build/ !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages .flutter-plugins-dependencies ios/Flutter/flutter_export_environment.sh + +# Generated mobx file +*.inject.summary +*.inject.dart +*.g.dart diff --git a/README.md b/README.md index 919ea3fe..ba81d5db 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,28 @@ A boilerplate project created in flutter using MobX and Provider. Boilerplate su * For Mobile: https://github.com/zubairehman/flutter-boilerplate-project/tree/master (stable channel) * For Web: https://github.com/zubairehman/flutter-boilerplate-project/tree/feature/web-support (beta channel) +# The difference with the original boilerplate + +1. Enhance navigation using the package `page_transition` for more customizable transitions. +2. Integrate Sentry for error reporting. +3. Add utils class to show common messages and dialogs. +4. Add Lint to the project and fix all the linting issues. +5. Enhance localization using the package `intl`. This will allow you to easily localize your app with more customization. +6. Simple folder structure when using firebase (Firestore). + +**Firebase:** + +Checkout branch `feature/firebase` for firebase support. + +# Contents +- [Getting Started](#getting-started) +- [How to Use](#how-to-use) +- [Hide Generated Files](#hide-generated-files) +- [Boilerplate Features](#boilerplate-features) +- [Wiki](#wiki) +- [Conclusion](#conclusion) +- [Setting CI-CD](#setting-ci-cd) + ## Getting Started The Boilerplate contains the minimal implementation required to create a new library or project. The repository code is preloaded with some basic components like basic app architecture, app theme, constants and required dependencies to create a new project. By using boiler plate code as standard initializer, we can have same patterns in all the projects that will inherit it. This will also help in reducing setup & development time by allowing you to use same code pattern and avoid re-writing from scratch. @@ -16,7 +38,7 @@ The Boilerplate contains the minimal implementation required to create a new lib Download or clone this repo by using the link below: ``` -https://github.com/zubairehman/flutter-boilerplate-project.git +https://github.com/kitemetric/flutter-boilerplate-project.git ``` **Step 2:** @@ -41,6 +63,18 @@ or watch command in order to keep the source code synced automatically: flutter packages pub run build_runner watch ``` +**Step 4: intl (l10n)** + +This project uses `l10n` library that works with code generation, execute the following command to generate files: + +``` +flutter gen-l10n +``` + +**Step 5: Setup Sentry** + +This project uses `Sentry` so you have to add Sentry DNS to be able to use Sentry in the project. Change the ```option.dns``` in `main.dart` or disable all Sentry related code in `main.dart` to disable Sentry. + ## Hide Generated Files In-order to hide generated files, navigate to `Android Studio` -> `Preferences` -> `Editor` -> `File Types` and paste the below lines under `ignore files and folders` section: @@ -56,7 +90,7 @@ In Visual Studio Code, navigate to `Preferences` -> `Settings` and search for `F **/*.g.dart ``` -## Boilerplate Features: +## Boilerplate Features * Splash * Login @@ -74,8 +108,9 @@ In Visual Studio Code, navigate to `Preferences` -> `Settings` and search for `F * Logging * Dependency Injection * Dark Theme Support (new) -* Multilingual Support (new) * Provider example (new) +* Sentry (new) +* intl (new) ### Up-Coming Features: @@ -88,6 +123,7 @@ In Visual Studio Code, navigate to `Preferences` -> `Settings` and search for `F * [Database](https://github.com/tekartik/sembast.dart) * [MobX](https://github.com/mobxjs/mobx.dart) (to connect the reactive data of your application with the UI) * [Provider](https://github.com/rrousselGit/provider) (State Management) +* [intl] (https://github.com/dart-lang/intl) * [Encryption](https://github.com/xxtea/xxtea-dart) * [Validation](https://github.com/dart-league/validators) * [Logging](https://github.com/zubairehman/Flogs) @@ -294,3 +330,92 @@ I will be happy to answer any questions that you may have on this approach, and Again to note, this is example can appear as over-architectured for what it is - but it is an example only. If you liked my work, don’t forget to ⭐ star the repo to show your support. + +## Setting CI-CD +Web-Android using github actions, iOS using Codemagic + +**Web and Android auto build and deploy/public to Firebase hosting/Internal Testing CH Play when trigger occurred, the only thing have to do is config env (only git repo owner can do that)** + +### Web +**Config env** + +You have to set the values below to GitHub secrets. [How to set the value to GitHub secrets.](https://github.com/Azure/actions-workflow-samples/blob/master/assets/create-secrets-for-GitHub-workflows.md) + +`FIREBASE_TOKEN` + +How to get FIREBASE_TOKEN: run cmd `firebase login:ci` + +### Android +You have to set the value to GitHub secrets. [How to set the value to GitHub secrets.](https://github.com/Azure/actions-workflow-samples/blob/master/assets/create-secrets-for-GitHub-workflows.md) + +`ANDROID_KEYSTORE_BASE64` + +`ANDROID_KEYSTORE_PASSWORD` + +`ANDROID_KEY_PASSWORD` + +`ANDROID_KEY_ALIAS` + +`GOOGLE_SERVICE_ACCOUNT_KEY` + +`ANDROID_PACKAGE_NAME` + +[How to get the above values.](https://viblo.asia/p/cai-dat-don-gian-automating-publishing-flutter-app-len-google-play-bang-github-actions-63vKjg1dZ2R) + +### iOS +To automatic build and deploy new version to TestFlight, we using **CodeMagic** + +**Step 1: Register** + +Register your app on Codemagic, fetch your codemagic.yaml to Codemagic + +**Step 2: Setup** + +*Environment*: need to provide the following values: +```yaml + PROVISIONING_PROFILE: # A file prefixed with .mobileprovision + CERTIFICATE: #A file prefixed with .p12 + CERTIFICATE_PASSWORD: #When you create a certificate, it will include a password for this certificate, remember it + flutter: #Target flutter version + xcode: #Target xcode version + cocoapods: #Target cocoapods version +``` +[What's Provision Profile and How to get?](https://viblo.asia/p/phan-3-provisioning-profiles-gAm5yjqLKdb) + +[What's Certificate and How to get?](https://stackoverflow.com/questions/9418661/how-to-create-p12-certificate-for-ios-distribution) + +*Trigger*: When the trigger occurs, the application will automatically build +```yaml +triggering: + events: + - tag + branch_patterns: + - pattern: '*' + include: true + source: true + tag_patterns: + - pattern: release-production* + include: true +``` +Current trigger: push tag with tag's name: `release-production*` or `release-staging*`, `*` can be version name like `-v2.1.0` + +*Publishing*: need to provide some information to be able to automatically publish the app to testflight +```yaml + publishing: + # have to config + app_store_connect: + api_key: + key_id: + issuer_id: + submit_to_testflight: false + submit_to_app_store: false +``` +Only role `ADMIN` in `User and Access` can create new api key and get them [Setup App Store Connect API key](https://support.appmachine.com/support/solutions/articles/80001023345-v2-how-to-setup-app-store-connect-api-key) + +However, you still have to manual the last step at testflight to roll out for testers. + + +CodeMagic provides 2 ways for setup the workflow, above I mentioned how to use .yaml file to write workflow. +In addition, you can refer to how to use Workflow Editor to create your workflow at [Easy to setup CodeMagic!](https://docs.codemagic.io/yaml-quick-start/building-a-flutter-app/) + + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..5fcdedab --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,33 @@ +# This file configures the analyzer to use the lint rule set from `package:lint` + +# For apps, use the default set +include: package:lint/analysis_options.yaml + +# Packages, that may be distributed (i.e. via pub.dev) should use the package +# version, resulting in a better pub score. +# include: package:lint/analysis_options_package.yaml + +# You might want to exclude auto-generated files from dart analysis +analyzer: + exclude: + - '**.g.dart' + +# You can customize the lint rules set to your own liking. A list of all rules +# can be found at https://dart-lang.github.io/linter/lints/options/options.html +linter: + rules: + # Util classes are awesome! + throw_of_invalid_type: false + avoid_classes_with_only_static_members: false + require_trailing_commas: false + sort_pub_dependencies: false + use_setters_to_change_properties: false + avoid_print: false + constant_identifier_names: false + + # Make constructors the first thing in every class + # sort_constructors_first: true + + # Choose wisely, but you don't have to + # prefer_double_quotes: true + # prefer_single_quotes: true \ No newline at end of file diff --git a/assets/lang/da.json b/assets/lang/app_da.arb similarity index 87% rename from assets/lang/da.json rename to assets/lang/app_da.arb index 064bb1a1..20b8fa7d 100644 --- a/assets/lang/da.json +++ b/assets/lang/app_da.arb @@ -1,4 +1,8 @@ { + "message_success": "Succes", + "message_error": "Fejl", + "message_information": "Information", + "login_start": "Below are list of strings for login da ", "login_et_user_email": "Brugernavn", "login_et_user_password": "Adgangskode", diff --git a/assets/lang/en.json b/assets/lang/app_en.arb similarity index 87% rename from assets/lang/en.json rename to assets/lang/app_en.arb index 1be1f370..f1699347 100644 --- a/assets/lang/en.json +++ b/assets/lang/app_en.arb @@ -1,4 +1,8 @@ { + "message_success": "Success", + "message_error": "Error", + "message_information": "Information", + "login_start": "Below are list of strings for login", "login_et_user_email": "Enter user email", "login_et_user_password": "Enter password", diff --git a/assets/lang/es.json b/assets/lang/app_es.arb similarity index 88% rename from assets/lang/es.json rename to assets/lang/app_es.arb index d4fc47f6..b134c115 100644 --- a/assets/lang/es.json +++ b/assets/lang/app_es.arb @@ -1,4 +1,8 @@ { + "message_success": "Éxito", + "message_error": "Error", + "message_information": "Información", + "login_start": "Below are list of strings for login es ", "login_et_user_email": "Ingrese el correo electrónico del usuario", "login_et_user_password": "Introducir la contraseña", diff --git a/codemagic.yaml b/codemagic.yaml new file mode 100644 index 00000000..fe6cde12 --- /dev/null +++ b/codemagic.yaml @@ -0,0 +1,101 @@ + +workflows: + workflow-for-main: + name: Workflow For Main + max_build_duration: 60 + environment: # have to config + vars: + PROVISIONING_PROFILE: + CERTIFICATE: + CERTIFICATE_PASSWORD: + flutter: 2.10.4 + xcode: '13.3' + cocoapods: 1.11.3 + triggering: # have to config, due to your team, the config below is trigger on push tag with tag name: release-production* + events: + - tag + branch_patterns: + - pattern: '*' + include: true + source: true + tag_patterns: + - pattern: release-production* + include: true + scripts: + - flutter packages pub get + - find . -name "Podfile" -execdir pod install \; + - keychain initialize + - | + # set up provisioning profiles + PROFILE_PATH="$(mktemp "$HOME/Library/MobileDevice/Provisioning Profiles"/ios.mobileprovision)" + echo "$PROVISIONING_PROFILE" | base64 --decode > "$PROFILE_PATH" + echo "Saved provisioning profile $PROFILE_PATH" + - | + # set up ios signing certificate + echo $CERTIFICATE | base64 --decode > '/tmp/certificate.p12' + keychain add-certificates --certificate '/tmp/certificate.p12' --certificate-password $CERTIFICATE_PASSWORD + - xcode-project use-profiles + - flutter build ipa --export-options-plist ~/export_options.plist + artifacts: + - build/ios/ipa/*.ipa + - /tmp/xcodebuild_logs/*.log + - '*.snap' + - build/windows/**/*.msix + - flutter_drive.log + publishing: + # have to config + app_store_connect: + api_key: + key_id: + issuer_id: + submit_to_testflight: false + submit_to_app_store: false + workflow-for-develop: + name: Workflow For Develop + max_build_duration: 60 + environment: + vars: + ROVISIONING_PROFILE: + CERTIFICATE: + CERTIFICATE_PASSWORD: + flutter: 2.10.4 + xcode: '13.3' + cocoapods: 1.11.3 + triggering: + events: + - tag + branch_patterns: + - pattern: '*' + include: true + source: true + tag_patterns: + - pattern: release-staging* + include: true + scripts: + - flutter packages pub get + - find . -name "Podfile" -execdir pod install \; + - keychain initialize + - | + # set up provisioning profiles + PROFILE_PATH="$(mktemp "$HOME/Library/MobileDevice/Provisioning Profiles"/ios.mobileprovision)" + echo "$PROVISIONING_PROFILE" | base64 --decode > "$PROFILE_PATH" + echo "Saved provisioning profile $PROFILE_PATH" + - | + # set up ios signing certificate + echo $CERTIFICATE | base64 --decode > '/tmp/certificate.p12' + keychain add-certificates --certificate '/tmp/certificate.p12' --certificate-password $CERTIFICATE_PASSWORD + - xcode-project use-profiles + - flutter build ipa --export-options-plist ~/export_options.plist + artifacts: + - build/ios/ipa/*.ipa + - /tmp/xcodebuild_logs/*.log + - '*.snap' + - build/windows/**/*.msix + - flutter_drive.log + publishing: + app_store_connect: + api_key: + key_id: + issuer_id: + submit_to_testflight: false + submit_to_app_store: false diff --git a/ios/Flutter/Flutter.podspec b/ios/Flutter/Flutter.podspec new file mode 100644 index 00000000..663d5b29 --- /dev/null +++ b/ios/Flutter/Flutter.podspec @@ -0,0 +1,18 @@ +# +# NOTE: This podspec is NOT to be published. It is only used as a local source! +# This is a generated file; do not edit or check into version control. +# + +Pod::Spec.new do |s| + s.name = 'Flutter' + s.version = '1.0.0' + s.summary = 'High-performance, high-fidelity mobile apps.' + s.homepage = 'https://flutter.io' + s.license = { :type => 'MIT' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } + s.ios.deployment_target = '9.0' + # Framework linking is handled by Flutter tooling, not CocoaPods. + # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. + s.vendored_frameworks = 'path/to/nothing' +end diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 00000000..3e0fd371 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,39 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index c83b0051..c4e7692a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -7,9 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 080F3EB6D97C94021A577FF3 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87B398317EBBEBE2AE284A78 /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 8981BC204AA6763F48392668 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 887069A31D31C9BF9D217F54 /* libPods-Runner.a */; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; @@ -40,7 +40,7 @@ 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 86CC2D58A4D5E9C5BEA67555 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 887069A31D31C9BF9D217F54 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 87B398317EBBEBE2AE284A78 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -57,7 +57,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8981BC204AA6763F48392668 /* libPods-Runner.a in Frameworks */, + 080F3EB6D97C94021A577FF3 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -67,7 +67,7 @@ 6A50AD2F0B3F0D889EFA460A /* Frameworks */ = { isa = PBXGroup; children = ( - 887069A31D31C9BF9D217F54 /* libPods-Runner.a */, + 87B398317EBBEBE2AE284A78 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -150,6 +150,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 5F9F5D8C455890CF1A08E548 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -222,6 +223,32 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 5F9F5D8C455890CF1A08E548 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework", + "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", + "${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework", + "${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework", + "${BUILT_PRODUCTS_DIR}/shared_preferences_ios/shared_preferences_ios.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_ios.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 641FA095A70B88123015AA6E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -353,7 +380,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -476,7 +506,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -499,7 +532,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 25f900c7..c40cab31 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -46,5 +46,7 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + diff --git a/ios/true b/ios/true new file mode 100644 index 00000000..73bc7c06 --- /dev/null +++ b/ios/true @@ -0,0 +1,38 @@ +[!] Unknown command: `Sentry:modular_headers` +Did you mean: outdated? + +Usage: + + $ pod COMMAND + + CocoaPods, the Cocoa library package manager. + +Commands: + + + cache Manipulate the CocoaPods cache + + deintegrate Deintegrate CocoaPods from your project + + env Display pod environment + + init Generate a Podfile for the current directory + + install Install project dependencies according to versions from a + Podfile.lock + + ipc Inter-process communication + + lib Develop pods + + list List pods + + outdated Show outdated project dependencies + + plugins Show available CocoaPods plugins + + repo Manage spec-repositories + + search Search for pods + + setup Setup the CocoaPods environment + + spec Manage pod specs + + trunk Interact with the CocoaPods API (e.g. publishing new specs) + + try Try a Pod! + + update Update outdated project dependencies and create new Podfile.lock + +Options: + + --allow-root Allows CocoaPods to run as root + --silent Show nothing + --version Show the version of the tool + --verbose Show more debugging information + --no-ansi Show output without ANSI codes + --help Show help banner of specified command diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 00000000..8737f09b --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: assets/lang +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart \ No newline at end of file diff --git a/lib/constants/assets.dart b/lib/constants/assets.dart index 4d8505c7..15c76ba1 100644 --- a/lib/constants/assets.dart +++ b/lib/constants/assets.dart @@ -6,5 +6,4 @@ class Assets { // login screen assets static const String carBackground = "assets/images/img_login.jpg"; - -} \ No newline at end of file +} diff --git a/lib/constants/colors.dart b/lib/constants/colors.dart index 9cecce17..971a6541 100644 --- a/lib/constants/colors.dart +++ b/lib/constants/colors.dart @@ -3,16 +3,16 @@ import 'package:flutter/material.dart'; class AppColors { AppColors._(); // this basically makes it so you can't instantiate this class - static const Map orange = const { - 50: const Color(0xFFFCF2E7), - 100: const Color(0xFFF8DEC3), - 200: const Color(0xFFF3C89C), - 300: const Color(0xFFEEB274), - 400: const Color(0xFFEAA256), - 500: const Color(0xFFE69138), - 600: const Color(0xFFE38932), - 700: const Color(0xFFDF7E2B), - 800: const Color(0xFFDB7424), - 900: const Color(0xFFD56217) + static const Map orange = { + 50: Color(0xFFFCF2E7), + 100: Color(0xFFF8DEC3), + 200: Color(0xFFF3C89C), + 300: Color(0xFFEEB274), + 400: Color(0xFFEAA256), + 500: Color(0xFFE69138), + 600: Color(0xFFE38932), + 700: Color(0xFFDF7E2B), + 800: Color(0xFFDB7424), + 900: Color(0xFFD56217) }; } diff --git a/lib/constants/dimens.dart b/lib/constants/dimens.dart index 20b774d9..552a7782 100644 --- a/lib/constants/dimens.dart +++ b/lib/constants/dimens.dart @@ -2,6 +2,6 @@ class Dimens { Dimens._(); //for all screens - static const double horizontal_padding = 12.0; - static const double vertical_padding = 12.0; -} \ No newline at end of file + static const double horizontalPadding = 12.0; + static const double verticalPadding = 12.0; +} diff --git a/lib/constants/font_family.dart b/lib/constants/font_family.dart index 69001996..a284f728 100644 --- a/lib/constants/font_family.dart +++ b/lib/constants/font_family.dart @@ -3,4 +3,4 @@ class FontFamily { static String productSans = "ProductSans"; static String roboto = "Roboto"; -} \ No newline at end of file +} diff --git a/lib/data/local/constants/db_constants.dart b/lib/data/local/constants/db_constants.dart index 25ba1b03..2713c7ce 100644 --- a/lib/data/local/constants/db_constants.dart +++ b/lib/data/local/constants/db_constants.dart @@ -2,11 +2,11 @@ class DBConstants { DBConstants._(); // Store Name - static const String STORE_NAME = 'demo'; + static const String storeName = 'demo'; // DB Name - static const DB_NAME = 'demo.db'; + static const dbName = 'demo.db'; // Fields - static const FIELD_ID = 'id'; + static const fieldID = 'id'; } diff --git a/lib/data/local/datasources/post/post_datasource.dart b/lib/data/local/datasources/post/post_datasource.dart index 8d1218a0..d9ab7103 100644 --- a/lib/data/local/datasources/post/post_datasource.dart +++ b/lib/data/local/datasources/post/post_datasource.dart @@ -6,7 +6,7 @@ import 'package:sembast/sembast.dart'; class PostDataSource { // A Store with int keys and Map values. // This Store acts like a persistent map, values of which are Flogs objects converted to Map - final _postsStore = intMapStoreFactory.store(DBConstants.STORE_NAME); + final _postsStore = intMapStoreFactory.store(DBConstants.storeName); // Private getter to shorten the amount of code needed to get the // singleton instance of an opened database. @@ -20,18 +20,18 @@ class PostDataSource { // DB functions:-------------------------------------------------------------- Future insert(Post post) async { - return await _postsStore.add(_db, post.toMap()); + return _postsStore.add(_db, post.toMap()); } Future count() async { - return await _postsStore.count(_db); + return _postsStore.count(_db); } Future> getAllSortedByFilter({List? filters}) async { //creating finder final finder = Finder( filter: filters != null ? Filter.and(filters) : null, - sortOrders: [SortOrder(DBConstants.FIELD_ID)]); + sortOrders: [SortOrder(DBConstants.fieldID)]); final recordSnapshots = await _postsStore.find( _db, @@ -48,11 +48,8 @@ class PostDataSource { } Future getPostsFromDb() async { - - print('Loading from database'); - // post list - var postsList; + PostList postsList = PostList(); // fetching data final recordSnapshots = await _postsStore.find( @@ -60,7 +57,7 @@ class PostDataSource { ); // Making a List out of List - if(recordSnapshots.length > 0) { + if(recordSnapshots.isNotEmpty) { postsList = PostList( posts: recordSnapshots.map((snapshot) { final post = Post.fromMap(snapshot.value); @@ -77,7 +74,7 @@ class PostDataSource { // For filtering by key (ID), RegEx, greater than, and many other criteria, // we use a Finder. final finder = Finder(filter: Filter.byKey(post.id)); - return await _postsStore.update( + return _postsStore.update( _db, post.toMap(), finder: finder, @@ -86,7 +83,7 @@ class PostDataSource { Future delete(Post post) async { final finder = Finder(filter: Filter.byKey(post.id)); - return await _postsStore.delete( + return _postsStore.delete( _db, finder: finder, ); diff --git a/lib/data/network/apis/posts/post_api.dart b/lib/data/network/apis/posts/post_api.dart index 154e4ecd..f7e6de04 100644 --- a/lib/data/network/apis/posts/post_api.dart +++ b/lib/data/network/apis/posts/post_api.dart @@ -2,37 +2,22 @@ import 'dart:async'; import 'package:boilerplate/data/network/constants/endpoints.dart'; import 'package:boilerplate/data/network/dio_client.dart'; -import 'package:boilerplate/data/network/rest_client.dart'; import 'package:boilerplate/models/post/post_list.dart'; class PostApi { // dio instance final DioClient _dioClient; - // rest-client instance - final RestClient _restClient; - // injecting dio instance - PostApi(this._dioClient, this._restClient); + PostApi(this._dioClient); /// Returns list of post in response Future getPosts() async { try { final res = await _dioClient.get(Endpoints.getPosts); - return PostList.fromJson(res); + return PostList.fromJson(res as List); } catch (e) { - print(e.toString()); - throw e; + rethrow; } } - -/// sample api call with default rest client -// Future getPosts() { -// -// return _restClient -// .get(Endpoints.getPosts) -// .then((dynamic res) => PostsList.fromJson(res)) -// .catchError((error) => throw NetworkException(message: error)); -// } - } diff --git a/lib/data/network/constants/endpoints.dart b/lib/data/network/constants/endpoints.dart index 160227aa..20a36c6c 100644 --- a/lib/data/network/constants/endpoints.dart +++ b/lib/data/network/constants/endpoints.dart @@ -11,5 +11,5 @@ class Endpoints { static const int connectionTimeout = 30000; // booking endpoints - static const String getPosts = baseUrl + "/posts"; -} \ No newline at end of file + static const String getPosts = "$baseUrl/posts"; +} diff --git a/lib/data/network/dio_client.dart b/lib/data/network/dio_client.dart index 3509fd1c..1369a4f3 100644 --- a/lib/data/network/dio_client.dart +++ b/lib/data/network/dio_client.dart @@ -25,15 +25,14 @@ class DioClient { ); return response.data; } catch (e) { - print(e.toString()); - throw e; + rethrow; } } // Post:---------------------------------------------------------------------- Future post( String uri, { - data, + dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, @@ -52,14 +51,14 @@ class DioClient { ); return response.data; } catch (e) { - throw e; + rethrow; } } // Put:----------------------------------------------------------------------- Future put( String uri, { - data, + dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, @@ -78,14 +77,14 @@ class DioClient { ); return response.data; } catch (e) { - throw e; + rethrow; } } // Delete:-------------------------------------------------------------------- Future delete( String uri, { - data, + dynamic data, Map? queryParameters, Options? options, CancelToken? cancelToken, @@ -102,7 +101,7 @@ class DioClient { ); return response.data; } catch (e) { - throw e; + rethrow; } } } diff --git a/lib/data/network/exceptions/network_exceptions.dart b/lib/data/network/exceptions/network_exceptions.dart index 8ba446d2..f85f6487 100644 --- a/lib/data/network/exceptions/network_exceptions.dart +++ b/lib/data/network/exceptions/network_exceptions.dart @@ -6,6 +6,6 @@ class NetworkException implements Exception { } class AuthException extends NetworkException { - AuthException({message, statusCode}) + AuthException({String? message, int? statusCode}) : super(message: message, statusCode: statusCode); } diff --git a/lib/data/network/rest_client.dart b/lib/data/network/rest_client.dart deleted file mode 100644 index b5fd6e57..00000000 --- a/lib/data/network/rest_client.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:boilerplate/data/network/constants/endpoints.dart'; -import 'package:http/http.dart' as http; - -import 'exceptions/network_exceptions.dart'; - -class RestClient { - // instantiate json decoder for json serialization - final JsonDecoder _decoder = JsonDecoder(); - - // Get:----------------------------------------------------------------------- - Future get(String path) { - return http.get(Uri.https(Endpoints.baseUrl, path)).then(_createResponse); - } - - // Post:---------------------------------------------------------------------- - Future post(String path, - {Map? headers, body, encoding}) { - return http - .post( - Uri.https(Endpoints.baseUrl, path), - body: body, - headers: headers, - encoding: encoding, - ) - .then(_createResponse); - } - - // Put:---------------------------------------------------------------------- - Future put(String path, - {Map? headers, body, encoding}) { - return http - .put( - Uri.https(Endpoints.baseUrl, path), - body: body, - headers: headers, - encoding: encoding, - ) - .then(_createResponse); - } - - // Delete:---------------------------------------------------------------------- - Future delete(String path, - {Map? headers, body, encoding}) { - return http - .delete( - Uri.https(Endpoints.baseUrl, path), - body: body, - headers: headers, - encoding: encoding, - ) - .then(_createResponse); - } - - // Response:------------------------------------------------------------------ - dynamic _createResponse(http.Response response) { - final String res = response.body; - final int statusCode = response.statusCode; - - if (statusCode < 200 || statusCode > 400) { - throw NetworkException( - message: 'Error fetching data from server', statusCode: statusCode); - } - - return _decoder.convert(res); - } -} diff --git a/lib/data/repository.dart b/lib/data/repository.dart index 49b53580..30fcaa30 100644 --- a/lib/data/repository.dart +++ b/lib/data/repository.dart @@ -1,14 +1,13 @@ import 'dart:async'; +import 'package:boilerplate/data/local/constants/db_constants.dart'; import 'package:boilerplate/data/local/datasources/post/post_datasource.dart'; +import 'package:boilerplate/data/network/apis/posts/post_api.dart'; import 'package:boilerplate/data/sharedpref/shared_preference_helper.dart'; import 'package:boilerplate/models/post/post.dart'; import 'package:boilerplate/models/post/post_list.dart'; import 'package:sembast/sembast.dart'; -import 'local/constants/db_constants.dart'; -import 'network/apis/posts/post_api.dart'; - class Repository { // data source object final PostDataSource _postDataSource; @@ -27,57 +26,59 @@ class Repository { // check to see if posts are present in database, then fetch from database // else make a network call to get all posts, store them into database for // later use - return await _postApi.getPosts().then((postsList) { + return _postApi.getPosts().then((postsList) { postsList.posts?.forEach((post) { _postDataSource.insert(post); }); return postsList; - }).catchError((error) => throw error); + }).catchError((error) => throw error as Object); } Future> findPostById(int id) { //creating filter - List filters = []; + final List filters = []; //check to see if dataLogsType is not null - Filter dataLogTypeFilter = Filter.equals(DBConstants.FIELD_ID, id); + final Filter dataLogTypeFilter = Filter.equals(DBConstants.fieldID, id); filters.add(dataLogTypeFilter); //making db call return _postDataSource .getAllSortedByFilter(filters: filters) .then((posts) => posts) - .catchError((error) => throw error); + .catchError((Object error) => throw error); } Future insert(Post post) => _postDataSource .insert(post) .then((id) => id) - .catchError((error) => throw error); + .catchError((Object error) => throw error); Future update(Post post) => _postDataSource .update(post) .then((id) => id) - .catchError((error) => throw error); + .catchError((Object error) => throw error); Future delete(Post post) => _postDataSource .update(post) .then((id) => id) - .catchError((error) => throw error); + .catchError((Object error) => throw error); // Login:--------------------------------------------------------------------- Future login(String email, String password) async { - return await Future.delayed(Duration(seconds: 2), ()=> true); + return Future.delayed(const Duration(seconds: 2), ()=> true); } + // ignore: avoid_positional_boolean_parameters Future saveIsLoggedIn(bool value) => _sharedPrefsHelper.saveIsLoggedIn(value); Future get isLoggedIn => _sharedPrefsHelper.isLoggedIn; // Theme: -------------------------------------------------------------------- + // ignore: avoid_positional_boolean_parameters Future changeBrightnessToDark(bool value) => _sharedPrefsHelper.changeBrightnessToDark(value); @@ -88,4 +89,4 @@ class Repository { _sharedPrefsHelper.changeLanguage(value); String? get currentLanguage => _sharedPrefsHelper.currentLanguage; -} \ No newline at end of file +} diff --git a/lib/data/sharedpref/constants/preferences.dart b/lib/data/sharedpref/constants/preferences.dart index 64fcc67b..c9b96b00 100644 --- a/lib/data/sharedpref/constants/preferences.dart +++ b/lib/data/sharedpref/constants/preferences.dart @@ -5,4 +5,4 @@ class Preferences { static const String auth_token = "authToken"; static const String is_dark_mode = "is_dark_mode"; static const String current_language = "current_language"; -} \ No newline at end of file +} diff --git a/lib/data/sharedpref/shared_preference_helper.dart b/lib/data/sharedpref/shared_preference_helper.dart index 446a9ddd..2ca1c84d 100644 --- a/lib/data/sharedpref/shared_preference_helper.dart +++ b/lib/data/sharedpref/shared_preference_helper.dart @@ -1,9 +1,10 @@ +// ignore_for_file: avoid_positional_boolean_parameters + import 'dart:async'; +import 'package:boilerplate/data/sharedpref/constants/preferences.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'constants/preferences.dart'; - class SharedPreferenceHelper { // shared pref instance final SharedPreferences _sharedPreference; @@ -50,4 +51,4 @@ class SharedPreferenceHelper { Future changeLanguage(String language) { return _sharedPreference.setString(Preferences.current_language, language); } -} \ No newline at end of file +} diff --git a/lib/di/components/service_locator.dart b/lib/di/components/service_locator.dart index c081e79c..f1259fdd 100644 --- a/lib/di/components/service_locator.dart +++ b/lib/di/components/service_locator.dart @@ -2,7 +2,6 @@ import 'package:boilerplate/data/local/datasources/post/post_datasource.dart'; import 'package:boilerplate/data/network/apis/posts/post_api.dart'; import 'package:boilerplate/data/network/dio_client.dart'; -import 'package:boilerplate/data/network/rest_client.dart'; import 'package:boilerplate/data/repository.dart'; import 'package:boilerplate/data/sharedpref/shared_preference_helper.dart'; import 'package:boilerplate/di/module/local_module.dart'; @@ -33,10 +32,9 @@ Future setupLocator() async { getIt.registerSingleton(SharedPreferenceHelper(await getIt.getAsync())); getIt.registerSingleton(NetworkModule.provideDio(getIt())); getIt.registerSingleton(DioClient(getIt())); - getIt.registerSingleton(RestClient()); // api's:--------------------------------------------------------------------- - getIt.registerSingleton(PostApi(getIt(), getIt())); + getIt.registerSingleton(PostApi(getIt())); // data sources getIt.registerSingleton(PostDataSource(await getIt.getAsync())); diff --git a/lib/di/module/local_module.dart b/lib/di/module/local_module.dart index 170884ae..24b22af5 100644 --- a/lib/di/module/local_module.dart +++ b/lib/di/module/local_module.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:boilerplate/data/local/constants/db_constants.dart'; import 'package:boilerplate/utils/encryption/xxtea.dart'; +// ignore: depend_on_referenced_packages import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sembast/sembast.dart'; @@ -9,8 +10,6 @@ import 'package:sembast/sembast_io.dart'; import 'package:shared_preferences/shared_preferences.dart'; abstract class LocalModule { - - /// A singleton preference provider. /// /// Calling it multiple times will return the same instance. @@ -23,20 +22,20 @@ abstract class LocalModule { /// Calling it multiple times will return the same instance. static Future provideDatabase() async { // Key for encryption - var encryptionKey = ""; + const encryptionKey = ""; // Get a platform-specific directory where persistent app data can be stored final appDocumentDir = await getApplicationDocumentsDirectory(); // Path with the form: /platform-specific-directory/demo.db - final dbPath = join(appDocumentDir.path, DBConstants.DB_NAME); + final dbPath = join(appDocumentDir.path, DBConstants.dbName); // Check to see if encryption is set, then provide codec // else init normal db with path - var database; + Database database; if (encryptionKey.isNotEmpty) { // Initialize the encryption codec with a user password - var codec = getXXTeaCodec(password: encryptionKey); + final codec = getXXTeaCodec(password: encryptionKey); database = await databaseFactoryIo.openDatabase(dbPath, codec: codec); } else { database = await databaseFactoryIo.openDatabase(dbPath); diff --git a/lib/di/module/network_module.dart b/lib/di/module/network_module.dart index f3fb079f..161e1317 100644 --- a/lib/di/module/network_module.dart +++ b/lib/di/module/network_module.dart @@ -15,17 +15,15 @@ abstract class NetworkModule { ..options.receiveTimeout = Endpoints.receiveTimeout ..options.headers = {'Content-Type': 'application/json; charset=utf-8'} ..interceptors.add(LogInterceptor( - request: true, responseBody: true, requestBody: true, - requestHeader: true, )) ..interceptors.add( InterceptorsWrapper( onRequest: (RequestOptions options, RequestInterceptorHandler handler) async { // getting token - var token = await sharedPrefHelper.authToken; + final String? token = await sharedPrefHelper.authToken; if (token != null) { options.headers.putIfAbsent('Authorization', () => token); diff --git a/lib/main.dart b/lib/main.dart index 1bf8677a..eb7b81ef 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,20 +1,20 @@ import 'dart:async'; +import 'package:boilerplate/di/components/service_locator.dart'; import 'package:boilerplate/ui/my_app.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; - -import 'di/components/service_locator.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await setPreferredOrientations(); await setupLocator(); return runZonedGuarded(() async { + await initSentry(); runApp(MyApp()); - }, (error, stack) { - print(stack); - print(error); + }, (error, stack) async { + await Sentry.captureException(error, stackTrace: stack); }); } @@ -26,3 +26,11 @@ Future setPreferredOrientations() { DeviceOrientation.landscapeLeft, ]); } + +Future initSentry() async { + await SentryFlutter.init( + (options) { + options.dsn = 'https://1329608af7aa4e2fab57140c806e7092@o1347552.ingest.sentry.io/6626326'; + }, + ); +} diff --git a/lib/models/post/post.dart b/lib/models/post/post.dart index 449b6c23..53dc858f 100644 --- a/lib/models/post/post.dart +++ b/lib/models/post/post.dart @@ -12,10 +12,10 @@ class Post { }); factory Post.fromMap(Map json) => Post( - userId: json["userId"], - id: json["id"], - title: json["title"], - body: json["body"], + userId: json["userId"] as int?, + id: json["id"] as int?, + title: json["title"] as String?, + body: json["body"] as String?, ); Map toMap() => { diff --git a/lib/models/post/post_list.dart b/lib/models/post/post_list.dart index e537a244..2c444bd9 100644 --- a/lib/models/post/post_list.dart +++ b/lib/models/post/post_list.dart @@ -9,7 +9,7 @@ class PostList { factory PostList.fromJson(List json) { List posts = []; - posts = json.map((post) => Post.fromMap(post)).toList(); + posts = json.map((post) => Post.fromMap(post as Map)).toList(); return PostList( posts: posts, diff --git a/lib/stores/error/error_store.dart b/lib/stores/error/error_store.dart index e98922fe..45e3cb3a 100644 --- a/lib/stores/error/error_store.dart +++ b/lib/stores/error/error_store.dart @@ -24,20 +24,19 @@ abstract class _ErrorStore with Store { // actions:------------------------------------------------------------------- @action void setErrorMessage(String message) { - this.errorMessage = message; + errorMessage = message; } @action void reset(String value) { - print('calling reset'); errorMessage = ''; } // dispose:------------------------------------------------------------------- @action - dispose() { + void dispose() { for (final d in _disposers) { d(); } } -} \ No newline at end of file +} diff --git a/lib/stores/error/error_store.g.dart b/lib/stores/error/error_store.g.dart index 5b1d960b..b1d99f25 100644 --- a/lib/stores/error/error_store.g.dart +++ b/lib/stores/error/error_store.g.dart @@ -6,38 +6,63 @@ part of 'error_store.dart'; // StoreGenerator // ************************************************************************** -// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars +// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic mixin _$ErrorStore on _ErrorStore, Store { final _$errorMessageAtom = Atom(name: '_ErrorStore.errorMessage'); @override String get errorMessage { - _$errorMessageAtom.reportObserved(); + _$errorMessageAtom.reportRead(); return super.errorMessage; } @override set errorMessage(String value) { - _$errorMessageAtom.context - .checkIfStateModificationsAreAllowed(_$errorMessageAtom); - super.errorMessage = value; - _$errorMessageAtom.reportChanged(); + _$errorMessageAtom.reportWrite(value, super.errorMessage, () { + super.errorMessage = value; + }); } - final _$showErrorAtom = Atom(name: '_ErrorStore.showError'); + final _$_ErrorStoreActionController = ActionController(name: '_ErrorStore'); @override - bool get showError { - _$showErrorAtom.reportObserved(); - return super.showError; + void setErrorMessage(String message) { + final _$actionInfo = _$_ErrorStoreActionController.startAction( + name: '_ErrorStore.setErrorMessage'); + try { + return super.setErrorMessage(message); + } finally { + _$_ErrorStoreActionController.endAction(_$actionInfo); + } } @override - set showError(bool value) { - _$showErrorAtom.context - .checkIfStateModificationsAreAllowed(_$showErrorAtom); - super.showError = value; - _$showErrorAtom.reportChanged(); + void reset(String value) { + final _$actionInfo = + _$_ErrorStoreActionController.startAction(name: '_ErrorStore.reset'); + try { + return super.reset(value); + } finally { + _$_ErrorStoreActionController.endAction(_$actionInfo); + } + } + + @override + void dispose() { + final _$actionInfo = + _$_ErrorStoreActionController.startAction(name: '_ErrorStore.dispose'); + try { + return super.dispose(); + } finally { + _$_ErrorStoreActionController.endAction(_$actionInfo); + } + } + + @override + String toString() { + return ''' +errorMessage: ${errorMessage} + '''; } } diff --git a/lib/stores/form/form_store.dart b/lib/stores/form/form_store.dart index 6e2b6f4a..6caee413 100644 --- a/lib/stores/form/form_store.dart +++ b/lib/stores/form/form_store.dart @@ -117,7 +117,7 @@ abstract class _FormStore with Store { Future login() async { loading = true; - Future.delayed(Duration(milliseconds: 2000)).then((future) { + Future.delayed(const Duration(milliseconds: 2000)).then((future) { loading = false; success = true; }).catchError((e) { @@ -126,7 +126,6 @@ abstract class _FormStore with Store { errorStore.errorMessage = e.toString().contains("ERROR_USER_NOT_FOUND") ? "Username and password doesn't match" : "Something went wrong, please check your internet connection and try again"; - print(e); }); } diff --git a/lib/stores/form/form_store.g.dart b/lib/stores/form/form_store.g.dart index 2e079822..5fdc28e7 100644 --- a/lib/stores/form/form_store.g.dart +++ b/lib/stores/form/form_store.g.dart @@ -6,141 +6,127 @@ part of 'form_store.dart'; // StoreGenerator // ************************************************************************** -// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars +// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic mixin _$FormStore on _FormStore, Store { - Computed _$canLoginComputed; + Computed? _$canLoginComputed; @override - bool get canLogin => - (_$canLoginComputed ??= Computed(() => super.canLogin)).value; - Computed _$canRegisterComputed; + bool get canLogin => (_$canLoginComputed ??= + Computed(() => super.canLogin, name: '_FormStore.canLogin')) + .value; + Computed? _$canRegisterComputed; @override bool get canRegister => - (_$canRegisterComputed ??= Computed(() => super.canRegister)).value; - Computed _$canForgetPasswordComputed; + (_$canRegisterComputed ??= Computed(() => super.canRegister, + name: '_FormStore.canRegister')) + .value; + Computed? _$canForgetPasswordComputed; @override - bool get canForgetPassword => (_$canForgetPasswordComputed ??= - Computed(() => super.canForgetPassword)) + bool get canForgetPassword => (_$canForgetPasswordComputed ??= Computed( + () => super.canForgetPassword, + name: '_FormStore.canForgetPassword')) .value; - final _$postsListAtom = Atom(name: '_FormStore.postsList'); - - @override - PostsList get postsList { - _$postsListAtom.reportObserved(); - return super.postsList; - } - - @override - set postsList(PostsList value) { - _$postsListAtom.context - .checkIfStateModificationsAreAllowed(_$postsListAtom); - super.postsList = value; - _$postsListAtom.reportChanged(); - } - final _$userEmailAtom = Atom(name: '_FormStore.userEmail'); @override String get userEmail { - _$userEmailAtom.reportObserved(); + _$userEmailAtom.reportRead(); return super.userEmail; } @override set userEmail(String value) { - _$userEmailAtom.context - .checkIfStateModificationsAreAllowed(_$userEmailAtom); - super.userEmail = value; - _$userEmailAtom.reportChanged(); + _$userEmailAtom.reportWrite(value, super.userEmail, () { + super.userEmail = value; + }); } final _$passwordAtom = Atom(name: '_FormStore.password'); @override String get password { - _$passwordAtom.reportObserved(); + _$passwordAtom.reportRead(); return super.password; } @override set password(String value) { - _$passwordAtom.context.checkIfStateModificationsAreAllowed(_$passwordAtom); - super.password = value; - _$passwordAtom.reportChanged(); + _$passwordAtom.reportWrite(value, super.password, () { + super.password = value; + }); } final _$confirmPasswordAtom = Atom(name: '_FormStore.confirmPassword'); @override String get confirmPassword { - _$confirmPasswordAtom.reportObserved(); + _$confirmPasswordAtom.reportRead(); return super.confirmPassword; } @override set confirmPassword(String value) { - _$confirmPasswordAtom.context - .checkIfStateModificationsAreAllowed(_$confirmPasswordAtom); - super.confirmPassword = value; - _$confirmPasswordAtom.reportChanged(); + _$confirmPasswordAtom.reportWrite(value, super.confirmPassword, () { + super.confirmPassword = value; + }); } final _$successAtom = Atom(name: '_FormStore.success'); @override bool get success { - _$successAtom.reportObserved(); + _$successAtom.reportRead(); return super.success; } @override set success(bool value) { - _$successAtom.context.checkIfStateModificationsAreAllowed(_$successAtom); - super.success = value; - _$successAtom.reportChanged(); + _$successAtom.reportWrite(value, super.success, () { + super.success = value; + }); } final _$loadingAtom = Atom(name: '_FormStore.loading'); @override bool get loading { - _$loadingAtom.reportObserved(); + _$loadingAtom.reportRead(); return super.loading; } @override set loading(bool value) { - _$loadingAtom.context.checkIfStateModificationsAreAllowed(_$loadingAtom); - super.loading = value; - _$loadingAtom.reportChanged(); + _$loadingAtom.reportWrite(value, super.loading, () { + super.loading = value; + }); } - final _$registerAsyncAction = AsyncAction('register'); + final _$registerAsyncAction = AsyncAction('_FormStore.register'); @override Future register() { return _$registerAsyncAction.run(() => super.register()); } - final _$loginAsyncAction = AsyncAction('login'); + final _$loginAsyncAction = AsyncAction('_FormStore.login'); @override Future login() { return _$loginAsyncAction.run(() => super.login()); } - final _$forgotPasswordAsyncAction = AsyncAction('forgotPassword'); + final _$forgotPasswordAsyncAction = AsyncAction('_FormStore.forgotPassword'); @override Future forgotPassword() { return _$forgotPasswordAsyncAction.run(() => super.forgotPassword()); } - final _$logoutAsyncAction = AsyncAction('logout'); + final _$logoutAsyncAction = AsyncAction('_FormStore.logout'); @override Future logout() { @@ -151,7 +137,8 @@ mixin _$FormStore on _FormStore, Store { @override void setUserId(String value) { - final _$actionInfo = _$_FormStoreActionController.startAction(); + final _$actionInfo = + _$_FormStoreActionController.startAction(name: '_FormStore.setUserId'); try { return super.setUserId(value); } finally { @@ -161,7 +148,8 @@ mixin _$FormStore on _FormStore, Store { @override void setPassword(String value) { - final _$actionInfo = _$_FormStoreActionController.startAction(); + final _$actionInfo = _$_FormStoreActionController.startAction( + name: '_FormStore.setPassword'); try { return super.setPassword(value); } finally { @@ -171,7 +159,8 @@ mixin _$FormStore on _FormStore, Store { @override void setConfirmPassword(String value) { - final _$actionInfo = _$_FormStoreActionController.startAction(); + final _$actionInfo = _$_FormStoreActionController.startAction( + name: '_FormStore.setConfirmPassword'); try { return super.setConfirmPassword(value); } finally { @@ -181,7 +170,8 @@ mixin _$FormStore on _FormStore, Store { @override void validateUserEmail(String value) { - final _$actionInfo = _$_FormStoreActionController.startAction(); + final _$actionInfo = _$_FormStoreActionController.startAction( + name: '_FormStore.validateUserEmail'); try { return super.validateUserEmail(value); } finally { @@ -191,7 +181,8 @@ mixin _$FormStore on _FormStore, Store { @override void validatePassword(String value) { - final _$actionInfo = _$_FormStoreActionController.startAction(); + final _$actionInfo = _$_FormStoreActionController.startAction( + name: '_FormStore.validatePassword'); try { return super.validatePassword(value); } finally { @@ -201,81 +192,107 @@ mixin _$FormStore on _FormStore, Store { @override void validateConfirmPassword(String value) { - final _$actionInfo = _$_FormStoreActionController.startAction(); + final _$actionInfo = _$_FormStoreActionController.startAction( + name: '_FormStore.validateConfirmPassword'); try { return super.validateConfirmPassword(value); } finally { _$_FormStoreActionController.endAction(_$actionInfo); } } -} -// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars + @override + String toString() { + return ''' +userEmail: ${userEmail}, +password: ${password}, +confirmPassword: ${confirmPassword}, +success: ${success}, +loading: ${loading}, +canLogin: ${canLogin}, +canRegister: ${canRegister}, +canForgetPassword: ${canForgetPassword} + '''; + } +} mixin _$FormErrorStore on _FormErrorStore, Store { - Computed _$hasErrorsInLoginComputed; + Computed? _$hasErrorsInLoginComputed; @override - bool get hasErrorsInLogin => (_$hasErrorsInLoginComputed ??= - Computed(() => super.hasErrorsInLogin)) + bool get hasErrorsInLogin => (_$hasErrorsInLoginComputed ??= Computed( + () => super.hasErrorsInLogin, + name: '_FormErrorStore.hasErrorsInLogin')) .value; - Computed _$hasErrorsInRegisterComputed; + Computed? _$hasErrorsInRegisterComputed; @override bool get hasErrorsInRegister => (_$hasErrorsInRegisterComputed ??= - Computed(() => super.hasErrorsInRegister)) + Computed(() => super.hasErrorsInRegister, + name: '_FormErrorStore.hasErrorsInRegister')) .value; - Computed _$hasErrorInForgotPasswordComputed; + Computed? _$hasErrorInForgotPasswordComputed; @override bool get hasErrorInForgotPassword => (_$hasErrorInForgotPasswordComputed ??= - Computed(() => super.hasErrorInForgotPassword)) + Computed(() => super.hasErrorInForgotPassword, + name: '_FormErrorStore.hasErrorInForgotPassword')) .value; final _$userEmailAtom = Atom(name: '_FormErrorStore.userEmail'); @override - String get userEmail { - _$userEmailAtom.reportObserved(); + String? get userEmail { + _$userEmailAtom.reportRead(); return super.userEmail; } @override - set userEmail(String value) { - _$userEmailAtom.context - .checkIfStateModificationsAreAllowed(_$userEmailAtom); - super.userEmail = value; - _$userEmailAtom.reportChanged(); + set userEmail(String? value) { + _$userEmailAtom.reportWrite(value, super.userEmail, () { + super.userEmail = value; + }); } final _$passwordAtom = Atom(name: '_FormErrorStore.password'); @override - String get password { - _$passwordAtom.reportObserved(); + String? get password { + _$passwordAtom.reportRead(); return super.password; } @override - set password(String value) { - _$passwordAtom.context.checkIfStateModificationsAreAllowed(_$passwordAtom); - super.password = value; - _$passwordAtom.reportChanged(); + set password(String? value) { + _$passwordAtom.reportWrite(value, super.password, () { + super.password = value; + }); } final _$confirmPasswordAtom = Atom(name: '_FormErrorStore.confirmPassword'); @override - String get confirmPassword { - _$confirmPasswordAtom.reportObserved(); + String? get confirmPassword { + _$confirmPasswordAtom.reportRead(); return super.confirmPassword; } @override - set confirmPassword(String value) { - _$confirmPasswordAtom.context - .checkIfStateModificationsAreAllowed(_$confirmPasswordAtom); - super.confirmPassword = value; - _$confirmPasswordAtom.reportChanged(); + set confirmPassword(String? value) { + _$confirmPasswordAtom.reportWrite(value, super.confirmPassword, () { + super.confirmPassword = value; + }); + } + + @override + String toString() { + return ''' +userEmail: ${userEmail}, +password: ${password}, +confirmPassword: ${confirmPassword}, +hasErrorsInLogin: ${hasErrorsInLogin}, +hasErrorsInRegister: ${hasErrorsInRegister}, +hasErrorInForgotPassword: ${hasErrorInForgotPassword} + '''; } } diff --git a/lib/stores/language/language_store.dart b/lib/stores/language/language_store.dart index 1bcc9d6d..b8d67df0 100644 --- a/lib/stores/language/language_store.dart +++ b/lib/stores/language/language_store.dart @@ -1,5 +1,5 @@ import 'package:boilerplate/data/repository.dart'; -import 'package:boilerplate/models/language/Language.dart'; +import 'package:boilerplate/models/language/language.dart'; import 'package:boilerplate/stores/error/error_store.dart'; import 'package:mobx/mobx.dart'; @@ -8,8 +8,6 @@ part 'language_store.g.dart'; class LanguageStore = _LanguageStore with _$LanguageStore; abstract class _LanguageStore with Store { - static const String TAG = "LanguageStore"; - // repository instance final Repository _repository; @@ -25,7 +23,7 @@ abstract class _LanguageStore with Store { // constructor:--------------------------------------------------------------- _LanguageStore(Repository repository) - : this._repository = repository { + : _repository = repository { init(); } @@ -47,7 +45,7 @@ abstract class _LanguageStore with Store { @action String getCode() { - var code; + String code; if (_locale == 'en') { code = "US"; @@ -55,6 +53,8 @@ abstract class _LanguageStore with Store { code = "DK"; } else if (_locale == 'es') { code = "ES"; + } else { + code = "US"; } return code; @@ -68,14 +68,10 @@ abstract class _LanguageStore with Store { } // general:------------------------------------------------------------------- - void init() async { + Future init() async { // getting current language from shared preference if(_repository.currentLanguage != null) { _locale = _repository.currentLanguage!; } } - - // dispose:------------------------------------------------------------------- - @override - dispose() {} } diff --git a/lib/stores/post/post_store.dart b/lib/stores/post/post_store.dart index 463d846d..385b173a 100644 --- a/lib/stores/post/post_store.dart +++ b/lib/stores/post/post_store.dart @@ -2,6 +2,7 @@ import 'package:boilerplate/data/repository.dart'; import 'package:boilerplate/models/post/post_list.dart'; import 'package:boilerplate/stores/error/error_store.dart'; import 'package:boilerplate/utils/dio/dio_error_util.dart'; +import 'package:dio/dio.dart'; import 'package:mobx/mobx.dart'; part 'post_store.g.dart'; @@ -10,13 +11,13 @@ class PostStore = _PostStore with _$PostStore; abstract class _PostStore with Store { // repository instance - late Repository _repository; + final Repository _repository; // store for handling errors final ErrorStore errorStore = ErrorStore(); // constructor:--------------------------------------------------------------- - _PostStore(Repository repository) : this._repository = repository; + _PostStore(Repository repository) : _repository = repository; // store variables:----------------------------------------------------------- static ObservableFuture emptyPostResponse = @@ -44,7 +45,7 @@ abstract class _PostStore with Store { future.then((postList) { this.postList = postList; }).catchError((error) { - errorStore.errorMessage = DioErrorUtil.handleError(error); + errorStore.errorMessage = DioErrorUtil.handleError(error as DioError); }); } } diff --git a/lib/stores/post/post_store.g.dart b/lib/stores/post/post_store.g.dart index 425891d2..9e83bfaa 100644 --- a/lib/stores/post/post_store.g.dart +++ b/lib/stores/post/post_store.g.dart @@ -6,59 +6,75 @@ part of 'post_store.dart'; // StoreGenerator // ************************************************************************** -// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars +// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic mixin _$PostStore on _PostStore, Store { - final _$postsListAtom = Atom(name: '_PostStore.postsList'); + Computed? _$loadingComputed; @override - PostsList get postsList { - _$postsListAtom.reportObserved(); - return super.postsList; + bool get loading => (_$loadingComputed ??= + Computed(() => super.loading, name: '_PostStore.loading')) + .value; + + final _$fetchPostsFutureAtom = Atom(name: '_PostStore.fetchPostsFuture'); + + @override + ObservableFuture get fetchPostsFuture { + _$fetchPostsFutureAtom.reportRead(); + return super.fetchPostsFuture; } @override - set postsList(PostsList value) { - _$postsListAtom.context - .checkIfStateModificationsAreAllowed(_$postsListAtom); - super.postsList = value; - _$postsListAtom.reportChanged(); + set fetchPostsFuture(ObservableFuture value) { + _$fetchPostsFutureAtom.reportWrite(value, super.fetchPostsFuture, () { + super.fetchPostsFuture = value; + }); } - final _$successAtom = Atom(name: '_PostStore.success'); + final _$postListAtom = Atom(name: '_PostStore.postList'); @override - bool get success { - _$successAtom.reportObserved(); - return super.success; + PostList? get postList { + _$postListAtom.reportRead(); + return super.postList; } @override - set success(bool value) { - _$successAtom.context.checkIfStateModificationsAreAllowed(_$successAtom); - super.success = value; - _$successAtom.reportChanged(); + set postList(PostList? value) { + _$postListAtom.reportWrite(value, super.postList, () { + super.postList = value; + }); } - final _$loadingAtom = Atom(name: '_PostStore.loading'); + final _$successAtom = Atom(name: '_PostStore.success'); @override - bool get loading { - _$loadingAtom.reportObserved(); - return super.loading; + bool get success { + _$successAtom.reportRead(); + return super.success; } @override - set loading(bool value) { - _$loadingAtom.context.checkIfStateModificationsAreAllowed(_$loadingAtom); - super.loading = value; - _$loadingAtom.reportChanged(); + set success(bool value) { + _$successAtom.reportWrite(value, super.success, () { + super.success = value; + }); } - final _$getPostsAsyncAction = AsyncAction('getPosts'); + final _$getPostsAsyncAction = AsyncAction('_PostStore.getPosts'); @override Future getPosts() { return _$getPostsAsyncAction.run(() => super.getPosts()); } + + @override + String toString() { + return ''' +fetchPostsFuture: ${fetchPostsFuture}, +postList: ${postList}, +success: ${success}, +loading: ${loading} + '''; + } } diff --git a/lib/stores/theme/theme_store.dart b/lib/stores/theme/theme_store.dart index 0f908742..2f2f21d5 100644 --- a/lib/stores/theme/theme_store.dart +++ b/lib/stores/theme/theme_store.dart @@ -8,7 +8,7 @@ part 'theme_store.g.dart'; class ThemeStore = _ThemeStore with _$ThemeStore; abstract class _ThemeStore with Store { - final String TAG = "_ThemeStore"; + final String tag = "_ThemeStore"; // repository instance final Repository _repository; @@ -26,12 +26,13 @@ abstract class _ThemeStore with Store { // constructor:--------------------------------------------------------------- _ThemeStore(Repository repository) - : this._repository = repository { + : _repository = repository { init(); } // actions:------------------------------------------------------------------- @action + // ignore: avoid_positional_boolean_parameters Future changeBrightnessToDark(bool value) async { _darkMode = value; await _repository.changeBrightnessToDark(value); @@ -44,10 +45,4 @@ abstract class _ThemeStore with Store { bool isPlatformDark(BuildContext context) => MediaQuery.platformBrightnessOf(context) == Brightness.dark; - - // dispose:------------------------------------------------------------------- - @override - dispose() { - - } } diff --git a/lib/stores/user/user_store.dart b/lib/stores/user/user_store.dart index cd419712..9bd30fcb 100644 --- a/lib/stores/user/user_store.dart +++ b/lib/stores/user/user_store.dart @@ -1,9 +1,8 @@ +import 'package:boilerplate/data/repository.dart'; import 'package:boilerplate/stores/error/error_store.dart'; +import 'package:boilerplate/stores/form/form_store.dart'; import 'package:mobx/mobx.dart'; -import '../../data/repository.dart'; -import '../form/form_store.dart'; - part 'user_store.g.dart'; class UserStore = _UserStore with _$UserStore; @@ -22,14 +21,14 @@ abstract class _UserStore with Store { bool isLoggedIn = false; // constructor:--------------------------------------------------------------- - _UserStore(Repository repository) : this._repository = repository { + _UserStore(Repository repository) : _repository = repository { // setting up disposers _setupDisposers(); // checking if user is logged in repository.isLoggedIn.then((value) { - this.isLoggedIn = value; + isLoggedIn = value; }); } @@ -65,21 +64,20 @@ abstract class _UserStore with Store { await future.then((value) async { if (value) { _repository.saveIsLoggedIn(true); - this.isLoggedIn = true; - this.success = true; + isLoggedIn = true; + success = true; } else { print('failed to login'); } - }).catchError((e) { - print(e); - this.isLoggedIn = false; - this.success = false; + }).catchError((Object e) { + isLoggedIn = false; + success = false; throw e; }); } - logout() { - this.isLoggedIn = false; + void logout() { + isLoggedIn = false; _repository.saveIsLoggedIn(false); } @@ -89,4 +87,4 @@ abstract class _UserStore with Store { d(); } } -} \ No newline at end of file +} diff --git a/lib/ui/home/home.dart b/lib/ui/home/home.dart index 1c5eb600..940c6d32 100644 --- a/lib/ui/home/home.dart +++ b/lib/ui/home/home.dart @@ -1,12 +1,12 @@ -import 'package:another_flushbar/flushbar_helper.dart'; import 'package:boilerplate/data/sharedpref/constants/preferences.dart'; -import 'package:boilerplate/utils/routes/routes.dart'; import 'package:boilerplate/stores/language/language_store.dart'; import 'package:boilerplate/stores/post/post_store.dart'; import 'package:boilerplate/stores/theme/theme_store.dart'; -import 'package:boilerplate/utils/locale/app_localization.dart'; +import 'package:boilerplate/utils/message/message.dart'; +import 'package:boilerplate/utils/routes/routes.dart'; import 'package:boilerplate/widgets/progress_indicator_widget.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:material_dialog/material_dialog.dart'; import 'package:provider/provider.dart'; @@ -54,7 +54,7 @@ class _HomeScreenState extends State { // app bar methods:----------------------------------------------------------- PreferredSizeWidget _buildAppBar() { return AppBar( - title: Text(AppLocalizations.of(context).translate('home_tv_posts')), + title: Text(AppLocalizations.of(context)!.home_tv_posts), actions: _buildActions(context), ); } @@ -90,7 +90,7 @@ class _HomeScreenState extends State { Navigator.of(context).pushReplacementNamed(Routes.login); }); }, - icon: Icon( + icon: const Icon( Icons.power_settings_new, ), ); @@ -101,7 +101,7 @@ class _HomeScreenState extends State { onPressed: () { _buildLanguageDialog(); }, - icon: Icon( + icon: const Icon( Icons.language, ), ); @@ -121,7 +121,7 @@ class _HomeScreenState extends State { return Observer( builder: (context) { return _postStore.loading - ? CustomProgressIndicatorWidget() + ? const CustomProgressIndicatorWidget() : Material(child: _buildListView()); }, ); @@ -132,7 +132,7 @@ class _HomeScreenState extends State { ? ListView.separated( itemCount: _postStore.postList!.posts!.length, separatorBuilder: (context, position) { - return Divider(); + return const Divider(); }, itemBuilder: (context, position) { return _buildListItem(position); @@ -140,7 +140,7 @@ class _HomeScreenState extends State { ) : Center( child: Text( - AppLocalizations.of(context).translate('home_tv_no_post_found'), + AppLocalizations.of(context)!.home_tv_no_post_found, ), ); } @@ -148,7 +148,7 @@ class _HomeScreenState extends State { Widget _buildListItem(int position) { return ListTile( dense: true, - leading: Icon(Icons.cloud_circle), + leading: const Icon(Icons.cloud_circle), title: Text( '${_postStore.postList?.posts?[position].title}', maxLines: 1, @@ -169,38 +169,24 @@ class _HomeScreenState extends State { return Observer( builder: (context) { if (_postStore.errorStore.errorMessage.isNotEmpty) { - return _showErrorMessage(_postStore.errorStore.errorMessage); + return ErrorMessage().showMessage(_postStore.errorStore.errorMessage, context); } - return SizedBox.shrink(); + return const SizedBox.shrink(); }, ); } - // General Methods:----------------------------------------------------------- - _showErrorMessage(String message) { - Future.delayed(Duration(milliseconds: 0), () { - if (message.isNotEmpty) { - FlushbarHelper.createError( - message: message, - title: AppLocalizations.of(context).translate('home_tv_error'), - duration: Duration(seconds: 3), - )..show(context); - } - }); - - return SizedBox.shrink(); - } - -_buildLanguageDialog() { +// General Methods:----------------------------------------------------------- +void _buildLanguageDialog() { _showDialog( context: context, child: MaterialDialog( borderRadius: 5.0, enableFullWidth: true, title: Text( - AppLocalizations.of(context).translate('home_tv_choose_language'), - style: TextStyle( + AppLocalizations.of(context)!.home_tv_choose_language, + style: const TextStyle( color: Colors.white, fontSize: 16.0, ), @@ -209,7 +195,6 @@ _buildLanguageDialog() { backgroundColor: Theme.of(context).scaffoldBackgroundColor, closeButtonColor: Colors.white, enableCloseButton: true, - enableBackButton: false, onCloseButtonClicked: () { Navigator.of(context).pop(); }, @@ -217,7 +202,7 @@ _buildLanguageDialog() { .map( (object) => ListTile( dense: true, - contentPadding: EdgeInsets.all(0.0), + contentPadding: EdgeInsets.zero, title: Text( object.language!, style: TextStyle( @@ -238,7 +223,7 @@ _buildLanguageDialog() { ); } - _showDialog({required BuildContext context, required Widget child}) { + void _showDialog({required BuildContext context, required Widget child}) { showDialog( context: context, builder: (BuildContext context) => child, diff --git a/lib/ui/login/login.dart b/lib/ui/login/login.dart index 22e37a1c..8402ca14 100644 --- a/lib/ui/login/login.dart +++ b/lib/ui/login/login.dart @@ -1,19 +1,20 @@ -import 'package:another_flushbar/flushbar_helper.dart'; import 'package:boilerplate/constants/assets.dart'; import 'package:boilerplate/data/sharedpref/constants/preferences.dart'; -import 'package:boilerplate/utils/routes/routes.dart'; import 'package:boilerplate/stores/form/form_store.dart'; import 'package:boilerplate/stores/theme/theme_store.dart'; import 'package:boilerplate/utils/device/device_utils.dart'; -import 'package:boilerplate/utils/locale/app_localization.dart'; +import 'package:boilerplate/utils/message/message.dart'; +import 'package:boilerplate/utils/routes/routes.dart'; import 'package:boilerplate/widgets/app_icon_widget.dart'; import 'package:boilerplate/widgets/empty_app_bar_widget.dart'; import 'package:boilerplate/widgets/progress_indicator_widget.dart'; import 'package:boilerplate/widgets/rounded_button_widget.dart'; import 'package:boilerplate/widgets/textfield_widget.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; class LoginScreen extends StatefulWidget { @@ -23,8 +24,8 @@ class LoginScreen extends StatefulWidget { class _LoginScreenState extends State { //text controllers:----------------------------------------------------------- - TextEditingController _userEmailController = TextEditingController(); - TextEditingController _passwordController = TextEditingController(); + final TextEditingController _userEmailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); //stores:--------------------------------------------------------------------- late ThemeStore _themeStore; @@ -50,7 +51,6 @@ class _LoginScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - primary: true, appBar: EmptyAppBar(), body: _buildBody(), ); @@ -61,32 +61,28 @@ class _LoginScreenState extends State { return Material( child: Stack( children: [ - MediaQuery.of(context).orientation == Orientation.landscape - ? Row( - children: [ - Expanded( - flex: 1, - child: _buildLeftSide(), - ), - Expanded( - flex: 1, - child: _buildRightSide(), - ), - ], - ) - : Center(child: _buildRightSide()), + if (MediaQuery.of(context).orientation == Orientation.landscape) Row( + children: [ + Expanded( + child: _buildLeftSide(), + ), + Expanded( + child: _buildRightSide(), + ), + ], + ) else Center(child: _buildRightSide()), Observer( builder: (context) { return _store.success ? navigate(context) - : _showErrorMessage(_store.errorStore.errorMessage); + : ErrorMessage().showMessage(_store.errorStore.errorMessage, context); }, ), Observer( builder: (context) { return Visibility( visible: _store.loading, - child: CustomProgressIndicatorWidget(), + child: const CustomProgressIndicatorWidget(), ); }, ) @@ -109,12 +105,11 @@ class _LoginScreenState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), child: Column( - mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, children: [ - AppIconWidget(image: 'assets/icons/ic_appicon.png'), - SizedBox(height: 24.0), + const AppIconWidget(image: 'assets/icons/ic_appicon.png'), + const SizedBox(height: 24.0), _buildUserIdField(), _buildPasswordField(), _buildForgotPasswordButton(), @@ -129,13 +124,12 @@ class _LoginScreenState extends State { return Observer( builder: (context) { return TextFieldWidget( - hint: AppLocalizations.of(context).translate('login_et_user_email'), + hint: AppLocalizations.of(context)!.login_et_user_email, inputType: TextInputType.emailAddress, icon: Icons.person, iconColor: _themeStore.darkMode ? Colors.white70 : Colors.black54, textController: _userEmailController, inputAction: TextInputAction.next, - autoFocus: false, onChanged: (value) { _store.setUserId(_userEmailController.text); }, @@ -153,9 +147,9 @@ class _LoginScreenState extends State { builder: (context) { return TextFieldWidget( hint: - AppLocalizations.of(context).translate('login_et_user_password'), + AppLocalizations.of(context)!.login_et_user_password, isObscure: true, - padding: EdgeInsets.only(top: 16.0), + padding: const EdgeInsets.only(top: 16.0), icon: Icons.lock, iconColor: _themeStore.darkMode ? Colors.white70 : Colors.black54, textController: _passwordController, @@ -172,31 +166,32 @@ class _LoginScreenState extends State { Widget _buildForgotPasswordButton() { return Align( alignment: FractionalOffset.centerRight, - child: FlatButton( - padding: EdgeInsets.all(0.0), + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: _onForgotPassword, child: Text( - AppLocalizations.of(context).translate('login_btn_forgot_password'), + AppLocalizations.of(context)!.login_btn_forgot_password, style: Theme.of(context) .textTheme .caption ?.copyWith(color: Colors.orangeAccent), ), - onPressed: () {}, ), ); } Widget _buildSignInButton() { return RoundedButtonWidget( - buttonText: AppLocalizations.of(context).translate('login_btn_sign_in'), + buttonText: AppLocalizations.of(context)!.login_btn_sign_in, buttonColor: Colors.orangeAccent, - textColor: Colors.white, onPressed: () async { if (_store.canLogin) { DeviceUtils.hideKeyboard(context); _store.login(); } else { - _showErrorMessage('Please fill in all fields'); + ErrorMessage().showMessage('Please fill in all fields', context); } }, ); @@ -207,29 +202,27 @@ class _LoginScreenState extends State { prefs.setBool(Preferences.is_logged_in, true); }); - Future.delayed(Duration(milliseconds: 0), () { + Future.delayed(Duration.zero, () { Navigator.of(context).pushNamedAndRemoveUntil( - Routes.home, (Route route) => false); + Routes.home, (Route route) => false, + ); }); return Container(); } // General Methods:----------------------------------------------------------- - _showErrorMessage(String message) { - if (message.isNotEmpty) { - Future.delayed(Duration(milliseconds: 0), () { - if (message.isNotEmpty) { - FlushbarHelper.createError( - message: message, - title: AppLocalizations.of(context).translate('home_tv_error'), - duration: Duration(seconds: 3), - )..show(context); - } - }); + Future _onForgotPassword() async { + try { + // Doing some method that may throw error + throw Exception('Some arbitrary error'); + } catch (exception, stackTrace) { + ErrorMessage().showMessage("This is an sample error message", context); + await Sentry.captureException( + exception, + stackTrace: stackTrace, + ); } - - return SizedBox.shrink(); } // dispose:------------------------------------------------------------------- diff --git a/lib/ui/my_app.dart b/lib/ui/my_app.dart index b9d4f25b..5056767e 100644 --- a/lib/ui/my_app.dart +++ b/lib/ui/my_app.dart @@ -8,12 +8,13 @@ import 'package:boilerplate/stores/theme/theme_store.dart'; import 'package:boilerplate/stores/user/user_store.dart'; import 'package:boilerplate/ui/home/home.dart'; import 'package:boilerplate/ui/login/login.dart'; -import 'package:boilerplate/utils/locale/app_localization.dart'; import 'package:boilerplate/utils/routes/routes.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:provider/provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; class MyApp extends StatelessWidget { // This widget is the root of your application. @@ -36,18 +37,20 @@ class MyApp extends StatelessWidget { name: 'global-observer', builder: (context) { return MaterialApp( + navigatorObservers: [ + SentryNavigatorObserver(), + ], debugShowCheckedModeBanner: false, title: Strings.appName, theme: _themeStore.darkMode ? AppThemeData.darkThemeData : AppThemeData.lightThemeData, - routes: Routes.routes, + onGenerateRoute: (settings) => Routes.routes(settings), locale: Locale(_languageStore.locale), supportedLocales: _languageStore.supportedLanguages .map((language) => Locale(language.locale!, language.code)) .toList(), - localizationsDelegates: [ - // A class which loads the translations from JSON files + localizationsDelegates: const [ AppLocalizations.delegate, // Built-in localization of basic text for Material widgets GlobalMaterialLocalizations.delegate, diff --git a/lib/ui/splash/splash.dart b/lib/ui/splash/splash.dart index 4679e14b..df3b4f88 100644 --- a/lib/ui/splash/splash.dart +++ b/lib/ui/splash/splash.dart @@ -21,20 +21,21 @@ class _SplashScreenState extends State { @override Widget build(BuildContext context) { - - return Material( + return const Material( child: Center(child: AppIconWidget(image: Assets.appLogo)), ); } - startTimer() { - var _duration = Duration(milliseconds: 2000); - return Timer(_duration, navigate); + Timer startTimer() { + const Duration duration = Duration(milliseconds: 2000); + return Timer(duration, navigate); } - navigate() async { - SharedPreferences preferences = await SharedPreferences.getInstance(); + Future navigate() async { + final SharedPreferences preferences = await SharedPreferences.getInstance(); + if (!mounted) return; + if (preferences.getBool(Preferences.is_logged_in) ?? false) { Navigator.of(context).pushReplacementNamed(Routes.home); } else { diff --git a/lib/utils/device/device_utils.dart b/lib/utils/device/device_utils.dart index d3a58d1e..ca55f1da 100644 --- a/lib/utils/device/device_utils.dart +++ b/lib/utils/device/device_utils.dart @@ -8,7 +8,7 @@ class DeviceUtils { /// /// hides the keyboard if its already open /// - static hideKeyboard(BuildContext context) { + static void hideKeyboard(BuildContext context) { FocusScope.of(context).unfocus(); } @@ -35,4 +35,4 @@ class DeviceUtils { /// static double getScaledHeight(BuildContext context, double scale) => scale * MediaQuery.of(context).size.height; -} \ No newline at end of file +} diff --git a/lib/utils/dio/dio_error_util.dart b/lib/utils/dio/dio_error_util.dart index fbd08812..47f34538 100644 --- a/lib/utils/dio/dio_error_util.dart +++ b/lib/utils/dio/dio_error_util.dart @@ -4,32 +4,30 @@ class DioErrorUtil { // general methods:------------------------------------------------------------ static String handleError(DioError error) { String errorDescription = ""; - if (error is DioError) { - switch (error.type) { - case DioErrorType.cancel: - errorDescription = "Request to API server was cancelled"; - break; - case DioErrorType.connectTimeout: - errorDescription = "Connection timeout with API server"; - break; - case DioErrorType.other: - errorDescription = - "Connection to API server failed due to internet connection"; - break; - case DioErrorType.receiveTimeout: - errorDescription = "Receive timeout in connection with API server"; - break; - case DioErrorType.response: - errorDescription = - "Received invalid status code: ${error.response?.statusCode}"; - break; - case DioErrorType.sendTimeout: - errorDescription = "Send timeout in connection with API server"; - break; - } - } else { - errorDescription = "Unexpected error occured"; + + switch (error.type) { + case DioErrorType.cancel: + errorDescription = "Request to API server was cancelled"; + break; + case DioErrorType.connectTimeout: + errorDescription = "Connection timeout with API server"; + break; + case DioErrorType.other: + errorDescription = + "Connection to API server failed due to internet connection"; + break; + case DioErrorType.receiveTimeout: + errorDescription = "Receive timeout in connection with API server"; + break; + case DioErrorType.response: + errorDescription = + "Received invalid status code: ${error.response?.statusCode}"; + break; + case DioErrorType.sendTimeout: + errorDescription = "Send timeout in connection with API server"; + break; } + return errorDescription; } -} \ No newline at end of file +} diff --git a/lib/utils/encryption/xxtea.dart b/lib/utils/encryption/xxtea.dart index 184e5858..79287181 100644 --- a/lib/utils/encryption/xxtea.dart +++ b/lib/utils/encryption/xxtea.dart @@ -20,7 +20,7 @@ class _XXTeaDecoder extends Converter> { @override Map convert(String input) { - var result = json.decode(xxtea.decryptToString(input, key)!); + final result = json.decode(xxtea.decryptToString(input, key)!); if (result is Map) { return result.cast(); } diff --git a/lib/utils/locale/app_localization.dart b/lib/utils/locale/app_localization.dart deleted file mode 100644 index 1b4ea781..00000000 --- a/lib/utils/locale/app_localization.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -class AppLocalizations { - // localization variables - final Locale locale; - late Map localizedStrings; - - // Static member to have a simple access to the delegate from the MaterialApp - static const LocalizationsDelegate delegate = - _AppLocalizationsDelegate(); - - // constructor - AppLocalizations(this.locale); - - // Helper method to keep the code in the widgets concise - // Localizations are accessed using an InheritedWidget "of" syntax - static AppLocalizations of(BuildContext context) { - return Localizations.of(context, AppLocalizations)!; - } - - // This is a helper method that will load local specific strings from file - // present in lang folder - Future load() async { - // Load the language JSON file from the "lang" folder - String jsonString = - await rootBundle.loadString('assets/lang/${locale.languageCode}.json'); - Map jsonMap = json.decode(jsonString); - - localizedStrings = jsonMap.map((key, value) { - return MapEntry( - key, value.toString().replaceAll(r"\'", "'").replaceAll(r"\t", " ")); - }); - - return true; - } - - // This method will be called from every widget which needs a localized text - String translate(String key) { - return localizedStrings[key]!; - } -} - -// LocalizationsDelegate is a factory for a set of localized resources -// In this case, the localized strings will be gotten in an AppLocalizations object -class _AppLocalizationsDelegate - extends LocalizationsDelegate { - // ignore: non_constant_identifier_names - final String TAG = "AppLocalizations"; - - // This delegate instance will never change (it doesn't even have fields!) - // It can provide a constant constructor. - const _AppLocalizationsDelegate(); - - @override - bool isSupported(Locale locale) { - // Include all of your supported language codes here - return ['en', 'es', 'da'].contains(locale.languageCode); - } - - @override - Future load(Locale locale) async { - // AppLocalizations class is where the JSON loading actually runs - AppLocalizations localizations = new AppLocalizations(locale); - await localizations.load(); - return localizations; - } - - @override - bool shouldReload(_AppLocalizationsDelegate old) => false; -} diff --git a/lib/utils/message/message.dart b/lib/utils/message/message.dart new file mode 100644 index 00000000..b1f8a5d8 --- /dev/null +++ b/lib/utils/message/message.dart @@ -0,0 +1,67 @@ +import 'package:another_flushbar/flushbar_helper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +abstract class Message { + SizedBox showMessage(String message, BuildContext context); +} + +class SuccessMessage extends Message { + @override + SizedBox showMessage(String message, BuildContext context, + {int duration = 3000}) { + if (message.isNotEmpty) { + Future.delayed(Duration.zero, () { + if (message.isNotEmpty) { + FlushbarHelper.createSuccess( + message: message, + title: AppLocalizations.of(context)!.message_success, + duration: Duration(milliseconds: duration), + ).show(context); + } + }); + } + + return const SizedBox.shrink(); + } +} + +class ErrorMessage extends Message { + @override + SizedBox showMessage(String message, BuildContext context, + {int duration = 3000}) { + if (message.isNotEmpty) { + Future.delayed(Duration.zero, () { + if (message.isNotEmpty) { + FlushbarHelper.createError( + message: message, + title: AppLocalizations.of(context)!.message_error, + duration: Duration(milliseconds: duration), + ).show(context); + } + }); + } + + return const SizedBox.shrink(); + } +} + +class InfomationMessage extends Message { + @override + SizedBox showMessage(String message, BuildContext context, + {int duration = 3000}) { + if (message.isNotEmpty) { + Future.delayed(Duration.zero, () { + if (message.isNotEmpty) { + FlushbarHelper.createInformation( + message: message, + title: AppLocalizations.of(context)!.message_information, + duration: Duration(milliseconds: duration), + ).show(context); + } + }); + } + + return const SizedBox.shrink(); + } +} diff --git a/lib/utils/routes/routes.dart b/lib/utils/routes/routes.dart index 9533a07f..f96d475f 100644 --- a/lib/utils/routes/routes.dart +++ b/lib/utils/routes/routes.dart @@ -2,21 +2,48 @@ import 'package:boilerplate/ui/home/home.dart'; import 'package:boilerplate/ui/login/login.dart'; import 'package:boilerplate/ui/splash/splash.dart'; import 'package:flutter/material.dart'; +import 'package:page_transition/page_transition.dart'; class Routes { Routes._(); - //static variables + // Static variables + static const Duration duration = Duration(milliseconds: 500); static const String splash = '/splash'; static const String login = '/login'; static const String home = '/home'; - static final routes = { - splash: (BuildContext context) => SplashScreen(), - login: (BuildContext context) => LoginScreen(), - home: (BuildContext context) => HomeScreen(), - }; -} - - + static PageTransition routes(RouteSettings settings) { + switch (settings.name) { + case splash: + return PageTransition( + child: SplashScreen(), + type: PageTransitionType.rightToLeftWithFade, + settings: settings, + duration: duration, + ); + case login: + return PageTransition( + child: LoginScreen(), + type: PageTransitionType.rightToLeftWithFade, + settings: settings, + duration: duration, + ); + case home: + return PageTransition( + child: HomeScreen(), + type: PageTransitionType.rightToLeftWithFade, + settings: settings, + duration: duration, + ); + default: + return PageTransition( + child: HomeScreen(), + type: PageTransitionType.rightToLeftWithFade, + settings: settings, + duration: duration, + ); + } + } +} diff --git a/lib/widgets/app_icon_widget.dart b/lib/widgets/app_icon_widget.dart index a4567c3b..18e22a60 100644 --- a/lib/widgets/app_icon_widget.dart +++ b/lib/widgets/app_icon_widget.dart @@ -1,24 +1,24 @@ import 'package:flutter/material.dart'; class AppIconWidget extends StatelessWidget { - final image; + final String image; const AppIconWidget({ Key? key, - this.image, + required this.image, }) : super(key: key); @override Widget build(BuildContext context) { //getting screen size - var size = MediaQuery.of(context).size; + final size = MediaQuery.of(context).size; //calculating container width double imageSize; if (MediaQuery.of(context).orientation == Orientation.portrait) { - imageSize = (size.width * 0.20); + imageSize = size.width * 0.20; } else { - imageSize = (size.height * 0.20); + imageSize = size.height * 0.20; } return Image.asset( diff --git a/lib/widgets/empty_app_bar_widget.dart b/lib/widgets/empty_app_bar_widget.dart index 7ea35f26..f94d1bf5 100644 --- a/lib/widgets/empty_app_bar_widget.dart +++ b/lib/widgets/empty_app_bar_widget.dart @@ -7,5 +7,5 @@ class EmptyAppBar extends StatelessWidget implements PreferredSizeWidget { } @override - Size get preferredSize => Size(0.0, 0.0); + Size get preferredSize => Size.zero; } diff --git a/lib/widgets/progress_indicator_widget.dart b/lib/widgets/progress_indicator_widget.dart index a11eeb5d..57593372 100644 --- a/lib/widgets/progress_indicator_widget.dart +++ b/lib/widgets/progress_indicator_widget.dart @@ -1,3 +1,5 @@ +// ignore_for_file: sort_child_properties_last + import 'package:flutter/material.dart'; class CustomProgressIndicatorWidget extends StatelessWidget { @@ -8,27 +10,27 @@ class CustomProgressIndicatorWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Align( - alignment: Alignment.center, child: Container( height: 100, - constraints: BoxConstraints.expand(), + constraints: const BoxConstraints.expand(), child: FittedBox( fit: BoxFit.none, child: SizedBox( height: 100, width: 100, child: Card( - child: Padding( - padding: const EdgeInsets.all(25.0), + child: const Padding( + padding: EdgeInsets.all(25.0), child: CircularProgressIndicator(), ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0)), + borderRadius: BorderRadius.circular(10.0),), ), ), ), - decoration: BoxDecoration( - color: Color.fromARGB(100, 105, 105, 105)), + decoration: const BoxDecoration( + color: Color.fromARGB(100, 105, 105, 105), + ), ), ); } diff --git a/lib/widgets/rounded_button_widget.dart b/lib/widgets/rounded_button_widget.dart index 2a68333b..fb242efc 100644 --- a/lib/widgets/rounded_button_widget.dart +++ b/lib/widgets/rounded_button_widget.dart @@ -16,9 +16,11 @@ class RoundedButtonWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return FlatButton( - color: buttonColor, - shape: StadiumBorder(), + return TextButton( + style: ButtonStyle( + shape: MaterialStateProperty.all(const StadiumBorder()), + backgroundColor: MaterialStateProperty.all(buttonColor), + ), onPressed: onPressed, child: Text( buttonText, diff --git a/lib/widgets/textfield_widget.dart b/lib/widgets/textfield_widget.dart index 7b7fd611..85361515 100644 --- a/lib/widgets/textfield_widget.dart +++ b/lib/widgets/textfield_widget.dart @@ -28,17 +28,18 @@ class TextFieldWidget extends StatelessWidget { onChanged: onChanged, autofocus: autoFocus, textInputAction: inputAction, - obscureText: this.isObscure, + obscureText: isObscure, maxLength: 25, - keyboardType: this.inputType, + keyboardType: inputType, style: Theme.of(context).textTheme.bodyText1, decoration: InputDecoration( - hintText: this.hint, + hintText: hint, hintStyle: Theme.of(context).textTheme.bodyText1!.copyWith(color: hintColor), errorText: errorText, counterText: '', - icon: this.isIcon ? Icon(this.icon, color: iconColor) : null), + icon: isIcon ? Icon(icon, color: iconColor) : null, + ), ), ); } @@ -52,7 +53,7 @@ class TextFieldWidget extends StatelessWidget { this.hint, this.isObscure = false, this.isIcon = true, - this.padding = const EdgeInsets.all(0), + this.padding = EdgeInsets.zero, this.hintColor = Colors.grey, this.iconColor = Colors.grey, this.focusNode, diff --git a/pubspec.yaml b/pubspec.yaml index 35b369ab..523fb6dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,9 +16,9 @@ dependencies: flutter: sdk: flutter + intl: ^0.17.0 flutter_localizations: sdk: flutter - flutter_cupertino_localizations: ^1.0.1 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -39,7 +39,7 @@ dependencies: flutter_mobx: ^2.0.0 # An helper to easily exposes a value using InheritedWidget without having to write one. - provider: ^5.0.0 + provider: ^6.0.3 # String validation and sanitization for Dart. Dart 2-compatible version of validator validators: ^3.0.0 @@ -54,10 +54,6 @@ dependencies: # Material Dialog material_dialog: ^1.0.0-nullsafety.1 - # This package provides internationalization and localization facilities, including message - # translation, plurals and genders, date/number formatting and parsing, and bidirectional text. - intl: ^0.17.0 - # XXTEA is a fast and secure encryption algorithm. This is a XXTEA library for Dart. xxtea: ^2.1.0 @@ -69,6 +65,8 @@ dependencies: get_it: ^7.1.3 google_fonts: ^2.3.2 + page_transition: ^2.0.9 + sentry_flutter: ^6.9.0 dev_dependencies: flutter_test: @@ -82,6 +80,7 @@ dev_dependencies: mobx_codegen: ^2.0.1+3 build_runner: ^1.12.2 analyzer: ^1.4.0 + lint: ^1.10.0 flutter_icons: image_path: "assets/icons/ic_launcher.png" @@ -94,7 +93,9 @@ flutter_icons: # The following section is specific to Flutter. flutter: - + # The following line to make our application will auto generate l10n files + generate: true + # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class.