├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_template.md │ ├── chore_template.md │ ├── config.yml │ ├── feature_template.md │ └── story_template.md ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ └── release_template.md ├── images │ └── table-of-contents.png ├── wiki │ ├── Deployment.md │ ├── Home.md │ └── _Sidebar.md └── workflows │ ├── android_deploy_production.yml │ ├── android_deploy_production_to_playstore.yml │ ├── android_deploy_staging.yml │ ├── bump_version.yml │ ├── configs │ └── changelog-config.json │ ├── ios_deploy_staging_to_firebase.yml │ ├── ios_deploy_to_app_store.yml │ ├── ios_deploy_to_testflight.yml │ ├── publish_docs_to_wiki.yml │ └── test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bricks ├── permission_handler │ ├── README.md │ ├── __brick__ │ │ ├── lib │ │ │ └── utils │ │ │ │ └── wrappers │ │ │ │ └── permission_wrapper.dart │ │ ├── {{~ _Podfile_build_configurations }} │ │ └── {{~ _pubspec_dependency.yaml }} │ └── brick.yaml └── template │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── __brick__ │ ├── .env.sample │ ├── .github │ │ ├── CODEOWNERS │ │ ├── ISSUE_TEMPLATE │ │ │ ├── bug_template.md │ │ │ ├── chore_template.md │ │ │ ├── feature_template.md │ │ │ └── story_template.md │ │ ├── PULL_REQUEST_TEMPLATE.md │ │ ├── PULL_REQUEST_TEMPLATE │ │ │ └── release_template.md │ │ ├── wiki │ │ │ └── .keep │ │ └── workflows │ │ │ ├── android_deploy_production.yml │ │ │ ├── android_deploy_production_to_playstore.yml │ │ │ ├── android_deploy_staging.yml │ │ │ ├── bump_version.yml │ │ │ ├── configs │ │ │ └── changelog-config.json │ │ │ ├── ios_deploy_staging_to_firebase.yml │ │ │ ├── ios_deploy_to_app_store.yml │ │ │ ├── ios_deploy_to_testflight.yml │ │ │ ├── publish_wiki.yml │ │ │ ├── test.yml │ │ │ └── whatsnew │ │ │ └── whatsnew-en-US │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── {{package_name.pathCase()}} │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── values-night │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── profile │ │ │ │ └── AndroidManifest.xml │ │ │ │ └── staging │ │ │ │ └── res │ │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── build.gradle │ │ ├── config │ │ │ └── debug.keystore │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ ├── settings.gradle │ │ └── signing.properties.sample │ ├── assets │ │ ├── fonts │ │ │ └── neuzeit.otf │ │ ├── images │ │ │ └── nimble_logo.png │ │ └── svg │ │ │ └── flutter_logo.svg │ ├── build.yaml │ ├── codecov.yml │ ├── integration_test │ │ ├── real_app_test.dart │ │ ├── screens │ │ │ └── home_screen_test.dart │ │ └── utils │ │ │ └── test_util.dart │ ├── ios │ │ ├── .gitignore │ │ ├── Flutter │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Gemfile │ │ ├── Gemfile.lock │ │ ├── Podfile │ │ ├── Podfile.lock │ │ ├── Runner.xcodeproj │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ ├── production.xcscheme │ │ │ │ └── staging.xcscheme │ │ ├── Runner.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ ├── Runner │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ │ └── LaunchImage.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── LaunchImage.png │ │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ │ └── README.md │ │ │ ├── Base.lproj │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ ├── RunnerTests │ │ │ └── RunnerTests.swift │ │ └── fastlane │ │ │ ├── Constants │ │ │ ├── Constants.rb │ │ │ └── Environments.rb │ │ │ ├── Fastfile │ │ │ ├── Gymfile │ │ │ ├── Managers │ │ │ ├── BuildManager.rb │ │ │ ├── DistributionManager.rb │ │ │ └── MatchManager.rb │ │ │ ├── Matchfile │ │ │ └── Pluginfile │ ├── l10n.yaml │ ├── lib │ │ ├── app │ │ │ ├── resources │ │ │ │ └── app_colors.dart │ │ │ └── screens │ │ │ │ └── home │ │ │ │ ├── home_screen.dart │ │ │ │ ├── home_view_model.dart │ │ │ │ └── home_view_state.dart │ │ ├── data │ │ │ ├── local │ │ │ │ └── secure_storage.dart │ │ │ ├── remote │ │ │ │ ├── datasources │ │ │ │ │ └── api_service.dart │ │ │ │ └── models │ │ │ │ │ ├── requests │ │ │ │ │ └── .keep │ │ │ │ │ └── responses │ │ │ │ │ └── user_response.dart │ │ │ └── repositories │ │ │ │ └── credential_repository_impl.dart │ │ ├── di │ │ │ ├── di.dart │ │ │ ├── interceptor │ │ │ │ └── app_interceptor.dart │ │ │ ├── module │ │ │ │ ├── network_module.dart │ │ │ │ └── storage_module.dart │ │ │ └── provider │ │ │ │ └── dio_provider.dart │ │ ├── domain │ │ │ ├── exceptions │ │ │ │ └── network_exceptions.dart │ │ │ ├── models │ │ │ │ └── user.dart │ │ │ ├── repositories │ │ │ │ └── credential_repository.dart │ │ │ └── usecases │ │ │ │ ├── base │ │ │ │ ├── base_use_case.dart │ │ │ │ └── use_case_result.dart │ │ │ │ └── get_users_use_case.dart │ │ ├── env.dart │ │ ├── l10n │ │ │ ├── app_en.arb │ │ │ ├── app_th.arb │ │ │ └── app_vi.arb │ │ └── main.dart │ ├── pubspec.lock │ ├── pubspec.yaml │ ├── test │ │ ├── app │ │ │ └── screens │ │ │ │ └── home │ │ │ │ └── home_view_model_test.dart │ │ ├── data │ │ │ └── repositories │ │ │ │ └── credential_repository_test.dart │ │ ├── domain │ │ │ └── usecases │ │ │ │ └── get_users_use_case_test.dart │ │ └── mocks │ │ │ ├── data │ │ │ └── remote │ │ │ │ └── models │ │ │ │ └── responses │ │ │ │ └── user_response_mocks.dart │ │ │ └── generate_mocks.dart │ └── test_driver │ │ └── integration_test.dart │ ├── brick.yaml │ └── hooks │ ├── .gitignore │ ├── bundles │ └── permission_handler_bundle.dart │ ├── hooks_util.dart │ ├── post_gen.dart │ ├── pre_gen.dart │ ├── pubspec.lock │ └── pubspec.yaml ├── mason-config.json ├── mason.yaml └── sample ├── .env.sample ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_template.md │ ├── chore_template.md │ ├── feature_template.md │ └── story_template.md ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ └── release_template.md ├── wiki │ └── .keep └── workflows │ ├── android_deploy_production.yml │ ├── android_deploy_production_to_playstore.yml │ ├── android_deploy_staging.yml │ ├── bump_version.yml │ ├── configs │ └── changelog-config.json │ ├── ios_deploy_staging_to_firebase.yml │ ├── ios_deploy_to_app_store.yml │ ├── ios_deploy_to_testflight.yml │ ├── publish_wiki.yml │ ├── test.yml │ └── whatsnew │ └── whatsnew-en-US ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── co │ │ │ │ └── nimblehq │ │ │ │ └── flutter │ │ │ │ └── template │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── profile │ │ └── AndroidManifest.xml │ │ └── staging │ │ └── res │ │ └── values │ │ └── strings.xml ├── build.gradle ├── config │ └── debug.keystore ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── signing.properties.sample ├── assets ├── fonts │ └── neuzeit.otf ├── images │ └── nimble_logo.png └── svg │ └── flutter_logo.svg ├── build.yaml ├── codecov.yml ├── integration_test ├── real_app_test.dart ├── screens │ └── home_screen_test.dart └── utils │ └── test_util.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Gemfile ├── Gemfile.lock ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ ├── production.xcscheme │ │ └── staging.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── RunnerTests │ └── RunnerTests.swift └── fastlane │ ├── Constants │ ├── Constants.rb │ └── Environments.rb │ ├── Fastfile │ ├── Gymfile │ ├── Managers │ ├── BuildManager.rb │ ├── DistributionManager.rb │ └── MatchManager.rb │ ├── Matchfile │ └── Pluginfile ├── l10n.yaml ├── lib ├── app │ ├── resources │ │ └── app_colors.dart │ └── screens │ │ └── home │ │ ├── home_screen.dart │ │ ├── home_view_model.dart │ │ └── home_view_state.dart ├── data │ ├── local │ │ └── secure_storage.dart │ ├── remote │ │ ├── datasources │ │ │ └── api_service.dart │ │ └── models │ │ │ ├── requests │ │ │ └── .keep │ │ │ └── responses │ │ │ └── user_response.dart │ └── repositories │ │ └── credential_repository_impl.dart ├── di │ ├── di.dart │ ├── interceptor │ │ └── app_interceptor.dart │ ├── module │ │ ├── network_module.dart │ │ └── storage_module.dart │ └── provider │ │ └── dio_provider.dart ├── domain │ ├── exceptions │ │ └── network_exceptions.dart │ ├── models │ │ └── user.dart │ ├── repositories │ │ └── credential_repository.dart │ └── usecases │ │ ├── base │ │ ├── base_use_case.dart │ │ └── use_case_result.dart │ │ └── get_users_use_case.dart ├── env.dart ├── l10n │ ├── app_en.arb │ ├── app_th.arb │ └── app_vi.arb ├── main.dart └── utils │ └── wrappers │ └── permission_wrapper.dart ├── pubspec.lock ├── pubspec.yaml ├── test ├── app │ └── screens │ │ └── home │ │ └── home_view_model_test.dart ├── data │ └── repositories │ │ └── credential_repository_test.dart ├── domain │ └── usecases │ │ └── get_users_use_case_test.dart └── mocks │ ├── data │ └── remote │ │ └── models │ │ └── responses │ │ └── user_response_mocks.dart │ └── generate_mocks.dart └── test_driver └── integration_test.dart /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Team 2 | # @luongvo is the Team Lead 3 | * @luongvo @manh-t @markgravity @sleepylee @chornerman @doannimble @hoangnguyen92dn @nmint8m @ducbm051291 @thiennguyen0196 @kaungkhantsoe @Shayokh144 @toby-thanathip @Wadeewee 4 | 5 | # Engineering Leads 6 | CODEOWNERS @nimblehq/engineering-leads 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug Report" 3 | about: "You found something that is not working. Report it so that it can be fixed. 👷‍" 4 | title: "Fix: " 5 | labels: "type : bug" 6 | --- 7 | 8 | ## Issue 9 | 10 | Describe the issue you are facing. Show us the implementation: screenshots, GIFs, etc. 11 | 12 | ## Expected 13 | 14 | Describe what should be the correct behavior. 15 | 16 | ## Steps to reproduce 17 | 18 | 1. 19 | 2. 20 | 3. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/chore_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Chore" 3 | about: "Open a chore issue for a minor update." 4 | title: "Update " 5 | labels: "type : chore" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the update in detail and why it is needed. 11 | 12 | ## Who Benefits? 13 | 14 | Describe who will be the beneficiaries e.g. everyone, specific chapters, clients... 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Request For Comments (RFC) 5 | about: When having an idea on how to improve our processes, propose the idea so that the team can provide feedback. 6 | # URL to create an RFC with the template 7 | url: https://github.com/nimblehq/flutter-templates/discussions/new?category=rfcs&title=+&body=%23%23+Issue%0D%0A%0D%0ADescribe+the+issue+the+team+is+currently+facing.+Provide+as+much+content+as+possible.%0D%0A%0D%0A%23%23+Solution%0D%0A%0D%0ADescribe+the+solution+you+are+prescribing+for+the+issue%0D%0A%0D%0A%23%23+Who+Benefits%3F%0D%0A%0D%0ADescribe+who+will+be+the+beneficiaries+e.g.+everyone,+specific+chapters,+clients...%0D%0A%0D%0A%23%23+What%27s+Next%3F%0D%0A%0D%0AProvide+an+actionable+list+of+things+that+must+happen+in+order+to+implement+the+solution:%0D%0A%0D%0A-+[+]%0D%0A-+[+]%0D%0A-+[+] 8 | 9 | - name: Ask a question 10 | about: When having a question about a pull request, issue, or problem, create a Q&A discussion to get help from the team. 11 | url: https://github.com/nimblehq/flutter-templates/discussions/new?category=q-a 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature" 3 | about: "Open a feature issue to add new functionalities." 4 | title: "Add " 5 | labels: "type : feature" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the big picture of the feature and why it is needed. 11 | 12 | ## Who Benefits? 13 | 14 | Describe who will be the beneficiaries e.g. everyone, specific chapters, clients... 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/story_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Story" 3 | about: "Open a feature story" 4 | title: "[Type] As a user, I can " 5 | labels: "type : feature" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the idea of the user story as in what the motive of the user story is. 11 | 12 | ## Acceptance Criteria 13 | 14 | List down how the user story will be tested and what criteria are necessary for the user story to be accepted. 15 | 16 | ## Design 17 | 18 | (Optional) Add design screenshots or Figma links for UI/UX-related stories. 19 | 20 | ## Resources 21 | 22 | (Optional) Add useful resources such as links to documentation, implementation ideas, or best practices. 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Note: for a release PR, append this parameter `?template=release_template.md` to the current URL to apply the release PR 2 | template, e.g. `{Github PR URL}?template=release_template.md` 3 | 4 | -- 5 | 6 | - Close # 7 | 8 | ## What happened 👀 9 | 10 | Provide a description of the **changes** this pull request brings to the codebase. Additionally, when the pull request is still being worked on, a checklist of the planned changes is welcome to track progress. 11 | 12 | ## Insight 📝 13 | 14 | Describe in detail why this solution is the most appropriate, which solution you tried but did not go with, and how to test the changes. References to relevant documentation are welcome as well. 15 | 16 | ## Proof Of Work 📹 17 | 18 | Show us the implementation: screenshots, GIFs, etc. 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/release_template.md: -------------------------------------------------------------------------------- 1 | https://github.com/nimblehq/flutter-templates/milestone/? 2 | 3 | ## Features 4 | 5 | Provide the ID and title of the issue in the section for each type (feature, chore and bug). The link is optional. 6 | 7 | - # 8 | 9 | ## Chores 10 | 11 | - # 12 | 13 | ## Bugs 14 | 15 | - # 16 | -------------------------------------------------------------------------------- /.github/images/table-of-contents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/.github/images/table-of-contents.png -------------------------------------------------------------------------------- /.github/wiki/Home.md: -------------------------------------------------------------------------------- 1 | Welcome to the Flutter-Templates wiki! 2 | -------------------------------------------------------------------------------- /.github/wiki/_Sidebar.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | - [[Home]] 4 | 5 | ## Infrastructure 6 | 7 | - [[Deployment]] 8 | -------------------------------------------------------------------------------- /.github/workflows/android_deploy_production_to_playstore.yml: -------------------------------------------------------------------------------- 1 | name: Android - Deploy Production build to Play Store 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build_and_deploy_android: 10 | name: Android - Deploy Production build to Play Store 11 | runs-on: ubuntu-latest 12 | environment: production 13 | timeout-minutes: 30 14 | steps: 15 | - name: Check out 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Java JDK 19 | uses: actions/setup-java@v3 20 | with: 21 | distribution: 'adopt' 22 | java-version: '11' 23 | 24 | - name: Set up Flutter environment 25 | uses: subosito/flutter-action@v2 26 | with: 27 | channel: 'stable' 28 | flutter-version: '3.10.5' 29 | 30 | - name: Generate new project 31 | run: | 32 | dart pub global activate mason_cli 33 | mason get 34 | mason make template -c mason-config.json 35 | 36 | - name: Get Flutter dependencies 37 | run: flutter pub get 38 | 39 | - name: Run code generator 40 | run: flutter packages pub run build_runner build --delete-conflicting-outputs 41 | 42 | - name: Set up .env 43 | env: 44 | ENV: ${{ secrets.ENV }} 45 | run: | 46 | echo -e "$ENV" > .env 47 | 48 | - name: Set up release signing configs 49 | env: 50 | ANDROID_RELEASE_KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }} 51 | ANDROID_SIGNING_PROPERTIES: ${{ secrets.ANDROID_SIGNING_PROPERTIES }} 52 | run: | 53 | echo $ANDROID_RELEASE_KEYSTORE_BASE64 | base64 --decode > android/config/release.keystore 54 | echo "$ANDROID_SIGNING_PROPERTIES" > android/signing.properties 55 | 56 | - name: Build Production App Bundle 57 | run: flutter build appbundle --flavor production --release --build-number $GITHUB_RUN_NUMBER 58 | 59 | # TODO We will enable this step later when having a shared Service Account on our organization 60 | # Basically, this step will work properly after providing a valid Service Account. 61 | # - name: Upload Android Production Release bundle to Play Store 62 | # uses: r0adkll/upload-google-play@v1 63 | # with: 64 | # serviceAccountJson: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} 65 | # packageName: co.nimblehq.flutter.template 66 | # releaseFile: build/app/outputs/bundle/productionRelease/app-production-release.aab 67 | # track: internal 68 | # userFraction: 1.0 69 | # whatsNewDirectory: .github/workflows/whatsnew 70 | -------------------------------------------------------------------------------- /.github/workflows/bump_version.yml: -------------------------------------------------------------------------------- 1 | name: Bump Version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | newVersion: 7 | description: "New version" 8 | required: true 9 | type: string 10 | 11 | jobs: 12 | bump_version: 13 | name: Bump version 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 5 16 | steps: 17 | - name: Check out 18 | uses: actions/checkout@v3 19 | 20 | - name: Set new version 21 | uses: jossef/action-set-json-field@v2.1 22 | with: 23 | file: mason-config.json 24 | field: app_version 25 | value: ${{ github.event.inputs.newVersion }} 26 | 27 | - name: Create pull request 28 | uses: peter-evans/create-pull-request@v4 29 | with: 30 | assignees: ${{ secrets.GITHUB_USER }} 31 | token: ${{ secrets.WIKI_ACTION_TOKEN }} 32 | commit-message: Bump version to ${{ github.event.inputs.newVersion }} 33 | committer: Nimble Bot 34 | branch: chore/bump-version-to-${{ github.event.inputs.newVersion }} 35 | delete-branch: true 36 | title: '[Chore] Bump version to ${{ github.event.inputs.newVersion }}' 37 | labels: | 38 | type : chore 39 | body: | 40 | ## What happened 👀 41 | 42 | Bump version to ${{ github.event.inputs.newVersion }} 43 | 44 | ## Insight 📝 45 | 46 | Automatically created by the Bump Version workflow. 47 | 48 | ## Proof Of Work 📹 49 | 50 | On the Files changed tab 51 | -------------------------------------------------------------------------------- /.github/workflows/configs/changelog-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## ✨ Features", 5 | "labels": [ 6 | "type : feature" 7 | ] 8 | }, 9 | { 10 | "title": "## 🐛 Bug fixes", 11 | "labels": [ 12 | "type : bug" 13 | ] 14 | }, 15 | { 16 | "title": "## 🧹 Chores", 17 | "labels": [ 18 | "type : chore" 19 | ] 20 | }, 21 | { 22 | "title": "## Others", 23 | "exclude_labels": [ 24 | "type : feature", 25 | "type : bug", 26 | "type : chore", 27 | "type : release" 28 | ] 29 | } 30 | ], 31 | "max_pull_requests": 200 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/publish_docs_to_wiki.yml: -------------------------------------------------------------------------------- 1 | # For setup instruction, refer to https://github.com/nimblehq/github-actions-workflows/blob/main/.github/workflows/publish_wiki.yml 2 | name: Publish Wiki 3 | 4 | on: 5 | push: 6 | paths: 7 | - .github/wiki/** 8 | branches: 9 | - develop 10 | 11 | jobs: 12 | publish_wiki: 13 | name: Publish Wiki 14 | uses: nimblehq/github-actions-workflows/.github/workflows/publish_wiki.yml@0.1.0 15 | with: 16 | USER_NAME: team-nimblehq 17 | USER_EMAIL: bot@nimblehq.co 18 | secrets: 19 | USER_TOKEN: ${{ secrets.WIKI_ACTION_TOKEN }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | .DS_Store 3 | 4 | # VSCode 5 | .vscode 6 | 7 | # Intellij 8 | *.iml 9 | .idea/* 10 | 11 | # Mason 12 | mason-lock.json 13 | .mason/ 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 and onwards Nimble. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /bricks/permission_handler/README.md: -------------------------------------------------------------------------------- 1 | # permission_handler 2 | 3 | [![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason) 4 | 5 | A new brick created with the Mason CLI. 6 | 7 | ## Description 🚀 8 | 9 | This brick has the simple implementation of [permission_handler](https://pub.dev/packages/permission_handler) package. 10 | -------------------------------------------------------------------------------- /bricks/permission_handler/__brick__/lib/utils/wrappers/permission_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:permission_handler/permission_handler.dart' 2 | as permission_handler; 3 | 4 | abstract class PermissionWrapper { 5 | Future requestCameraPermission(); 6 | 7 | Future isCameraPermissionDenied(); 8 | 9 | Future isCameraPermissionPermanentlyDenied(); 10 | } 11 | 12 | class PermissionWrapperImpl extends PermissionWrapper { 13 | @override 14 | Future requestCameraPermission() { 15 | return permission_handler.Permission.camera.request().then( 16 | (status) => status == permission_handler.PermissionStatus.granted); 17 | } 18 | 19 | @override 20 | Future isCameraPermissionDenied() { 21 | return permission_handler.Permission.camera.isDenied; 22 | } 23 | 24 | @override 25 | Future isCameraPermissionPermanentlyDenied() { 26 | return permission_handler.Permission.camera.isPermanentlyDenied; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bricks/permission_handler/__brick__/{{~ _Podfile_build_configurations }}: -------------------------------------------------------------------------------- 1 | # TODO You can enable the permissions needed here. 2 | # permission, just remove the `#` character in front so it looks like this: 3 | # 4 | # ## dart: PermissionGroup.camera 5 | #'PERMISSION_CAMERA=1' 6 | # 7 | # Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler/ios/Classes/PermissionHandlerEnums.h 8 | config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 9 | '$(inherited)', 10 | 11 | ## dart: PermissionGroup.calendar 12 | # 'PERMISSION_EVENTS=1', 13 | 14 | ## dart: PermissionGroup.reminders 15 | # 'PERMISSION_REMINDERS=1', 16 | 17 | ## dart: PermissionGroup.contacts 18 | # 'PERMISSION_CONTACTS=1', 19 | 20 | ## dart: PermissionGroup.camera 21 | # 'PERMISSION_CAMERA=1', 22 | 23 | ## dart: PermissionGroup.microphone 24 | # 'PERMISSION_MICROPHONE=1', 25 | 26 | ## dart: PermissionGroup.speech 27 | # 'PERMISSION_SPEECH_RECOGNIZER=1', 28 | 29 | ## dart: PermissionGroup.photos 30 | # 'PERMISSION_PHOTOS=1', 31 | 32 | ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse] 33 | # 'PERMISSION_LOCATION=1', 34 | 35 | ## dart: PermissionGroup.notification 36 | # 'PERMISSION_NOTIFICATIONS=1', 37 | 38 | ## dart: PermissionGroup.mediaLibrary 39 | # 'PERMISSION_MEDIA_LIBRARY=1', 40 | 41 | ## dart: PermissionGroup.sensors 42 | # 'PERMISSION_SENSORS=1', 43 | 44 | ## dart: PermissionGroup.bluetooth 45 | # 'PERMISSION_BLUETOOTH=1', 46 | 47 | ## dart: PermissionGroup.appTrackingTransparency 48 | # 'PERMISSION_APP_TRACKING_TRANSPARENCY=1', 49 | 50 | ## dart: PermissionGroup.criticalAlerts 51 | # 'PERMISSION_CRITICAL_ALERTS=1' 52 | ] -------------------------------------------------------------------------------- /bricks/permission_handler/__brick__/{{~ _pubspec_dependency.yaml }}: -------------------------------------------------------------------------------- 1 | 2 | permission_handler: ^10.2.0 -------------------------------------------------------------------------------- /bricks/permission_handler/brick.yaml: -------------------------------------------------------------------------------- 1 | name: permission_handler 2 | description: A new brick created with the Mason CLI. 3 | 4 | # The following defines the version and build number for your brick. 5 | # A version number is three numbers separated by dots, like 1.2.34 6 | # followed by an optional build number (separated by a +). 7 | version: 0.1.0+1 8 | 9 | # The following defines the environment for the current brick. 10 | # It includes the version of mason that the brick requires. 11 | environment: 12 | mason: ">=0.1.0-dev.41 <0.1.0" 13 | 14 | # Variables specify dynamic values that your brick depends on. 15 | # Zero or more variables can be specified for a given brick. 16 | # Each variable has: 17 | # * a type (string, number, boolean, enum, or array) 18 | # * an optional short description 19 | # * an optional default value 20 | # * an optional list of default values (array only) 21 | # * an optional prompt phrase used when asking for the variable 22 | # * a list of values (enums only) 23 | vars: 24 | # Injected from the root brick by post_gen hook 25 | project_name: 26 | type: string 27 | -------------------------------------------------------------------------------- /bricks/template/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0+1 2 | 3 | - TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /bricks/template/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /bricks/template/README.md: -------------------------------------------------------------------------------- 1 | # template 2 | 3 | [![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason) 4 | 5 | A new brick created with the Mason CLI. 6 | 7 | _Generated by [mason][1] 🧱_ 8 | 9 | ## Getting Started 🚀 10 | 11 | This is a starting point for a new brick. 12 | A few resources to get you started if this is your first brick template: 13 | 14 | - [Official Mason Documentation][2] 15 | - [Code generation with Mason Blog][3] 16 | - [Very Good Livestream: Felix Angelov Demos Mason][4] 17 | 18 | [1]: https://github.com/felangel/mason 19 | [2]: https://github.com/felangel/mason/tree/master/packages/mason_cli#readme 20 | [3]: https://verygood.ventures/blog/code-generation-with-mason 21 | [4]: https://youtu.be/G4PTjA6tpTU 22 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.env.sample: -------------------------------------------------------------------------------- 1 | SECRET= 2 | REST_API_ENDPOINT= 3 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Team 2 | # @teamlead is the Team Lead 3 | * @teammember1 @teammember2 4 | 5 | # Engineering Leads 6 | CODEOWNERS @nimblehq/engineering-leads 7 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/ISSUE_TEMPLATE/bug_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug Report" 3 | about: "You found something that is not working. Report it so that it can be fixed. 👷‍" 4 | title: "Fix: " 5 | labels: "type : bug" 6 | --- 7 | 8 | ## Issue 9 | 10 | Describe the issue you are facing. Show us the implementation: screenshots, GIFs, etc. 11 | 12 | ## Expected 13 | 14 | Describe what should be the correct behavior. 15 | 16 | ## Steps to reproduce 17 | 18 | 1. 19 | 2. 20 | 3. 21 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/ISSUE_TEMPLATE/chore_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Chore" 3 | about: "Open a chore issue for a minor update." 4 | title: "Update " 5 | labels: "type : chore" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the update in detail and why it is needed. 11 | 12 | ## Who Benefits? 13 | 14 | Describe who will be the beneficiaries e.g. everyone, specific chapters, clients... 15 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/ISSUE_TEMPLATE/feature_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature" 3 | about: "Open a feature issue to add new functionalities." 4 | title: "Add " 5 | labels: "type : feature" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the big picture of the feature and why it is needed. 11 | 12 | ## Who Benefits? 13 | 14 | Describe who will be the beneficiaries e.g. everyone, specific chapters, clients... 15 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/ISSUE_TEMPLATE/story_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Story" 3 | about: "Open a feature story" 4 | title: "[Type] As a user, I can " 5 | labels: "type : feature" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the idea of the user story as in what the motive of the user story is. 11 | 12 | ## Acceptance Criteria 13 | 14 | List down how the user story will be tested and what criteria are necessary for the user story to be accepted. 15 | 16 | ## Design 17 | 18 | (Optional) Add design screenshots or Figma links for UI/UX-related stories. 19 | 20 | ## Resources 21 | 22 | (Optional) Add useful resources such as links to documentation, implementation ideas, or best practices. 23 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - Close # 2 | 3 | ## What happened 👀 4 | 5 | Provide a description of the **changes** this pull request brings to the codebase. Additionally, when the pull request is still being worked on, a checklist of the planned changes is welcome to track progress. 6 | 7 | ## Insight 📝 8 | 9 | Describe in detail why this solution is the most appropriate, which solution you tried but did not go with, and how to test the changes. References to relevant documentation are welcome as well. 10 | 11 | ## Proof Of Work 📹 12 | 13 | Show us the implementation: screenshots, GIFs, etc. 14 | 15 | 16 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/PULL_REQUEST_TEMPLATE/release_template.md: -------------------------------------------------------------------------------- 1 | Link to the milestone on Github e.g. https://github.com/nimblehq/{{project_name.paramCase()}}/milestone/41?closed=1 2 | or Link to the project management tool for the release 3 | 4 | ## Features 5 | 6 | Provide the ID and title of the issue in the section for each type (feature, chore and bug). The link is optional. 7 | 8 | - [#1] As a user, I can log in or 9 | - [[#1](https://github.com/nimblehq/{{project_name.paramCase()}}/issues/1)] As a user, I can log in 10 | 11 | ## Chores 12 | - Same structure as in ## Feature 13 | 14 | ## Bugs 15 | - Same structure as in ## Feature 16 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/wiki/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/.github/wiki/.keep -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/workflows/android_deploy_production_to_playstore.yml: -------------------------------------------------------------------------------- 1 | name: Android - Deploy Production build to Play Store 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build_and_deploy_android: 10 | name: Android - Deploy Production build to Play Store 11 | runs-on: ubuntu-latest 12 | environment: production 13 | timeout-minutes: 30 14 | steps: 15 | - name: Check out 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Java JDK 19 | uses: actions/setup-java@v3 20 | with: 21 | distribution: 'adopt' 22 | java-version: '11' 23 | 24 | - name: Set up Flutter environment 25 | uses: subosito/flutter-action@v2 26 | with: 27 | channel: 'stable' 28 | flutter-version: '3.10.5' 29 | 30 | - name: Get Flutter dependencies 31 | run: flutter pub get 32 | 33 | - name: Run code generator 34 | run: flutter packages pub run build_runner build --delete-conflicting-outputs 35 | 36 | - name: Set up .env 37 | env: 38 | ENV: ${{#mustacheCase}}secrets.ENV{{/mustacheCase}} 39 | run: | 40 | echo -e "$ENV" > .env 41 | 42 | - name: Set up release signing configs 43 | env: 44 | ANDROID_RELEASE_KEYSTORE_BASE64: ${{#mustacheCase}}secrets.ANDROID_RELEASE_KEYSTORE_BASE64{{/mustacheCase}} 45 | ANDROID_SIGNING_PROPERTIES: ${{#mustacheCase}}secrets.ANDROID_SIGNING_PROPERTIES{{/mustacheCase}} 46 | run: | 47 | echo $ANDROID_RELEASE_KEYSTORE_BASE64 | base64 --decode > android/config/release.keystore 48 | echo "$ANDROID_SIGNING_PROPERTIES" > android/signing.properties 49 | 50 | - name: Build Production App Bundle 51 | run: flutter build appbundle --flavor production --release --build-number $GITHUB_RUN_NUMBER 52 | 53 | - name: Upload Android Production Release bundle to Play Store 54 | uses: r0adkll/upload-google-play@v1 55 | with: 56 | serviceAccountJson: ${{#mustacheCase}}secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON{{/mustacheCase}} 57 | packageName: {{package_name.dotCase()}} 58 | releaseFile: build/app/outputs/bundle/productionRelease/app-production-release.aab 59 | track: internal 60 | userFraction: 1.0 61 | whatsNewDirectory: .github/workflows/whatsnew 62 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/workflows/bump_version.yml: -------------------------------------------------------------------------------- 1 | name: Bump Version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | newVersion: 7 | description: "New version" 8 | required: true 9 | type: string 10 | 11 | jobs: 12 | bump_version: 13 | name: Bump version 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 5 16 | steps: 17 | - name: Check out 18 | uses: actions/checkout@v3 19 | 20 | - name: Set new version 21 | run: | 22 | perl -i -pe 's/^(version:\s+\d+\.\d+\.\d+\+)(\d+)$/"version: ${{#mustacheCase}}github.event.inputs.newVersion{{/mustacheCase}}+".($2+1)/e' ./pubspec.yaml 23 | 24 | - name: Create pull request 25 | uses: peter-evans/create-pull-request@v4 26 | with: 27 | assignees: ${{#mustacheCase}}secrets.GITHUB_USER{{/mustacheCase}} 28 | token: ${{#mustacheCase}}secrets.WIKI_ACTION_TOKEN{{/mustacheCase}} 29 | commit-message: Bump version to ${{#mustacheCase}}github.event.inputs.newVersion{{/mustacheCase}} 30 | committer: Nimble Bot 31 | branch: chore/bump-version-to-${{#mustacheCase}}github.event.inputs.newVersion{{/mustacheCase}} 32 | delete-branch: true 33 | title: '[Chore] Bump version to ${{#mustacheCase}}github.event.inputs.newVersion{{/mustacheCase}}' 34 | labels: | 35 | type : chore 36 | body: | 37 | ## What happened 👀 38 | 39 | Bump version to ${{#mustacheCase}}github.event.inputs.newVersion{{/mustacheCase}} 40 | 41 | ## Insight 📝 42 | 43 | Automatically created by the Bump Version workflow. 44 | 45 | ## Proof Of Work 📹 46 | 47 | On the Files changed tab 48 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/workflows/configs/changelog-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## ✨ Features", 5 | "labels": [ 6 | "type : feature" 7 | ] 8 | }, 9 | { 10 | "title": "## 🐛 Bug fixes", 11 | "labels": [ 12 | "type : bug" 13 | ] 14 | }, 15 | { 16 | "title": "## 🧹 Chores", 17 | "labels": [ 18 | "type : chore" 19 | ] 20 | }, 21 | { 22 | "title": "## Others", 23 | "exclude_labels": [ 24 | "type : feature", 25 | "type : bug", 26 | "type : chore", 27 | "type : release" 28 | ] 29 | } 30 | ], 31 | "max_pull_requests": 200 32 | } 33 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/workflows/publish_wiki.yml: -------------------------------------------------------------------------------- 1 | # For setup instruction, refer to https://github.com/nimblehq/github-actions-workflows/blob/main/.github/workflows/publish_wiki.yml 2 | name: Publish Wiki 3 | 4 | on: 5 | push: 6 | paths: 7 | - .github/wiki/** 8 | branches: 9 | - develop 10 | 11 | jobs: 12 | publish: 13 | name: Publish Wiki 14 | uses: nimblehq/github-actions-workflows/.github/workflows/publish_wiki.yml@0.1.0 15 | with: 16 | USER_NAME: github-wiki-workflow 17 | USER_EMAIL: ${{#mustacheCase}}secrets.GH_EMAIL{{/mustacheCase}} 18 | secrets: 19 | USER_TOKEN: ${{#mustacheCase}}secrets.GH_TOKEN{{/mustacheCase}} 20 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | # Trigger the workflow on push or pull request, 4 | # but push action is only for the feature branch 5 | pull_request: 6 | types: [ opened, synchronize, edited, reopened ] 7 | push: 8 | branches-ignore: 9 | - develop 10 | - 'release/**' 11 | jobs: 12 | lint_and_test: 13 | name: Static code analyze & Unit test 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 30 16 | environment: staging 17 | steps: 18 | - name: Check out 19 | uses: actions/checkout@v3 20 | 21 | - name: Set up Flutter environment 22 | uses: subosito/flutter-action@v2 23 | with: 24 | channel: 'stable' 25 | flutter-version: '3.10.5' 26 | 27 | - name: Get Flutter dependencies 28 | run: flutter pub get 29 | 30 | - name: Run code generator 31 | run: flutter packages pub run build_runner build --delete-conflicting-outputs 32 | 33 | - name: Check for any formatting issues in the code 34 | run: dart format --set-exit-if-changed . 35 | 36 | - name: Statically analyze the Dart code for any errors 37 | run: flutter analyze . 38 | 39 | - name: Run widget tests, unit tests 40 | run: flutter test --machine --coverage 41 | 42 | - name: Upload coverage to codecov 43 | uses: codecov/codecov-action@v2 44 | with: 45 | files: ./coverage/lcov.info 46 | flags: unittests # optional 47 | token: ${{#mustacheCase}}secrets.CODECOV_TOKEN{{/mustacheCase}} # not required for public repos 48 | fail_ci_if_error: false 49 | verbose: true 50 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.github/workflows/whatsnew/whatsnew-en-US: -------------------------------------------------------------------------------- 1 | 2 | Enter or paste your release notes for en-US here 3 | 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.gitignore: -------------------------------------------------------------------------------- 1 | # Used by dotenv library to load environment variables. 2 | .env 3 | .env.staging 4 | 5 | # Miscellaneous 6 | *.class 7 | *.log 8 | *.pyc 9 | *.swp 10 | .DS_Store 11 | .atom/ 12 | .buildlog/ 13 | .history 14 | .svn/ 15 | migrate_working_dir/ 16 | 17 | # IntelliJ related 18 | *.iml 19 | *.ipr 20 | *.iws 21 | .idea/ 22 | 23 | # The .vscode folder contains launch configuration and tasks you configure in 24 | # VS Code which you may wish to be included in version control, so this line 25 | # is commented out by default. 26 | .vscode/ 27 | 28 | # Flutter version manager related 29 | .fvm/ 30 | 31 | # Flutter/Dart/Pub related 32 | **/doc/api/ 33 | **/ios/Flutter/.last_build_id 34 | **/ios/.envfile 35 | **/ios/Flutter/tmp.xcconfig 36 | .dart_tool/ 37 | .flutter-plugins 38 | .flutter-plugins-dependencies 39 | .packages 40 | .pub-cache/ 41 | .pub/ 42 | /build/ 43 | 44 | # Web related 45 | lib/generated_plugin_registrant.dart 46 | 47 | # Symbolication related 48 | app.*.symbols 49 | 50 | # Obfuscation related 51 | app.*.map.json 52 | 53 | # Android Studio will place build artifacts here 54 | /android/app/debug 55 | /android/app/profile 56 | /android/app/release 57 | 58 | # Flutter generated files 59 | *.g.dart 60 | *.gen.dart 61 | *.config.dart 62 | *.freezed.dart 63 | *.mocks.dart 64 | -------------------------------------------------------------------------------- /bricks/template/__brick__/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /bricks/template/__brick__/README.md: -------------------------------------------------------------------------------- 1 | # {{app_name.titleCase()}} 2 | 3 | [![codecov](https://codecov.io/gh/nimblehq/{{project_name.paramCase()}}/branch/main/graph/badge.svg?token=ATUNXDX218)](https://codecov.io/gh/nimblehq/{{project_name.paramCase()}}) 4 | 5 | ## Prerequisite 6 | 7 | - Flutter 3.10.5 8 | - Flutter version manager (recommend): [fvm](https://fvm.app/) 9 | 10 | ## Getting Started 11 | 12 | ### Setup 13 | 14 | - Create these `.env` files in the root directory according to the flavors and add the required 15 | environment variables. The example file is `.env.sample`. 16 | 17 | - Staging: `.env.staging` 18 | 19 | - Production: `.env` 20 | 21 | - To make the Android release build, 22 | 23 | - put the `release.keystore` at the `android/config` folder, 24 | 25 | - create the `signing.properties` file to provide keystore credentials in the `android` folder. The example file is `signing.properties.sample`. 26 | 27 | ### Run 28 | 29 | - Run code generator for JSON models, DI dependencies, etc: 30 | 31 | - `$ fvm flutter packages pub run build_runner build --delete-conflicting-outputs` 32 | 33 | - Run the app with the desired app flavor: 34 | 35 | - `$ fvm flutter run --flavor staging` 36 | - `$ fvm flutter run --flavor production` 37 | 38 | - Check code formatting & static code analyzing: 39 | 40 | - `$ dart format --set-exit-if-changed .` 41 | - `$ fvm flutter analyze .` 42 | 43 | ### Test 44 | 45 | - Run unit testing: 46 | 47 | - `$ fvm flutter test` 48 | - `$ fvm flutter test --machine --coverage` 49 | 50 | - Run integration testing: 51 | 52 | - `$ fvm flutter drive --driver=test_driver/integration_test.dart --target=integration_test/{test_file}.dart --flavor staging` 53 | 54 | - For example: 55 | 56 | `$ fvm flutter drive --driver=test_driver/integration_test.dart --target=integration_test/my_home_page_test.dart --flavor staging` 57 | 58 | ## License 59 | 60 | This project is Copyright (c) 2014 and onwards. It is free software, 61 | and may be redistributed under the terms specified in the [LICENSE] file. 62 | 63 | [LICENSE]: /LICENSE 64 | 65 | ## About 66 | 67 | ![Nimble](https://assets.nimblehq.co/logo/dark/logo-dark-text-160.png) 68 | 69 | This project is maintained and funded by Nimble. 70 | 71 | We love open source and do our part in sharing our work with the community! 72 | See [our other projects][community] or [hire our team][hire] to help build your product. 73 | 74 | [community]: https://github.com/nimblehq 75 | [hire]: https://nimblehq.co/ 76 | -------------------------------------------------------------------------------- /bricks/template/__brick__/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | 31 | analyzer: 32 | exclude: [ 33 | lib/**.g.dart, 34 | lib/**.gen.dart, 35 | lib/**.config.dart, 36 | lib/**.freezed.dart, 37 | test/**.mocks.dart 38 | ] 39 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | signing.properties 12 | config/release.keystore 13 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # Keep BuildConfig for Flutter Config https://github.com/ByneappLLC/flutter_config/blob/master/doc/ANDROID.md#android-setup 24 | -keep class {{package_name.dotCase()}}.BuildConfig { *; } 25 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/kotlin/{{package_name.pathCase()}}/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package {{package_name.dotCase()}} 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{app_name.titleCase()}} 4 | 5 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/app/src/staging/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{app_name.titleCase()}} Staging 4 | 5 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/config/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/android/config/debug.keystore -------------------------------------------------------------------------------- /bricks/template/__brick__/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /bricks/template/__brick__/android/signing.properties.sample: -------------------------------------------------------------------------------- 1 | KEYSTORE_PASSWORD= 2 | KEY_ALIAS= 3 | KEY_PASSWORD= 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/assets/fonts/neuzeit.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/assets/fonts/neuzeit.otf -------------------------------------------------------------------------------- /bricks/template/__brick__/assets/images/nimble_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/assets/images/nimble_logo.png -------------------------------------------------------------------------------- /bricks/template/__brick__/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | # Full FieldRename's options, refer https://github.com/google/json_serializable.dart/blob/2185e8b80d8d0c12e2adbf897d920b6f5725cded/json_annotation/lib/src/json_serializable.dart#L16-L32 7 | field_rename: "{{json_field_rename_format}}" 8 | -------------------------------------------------------------------------------- /bricks/template/__brick__/codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | bot: [bot_username] # For the private repository, set up a TeamBot https://docs.codecov.com/docs/team-bot, e.g., bob 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | status: 7 | project: off 8 | patch: off 9 | 10 | parsers: 11 | gcov: 12 | branch_detection: 13 | conditional: yes 14 | loop: yes 15 | method: no 16 | macro: no 17 | 18 | comment: 19 | layout: "reach,diff,flags,files,tree" 20 | behavior: default 21 | require_changes: no 22 | 23 | ignore: 24 | - "lib/di" 25 | - "lib/gen" 26 | -------------------------------------------------------------------------------- /bricks/template/__brick__/integration_test/real_app_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:integration_test/integration_test.dart'; 4 | 5 | import 'utils/test_util.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | testWidgets('Real App', (WidgetTester tester) async { 11 | await tester.pumpWidget(TestUtil.pumpWidgetWithRealApp('/')); 12 | await tester.pumpAndSettle(); 13 | 14 | expect(find.widgetWithText(AppBar, '{{app_name.titleCase()}} testing'), 15 | findsOneWidget); 16 | expect(find.text('This is only for testing'), findsOneWidget); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /bricks/template/__brick__/integration_test/screens/home_screen_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:{{project_name.snakeCase()}}/app/screens/home/home_screen.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:integration_test/integration_test.dart'; 5 | 6 | import '../utils/test_util.dart'; 7 | 8 | void main() { 9 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | testWidgets('My Home Page widget', (WidgetTester tester) async { 12 | await tester 13 | .pumpWidget(TestUtil.pumpWidgetWithShellApp(const HomeScreen())); 14 | await tester.pumpAndSettle(); 15 | 16 | expect(find.widgetWithText(AppBar, '{{app_name.titleCase()}} testing'), 17 | findsOneWidget); 18 | expect(find.text('This is only for testing'), findsOneWidget); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /bricks/template/__brick__/integration_test/utils/test_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_config/flutter_config.dart'; 3 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 4 | import 'package:package_info_plus/package_info_plus.dart'; 5 | import 'package:{{project_name.snakeCase()}}/main.dart'; 6 | 7 | class TestUtil { 8 | /// This is useful when we test the whole app with the real configs(styling, 9 | /// localization, routes, etc) 10 | static Widget pumpWidgetWithRealApp(String initialRoute) { 11 | _initDependencies(); 12 | return MyApp(); 13 | } 14 | 15 | /// We normally use this function to test a specific [widget] without 16 | /// considering much about theming. 17 | static Widget pumpWidgetWithShellApp(Widget widget) { 18 | _initDependencies(); 19 | return MaterialApp( 20 | localizationsDelegates: AppLocalizations.localizationsDelegates, 21 | supportedLocales: AppLocalizations.supportedLocales, 22 | home: widget, 23 | ); 24 | } 25 | 26 | static void _initDependencies() { 27 | PackageInfo.setMockInitialValues( 28 | appName: '{{app_name.titleCase()}} testing', 29 | packageName: '', 30 | version: '', 31 | buildNumber: '', 32 | buildSignature: ''); 33 | FlutterConfig.loadValueForTesting({'SECRET': 'This is only for testing'}); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | Flutter/tmp.xcconfig 28 | Flutter/.last_build_id 29 | ServiceDefinitions.json 30 | Runner/GeneratedPluginRegistrant.* 31 | Build/ 32 | DerivedData/ 33 | fastlane/README.md 34 | fastlane/report.xml 35 | .envfile 36 | 37 | # Exceptions to above rules. 38 | !default.mode1v3 39 | !default.mode2v3 40 | !default.pbxuser 41 | !default.perspectivev3 42 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include? "tmp.xcconfig" 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include? "tmp.xcconfig" 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | gem "cocoapods" 5 | 6 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 7 | eval_gemfile(plugins_path) if File.exist?(plugins_path) -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | target.build_configurations.each do |config| 41 | {{#add_permission_handler}}{{{ _Podfile_build_configurations }}}{{/add_permission_handler}} 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_config (0.0.1): 4 | - Flutter 5 | - integration_test (0.0.1): 6 | - Flutter 7 | - package_info_plus (0.4.5): 8 | - Flutter 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `Flutter`) 12 | - flutter_config (from `.symlinks/plugins/flutter_config/ios`) 13 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 14 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 15 | 16 | EXTERNAL SOURCES: 17 | Flutter: 18 | :path: Flutter 19 | flutter_config: 20 | :path: ".symlinks/plugins/flutter_config/ios" 21 | integration_test: 22 | :path: ".symlinks/plugins/integration_test/ios" 23 | package_info_plus: 24 | :path: ".symlinks/plugins/package_info_plus/ios" 25 | 26 | SPEC CHECKSUMS: 27 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 28 | flutter_config: 2226c1df19c78fe34a05eb7f1363445f18e76fc1 29 | integration_test: 13825b8a9334a850581300559b8839134b124670 30 | package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 31 | 32 | PODFILE CHECKSUM: 22cb4141f6291ab800e0ca928cc1f87e9a69bf64 33 | 34 | COCOAPODS: 1.11.3 35 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | $(APP_DISPLAY_NAME) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | {{project_name.snakeCase()}} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/fastlane/Constants/Constants.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Constants 4 | ################# 5 | #### PROJECT #### 6 | ################# 7 | 8 | # Workspace path 9 | def self.WORKSPACE_PATH 10 | './Runner.xcworkspace' 11 | end 12 | 13 | # Project path 14 | def self.PROJECT_PATH 15 | './Runner.xcodeproj' 16 | end 17 | 18 | # bundle ID for Staging app 19 | def self.BUNDLE_ID_STAGING 20 | '{{package_name.dotCase()}}.staging' 21 | end 22 | 23 | # bundle ID for Production app 24 | def self.BUNDLE_ID_PRODUCTION 25 | '{{package_name.dotCase()}}' 26 | end 27 | 28 | ################# 29 | #### BUILDING ### 30 | ################# 31 | 32 | # a derived data path 33 | def self.DERIVED_DATA_PATH 34 | './DerivedData' 35 | end 36 | 37 | # a build path 38 | def self.BUILD_PATH 39 | './Build' 40 | end 41 | 42 | ################# 43 | #### KEYCHAIN #### 44 | ################# 45 | 46 | # Keychain name 47 | def self.KEYCHAIN_NAME 48 | 'github_action_keychain' 49 | end 50 | 51 | def self.KEYCHAIN_PASSWORD 52 | 'password' 53 | end 54 | 55 | ################# 56 | ### ARCHIVING ### 57 | ################# 58 | # an staging environment scheme name 59 | def self.SCHEME_NAME_STAGING 60 | 'staging' 61 | end 62 | 63 | # a Production environment scheme name 64 | def self.SCHEME_NAME_PRODUCTION 65 | 'production' 66 | end 67 | 68 | # an staging product name 69 | def self.PRODUCT_NAME_STAGING 70 | '{{app_name.titleCase()}} Staging' 71 | end 72 | 73 | # a staging TestFlight product name 74 | def self.PRODUCT_NAME_STAGING_TEST_FLIGHT 75 | '{{app_name.titleCase()}} Staging' 76 | end 77 | 78 | # a Production product name 79 | def self.PRODUCT_NAME_PRODUCTION 80 | '{{app_name.titleCase()}}' 81 | end 82 | 83 | # a main target name 84 | def self.MAIN_TARGET_NAME 85 | 'Runner' 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/fastlane/Constants/Environments.rb: -------------------------------------------------------------------------------- 1 | class Environments 2 | def self.CI 3 | ENV['CI'] 4 | end 5 | 6 | def self.MANUAL_VERSION 7 | ENV['MANUAL_VERSION'] 8 | end 9 | 10 | def self.FASTLANE_USER 11 | ENV['FASTLANE_USER'] 12 | end 13 | 14 | def self.TEAM_ID 15 | ENV['TEAM_ID'] 16 | end 17 | 18 | def self.APP_STORE_KEY_ID 19 | ENV['APP_STORE_KEY_ID'] 20 | end 21 | 22 | def self.APP_STORE_ISSUER_ID 23 | ENV['APP_STORE_ISSUER_ID'] 24 | end 25 | 26 | def self.APP_STORE_CONNECT_API_KEY_BASE64 27 | ENV['APP_STORE_CONNECT_API_KEY_BASE64'] 28 | end 29 | 30 | ################# 31 | ### Firebase ### 32 | ################# 33 | 34 | def self.FIREBASE_CLI_TOKEN 35 | ENV['FIREBASE_CLI_TOKEN'] 36 | end 37 | 38 | def self.FIREBASE_APP_ID 39 | ENV['FIREBASE_APP_ID'] 40 | end 41 | 42 | def self.FIREBASE_TESTER_GROUPS 43 | ENV['FIREBASE_DISTRIBUTION_TESTER_GROUPS'] 44 | end 45 | 46 | def self.GITHUB_RUN_NUMBER 47 | ENV['GITHUB_RUN_NUMBER'] 48 | end 49 | 50 | def self.RELEASE_NOTE_CONTENT 51 | ENV['RELEASE_NOTE_CONTENT'] 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/fastlane/Gymfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | clean(true) 4 | export_team_id(Environments.TEAM_ID) 5 | output_directory(Constants.BUILD_PATH) # .ipa 6 | build_path(Constants.BUILD_PATH) # .xcarchive is stored 7 | derived_data_path(Constants.DERIVED_DATA_PATH) # .app 8 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/fastlane/Managers/BuildManager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BuildManager 4 | def initialize(fastlane:) 5 | @fastlane = fastlane 6 | end 7 | 8 | def build_app_store(scheme, product_name, bundle_identifier, include_bitcode) 9 | @fastlane.gym( 10 | scheme: scheme, 11 | export_method: 'app-store', 12 | export_options: { 13 | provisioningProfiles: { 14 | @bundle_identifier_staging.to_s => "match AppStore #{bundle_identifier}" 15 | } 16 | }, 17 | include_bitcode: include_bitcode, 18 | output_name: product_name 19 | ) 20 | end 21 | 22 | def build_ad_hoc(scheme, product_name, bundle_identifier) 23 | @fastlane.gym( 24 | scheme: scheme, 25 | export_method: 'ad-hoc', 26 | export_options: { 27 | provisioningProfiles: { 28 | @bundle_identifier_staging.to_s => "match Adhoc #{bundle_identifier}" 29 | } 30 | }, 31 | include_bitcode: false, 32 | output_name: product_name, 33 | disable_xcpretty: true 34 | ) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/fastlane/Managers/DistributionManager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DistributionManager 4 | def initialize(fastlane:, build_path:, firebase_token:) 5 | @fastlane = fastlane 6 | @build_path = build_path 7 | @firebase_token = firebase_token 8 | end 9 | 10 | def upload_to_testflight(product_name:, bundle_identifier:) 11 | @fastlane.pilot( 12 | ipa: "#{@build_path}/#{product_name}.ipa", 13 | app_identifier: bundle_identifier, 14 | notify_external_testers: false, 15 | skip_waiting_for_build_processing: true 16 | ) 17 | end 18 | 19 | def upload_to_app_store_connect(product_name:, bundle_identifier:) 20 | @fastlane.deliver( 21 | ipa: "#{@build_path}/#{product_name}.ipa", 22 | app_identifier: bundle_identifier, 23 | force: true, 24 | skip_metadata: true, 25 | skip_screenshots: true, 26 | run_precheck_before_submit: false 27 | ) 28 | end 29 | 30 | def upload_to_firebase(product_name:, firebase_app_id:, notes:, tester_groups:) 31 | ipa_path = "#{@build_path}/#{product_name}.ipa" 32 | @fastlane.firebase_app_distribution( 33 | app: firebase_app_id, 34 | ipa_path: ipa_path, 35 | groups: tester_groups, 36 | firebase_cli_token: @firebase_token, 37 | release_notes: notes 38 | ) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/fastlane/Managers/MatchManager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MatchManager 4 | def initialize( 5 | fastlane:, 6 | keychain_name:, 7 | keychain_password:, 8 | is_ci: 9 | ) 10 | @fastlane = fastlane 11 | @keychain_name = keychain_name 12 | @keychain_password = keychain_password 13 | @is_ci = is_ci 14 | end 15 | 16 | def sync_adhoc_signing(app_identifier:) 17 | if @is_ci 18 | create_ci_keychain 19 | @fastlane.match( 20 | type: 'adhoc', 21 | keychain_name: @keychain_name, 22 | keychain_password: @keychain_password, 23 | app_identifier: app_identifier, 24 | readonly: true 25 | ) 26 | else 27 | @fastlane.match(type: 'adhoc', app_identifier: app_identifier, readonly: true) 28 | end 29 | end 30 | 31 | def sync_app_store_signing(app_identifier:) 32 | if @is_ci 33 | create_ci_keychain 34 | @fastlane.match( 35 | type: 'appstore', 36 | keychain_name: @keychain_name, 37 | keychain_password: @keychain_password, 38 | app_identifier: app_identifier, 39 | readonly: true 40 | ) 41 | else 42 | @fastlane.match(type: 'appstore', app_identifier: app_identifier, readonly: true) 43 | end 44 | end 45 | 46 | def create_ci_keychain 47 | @fastlane.create_keychain( 48 | name: @keychain_name, 49 | password: @keychain_password, 50 | default_keychain: true, 51 | unlock: true, 52 | timeout: 3600, 53 | lock_when_sleeps: false 54 | ) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/fastlane/Matchfile: -------------------------------------------------------------------------------- 1 | git_url("git@github.com:nimblehq/match-certificates.git") 2 | 3 | storage_mode("git") 4 | 5 | # type("appstore") # The default type, can be: appstore, adhoc, enterprise or development 6 | 7 | # app_identifier(["tools.fastlane.app", "tools.fastlane.app2"]) 8 | # username("user@fastlane.tools") # Your Apple Developer Portal username 9 | 10 | # For all available options run `fastlane match --help` 11 | # Remove the # in the beginning of the line to enable the other options 12 | 13 | # The docs are available on https://docs.fastlane.tools/actions/match 14 | readonly(true) 15 | force(false) 16 | -------------------------------------------------------------------------------- /bricks/template/__brick__/ios/fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-firebase_app_distribution' 6 | -------------------------------------------------------------------------------- /bricks/template/__brick__/l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/app/resources/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColors { 4 | static const Color nimblePrimaryBlue = Color(0xFF201547); 5 | } 6 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/app/screens/home/home_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:{{project_name.snakeCase()}}/app/screens/home/home_view_state.dart'; 3 | import 'package:{{project_name.snakeCase()}}/domain/usecases/base/base_use_case.dart'; 4 | import 'package:{{project_name.snakeCase()}}/domain/usecases/get_users_use_case.dart'; 5 | import 'package:{{project_name.snakeCase()}}/domain/models/user.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | 8 | class HomeViewModel extends StateNotifier { 9 | final GetUsersUseCase _getUsersUseCase; 10 | 11 | HomeViewModel( 12 | this._getUsersUseCase, 13 | ) : super(const HomeViewState.init()); 14 | 15 | final StreamController> _usersStream = StreamController(); 16 | 17 | Stream> get usersStream => _usersStream.stream; 18 | 19 | Future getUsers() async { 20 | final result = await _getUsersUseCase.call(); 21 | if (result is Success>) { 22 | _usersStream.add(result.value); 23 | } else { 24 | // TODO handle error 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/app/screens/home/home_view_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'home_view_state.freezed.dart'; 4 | 5 | @freezed 6 | class HomeViewState with _$HomeViewState { 7 | const factory HomeViewState.init() = _Init; 8 | } 9 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/data/local/secure_storage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | 4 | const _keyAccessToken = 'KEY_ACCESS_TOKEN'; 5 | 6 | abstract class SecureStorage { 7 | Future get accessToken; 8 | 9 | Future storeAccessToken(String accessToken); 10 | 11 | Future clearAll(); 12 | } 13 | 14 | @Singleton(as: SecureStorage) 15 | class SecureStorageImpl extends SecureStorage { 16 | final FlutterSecureStorage _storage; 17 | 18 | SecureStorageImpl(this._storage); 19 | 20 | @override 21 | Future get accessToken => _storage.read(key: _keyAccessToken); 22 | 23 | @override 24 | Future storeAccessToken(String accessToken) { 25 | return _storage.write(key: _keyAccessToken, value: accessToken); 26 | } 27 | 28 | @override 29 | Future clearAll() { 30 | return _storage.deleteAll(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/data/remote/datasources/api_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:{{project_name.snakeCase()}}/data/remote/models/responses/user_response.dart'; 3 | import 'package:retrofit/retrofit.dart'; 4 | 5 | part 'api_service.g.dart'; 6 | 7 | abstract class BaseApiService { 8 | Future> getUsers(); 9 | } 10 | 11 | @RestApi() 12 | abstract class ApiService extends BaseApiService { 13 | factory ApiService(Dio dio, {String baseUrl}) = _ApiService; 14 | 15 | // TODO add API endpoint 16 | @override 17 | @GET('users') 18 | Future> getUsers(); 19 | } 20 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/data/remote/models/requests/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/bricks/template/__brick__/lib/data/remote/models/requests/.keep -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/data/remote/models/responses/user_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:{{project_name.snakeCase()}}/domain/models/user.dart'; 3 | 4 | part 'user_response.g.dart'; 5 | 6 | @JsonSerializable() 7 | class UserResponse { 8 | final String email; 9 | final String username; 10 | 11 | UserResponse(this.email, this.username); 12 | 13 | factory UserResponse.fromJson(Map json) => 14 | _$UserResponseFromJson(json); 15 | 16 | Map toJson() => _$UserResponseToJson(this); 17 | 18 | User toUser() => User( 19 | email: email, 20 | username: username, 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/data/repositories/credential_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:{{project_name.snakeCase()}}/data/remote/datasources/api_service.dart'; 2 | import 'package:{{project_name.snakeCase()}}/domain/exceptions/network_exceptions.dart'; 3 | import 'package:{{project_name.snakeCase()}}/domain/models/user.dart'; 4 | import 'package:{{project_name.snakeCase()}}/domain/repositories/credential_repository.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | @LazySingleton(as: CredentialRepository) 8 | class CredentialRepositoryImpl extends CredentialRepository { 9 | final BaseApiService _apiService; 10 | 11 | CredentialRepositoryImpl(this._apiService); 12 | 13 | @override 14 | Future> getUsers() async { 15 | try { 16 | final userResponses = await _apiService.getUsers(); 17 | return userResponses 18 | .map((userResponse) => userResponse.toUser()) 19 | .toList(); 20 | } catch (exception) { 21 | throw NetworkExceptions.fromDioException(exception); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/di/di.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | 4 | import 'di.config.dart'; 5 | 6 | final GetIt getIt = GetIt.instance; 7 | 8 | @injectableInit 9 | Future configureInjection() async => getIt.init(); 10 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/di/interceptor/app_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | class AppInterceptor extends Interceptor { 6 | final bool _requireAuthenticate; 7 | final Dio _dio; 8 | 9 | AppInterceptor( 10 | this._requireAuthenticate, 11 | this._dio, 12 | ); 13 | 14 | @override 15 | Future onRequest( 16 | RequestOptions options, RequestInterceptorHandler handler) async { 17 | if (_requireAuthenticate) { 18 | // TODO header authorization here 19 | // options.headers 20 | // .putIfAbsent(HEADER_AUTHORIZATION, () => ""); 21 | } 22 | return super.onRequest(options, handler); 23 | } 24 | 25 | @override 26 | void onError(DioError err, ErrorInterceptorHandler handler) { 27 | final statusCode = err.response?.statusCode; 28 | if ((statusCode == HttpStatus.forbidden || 29 | statusCode == HttpStatus.unauthorized) && 30 | _requireAuthenticate) { 31 | _doRefreshToken(err, handler); 32 | } else { 33 | handler.next(err); 34 | } 35 | } 36 | 37 | Future _doRefreshToken( 38 | DioError err, 39 | ErrorInterceptorHandler handler, 40 | ) async { 41 | try { 42 | // TODO Request new token 43 | 44 | // if (result is Success) { 45 | // TODO Update new token header 46 | // err.requestOptions.headers[_headerAuthorization] = newToken; 47 | 48 | // Create request with new access token 49 | final options = Options( 50 | method: err.requestOptions.method, 51 | headers: err.requestOptions.headers); 52 | final newRequest = await _dio.request( 53 | "${err.requestOptions.baseUrl}${err.requestOptions.path}", 54 | options: options, 55 | data: err.requestOptions.data, 56 | queryParameters: err.requestOptions.queryParameters); 57 | handler.resolve(newRequest); 58 | // } else { 59 | // handler.next(err); 60 | // } 61 | } catch (exception) { 62 | if (exception is DioError) { 63 | handler.next(exception); 64 | } else { 65 | handler.next(err); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/di/module/network_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:{{project_name.snakeCase()}}/data/remote/datasources/api_service.dart'; 2 | import 'package:{{project_name.snakeCase()}}/env.dart'; 3 | import 'package:{{project_name.snakeCase()}}/di/provider/dio_provider.dart'; 4 | import 'package:injectable/injectable.dart'; 5 | 6 | @module 7 | abstract class NetworkModule { 8 | @Singleton(as: BaseApiService) 9 | ApiService provideApiService(DioProvider dioProvider) { 10 | return ApiService( 11 | dioProvider.getDio(), 12 | baseUrl: Env.restApiEndpoint, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/di/module/storage_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | 4 | @module 5 | abstract class StorageModule { 6 | @singleton 7 | FlutterSecureStorage get flutterSecureStorage => const FlutterSecureStorage( 8 | aOptions: AndroidOptions(encryptedSharedPreferences: true)); 9 | } 10 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/di/provider/dio_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:{{project_name.snakeCase()}}/di/interceptor/app_interceptor.dart'; 4 | import 'package:injectable/injectable.dart'; 5 | 6 | const String headerContentType = 'Content-Type'; 7 | const String defaultContentType = 'application/json; charset=utf-8'; 8 | 9 | @Singleton() 10 | class DioProvider { 11 | Dio? _dio; 12 | 13 | Dio getDio() { 14 | _dio ??= _createDio(); 15 | return _dio!; 16 | } 17 | 18 | Dio _createDio({bool requireAuthenticate = false}) { 19 | final dio = Dio(); 20 | final appInterceptor = AppInterceptor( 21 | requireAuthenticate, 22 | dio, 23 | ); 24 | final interceptors = []; 25 | interceptors.add(appInterceptor); 26 | if (!kReleaseMode) { 27 | interceptors.add(LogInterceptor( 28 | request: true, 29 | responseBody: true, 30 | requestBody: true, 31 | requestHeader: true, 32 | )); 33 | } 34 | 35 | return dio 36 | ..options.connectTimeout = const Duration(seconds: 3000) 37 | ..options.receiveTimeout = const Duration(seconds: 5000) 38 | ..options.headers = {headerContentType: defaultContentType} 39 | ..interceptors.addAll(interceptors); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/domain/models/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class User extends Equatable { 4 | final String email; 5 | final String username; 6 | 7 | const User({ 8 | required this.email, 9 | required this.username, 10 | }); 11 | 12 | @override 13 | bool? get stringify => true; 14 | 15 | @override 16 | List get props => [ 17 | email, 18 | username, 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/domain/repositories/credential_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:{{project_name.snakeCase()}}/domain/models/user.dart'; 2 | 3 | abstract class CredentialRepository { 4 | Future> getUsers(); 5 | } 6 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/domain/usecases/base/base_use_case.dart: -------------------------------------------------------------------------------- 1 | part 'use_case_result.dart'; 2 | 3 | abstract class BaseUseCase { 4 | const BaseUseCase(); 5 | } 6 | 7 | abstract class UseCase extends BaseUseCase> { 8 | const UseCase() : super(); 9 | 10 | Future> call(P params); 11 | } 12 | 13 | abstract class NoParamsUseCase extends BaseUseCase> { 14 | const NoParamsUseCase() : super(); 15 | 16 | Future> call(); 17 | } 18 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/domain/usecases/base/use_case_result.dart: -------------------------------------------------------------------------------- 1 | part of 'base_use_case.dart'; 2 | 3 | abstract class Result { 4 | Result._(); 5 | } 6 | 7 | class Success extends Result { 8 | final T value; 9 | 10 | Success(this.value) : super._(); 11 | } 12 | 13 | class UseCaseException implements Exception { 14 | final dynamic actualException; 15 | 16 | UseCaseException(this.actualException); 17 | } 18 | 19 | class Failed extends Result { 20 | final UseCaseException exception; 21 | 22 | Failed(this.exception) : super._(); 23 | } 24 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/domain/usecases/get_users_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:{{project_name.snakeCase()}}/domain/exceptions/network_exceptions.dart'; 2 | import 'package:{{project_name.snakeCase()}}/domain/repositories/credential_repository.dart'; 3 | import 'package:{{project_name.snakeCase()}}/domain/usecases/base/base_use_case.dart'; 4 | import 'package:{{project_name.snakeCase()}}/domain/models/user.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | @Injectable() 8 | class GetUsersUseCase extends NoParamsUseCase> { 9 | final CredentialRepository _credentialRepository; 10 | 11 | GetUsersUseCase(this._credentialRepository); 12 | 13 | @override 14 | Future>> call() async { 15 | return _credentialRepository 16 | .getUsers() 17 | .then((value) => 18 | Success(value) as Result>) // ignore: unnecessary_cast 19 | .onError( 20 | (err, stackTrace) => Failed(UseCaseException(err))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/env.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_config/flutter_config.dart'; 2 | 3 | class Env { 4 | static String get restApiEndpoint { 5 | return FlutterConfig.get('REST_API_ENDPOINT'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/l10n/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Hello!" 3 | } 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/l10n/app_th.arb: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "สวัสดี" 3 | } 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/l10n/app_vi.arb: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Xin chào!" 3 | } 4 | -------------------------------------------------------------------------------- /bricks/template/__brick__/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:go_router/go_router.dart'; 5 | import 'package:flutter_config/flutter_config.dart'; 6 | import 'package:{{project_name.snakeCase()}}/di/di.dart'; 7 | import 'package:{{project_name.snakeCase()}}/gen/assets.gen.dart'; 8 | import 'package:{{project_name.snakeCase()}}/app/screens/home/home_screen.dart'; 9 | 10 | void main() async { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | await FlutterConfig.loadEnvVariables(); 13 | await configureInjection(); 14 | runApp( 15 | ProviderScope( 16 | child: MyApp(), 17 | ), 18 | ); 19 | } 20 | 21 | const routePathRootScreen = '/'; 22 | const routePathSecondScreen = 'second'; 23 | 24 | class MyApp extends StatelessWidget { 25 | MyApp({Key? key}) : super(key: key); 26 | 27 | final GoRouter _router = GoRouter( 28 | routes: [ 29 | GoRoute( 30 | path: routePathRootScreen, 31 | builder: (BuildContext context, GoRouterState state) => 32 | const HomeScreen(), 33 | routes: [ 34 | GoRoute( 35 | path: routePathSecondScreen, 36 | builder: (BuildContext context, GoRouterState state) => 37 | const SecondScreen(), 38 | ), 39 | ], 40 | ), 41 | ], 42 | ); 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return MaterialApp.router( 47 | theme: ThemeData( 48 | primarySwatch: Colors.blue, 49 | brightness: Brightness.light, 50 | fontFamily: Assets.fonts.neuzeit, 51 | ), 52 | localizationsDelegates: AppLocalizations.localizationsDelegates, 53 | supportedLocales: AppLocalizations.supportedLocales, 54 | routeInformationProvider: _router.routeInformationProvider, 55 | routeInformationParser: _router.routeInformationParser, 56 | routerDelegate: _router.routerDelegate, 57 | ); 58 | } 59 | } 60 | 61 | class SecondScreen extends StatelessWidget { 62 | const SecondScreen({ 63 | Key? key, 64 | }) : super(key: key); 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | return Scaffold( 69 | appBar: AppBar( 70 | title: const Text("Second Screen"), 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /bricks/template/__brick__/test/app/screens/home/home_view_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:{{project_name.snakeCase()}}/app/screens/home/home_view_model.dart'; 5 | import 'package:{{project_name.snakeCase()}}/domain/usecases/base/base_use_case.dart'; 6 | import 'package:{{project_name.snakeCase()}}/app/screens/home/home_screen.dart'; 7 | 8 | import '../../../mocks/generate_mocks.mocks.dart'; 9 | import '../../../mocks/data/remote/models/responses/user_response_mocks.dart'; 10 | 11 | void main() { 12 | group("HomeViewModelTest", () { 13 | late ProviderContainer container; 14 | late MockGetUsersUseCase mockGetUsersUseCase; 15 | 16 | setUp(() { 17 | TestWidgetsFlutterBinding.ensureInitialized(); 18 | mockGetUsersUseCase = MockGetUsersUseCase(); 19 | 20 | container = ProviderContainer( 21 | overrides: [ 22 | homeViewModelProvider.overrideWith((ref) => HomeViewModel( 23 | mockGetUsersUseCase, 24 | )), 25 | ], 26 | ); 27 | addTearDown(container.dispose); 28 | }); 29 | 30 | test('When calling get user list successfully, it returns correctly', 31 | () async { 32 | final expectedResult = [UserResponseMocks.mock().toUser()]; 33 | when(mockGetUsersUseCase.call()) 34 | .thenAnswer((_) async => Success(expectedResult)); 35 | 36 | final usersStream = 37 | container.read(homeViewModelProvider.notifier).usersStream; 38 | expect(usersStream, emitsInOrder([expectedResult])); 39 | 40 | container.read(homeViewModelProvider.notifier).getUsers(); 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /bricks/template/__brick__/test/data/repositories/credential_repository_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:{{project_name.snakeCase()}}/domain/exceptions/network_exceptions.dart'; 2 | import 'package:{{project_name.snakeCase()}}/domain/repositories/credential_repository.dart'; 3 | import 'package:{{project_name.snakeCase()}}/data/repositories/credential_repository_impl.dart'; 4 | import 'package:{{project_name.snakeCase()}}/data/remote/models/responses/user_response.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:mockito/mockito.dart'; 7 | 8 | import '../../mocks/generate_mocks.mocks.dart'; 9 | 10 | void main() { 11 | group('CredentialRepository', () { 12 | MockApiService mockApiService = MockApiService(); 13 | late CredentialRepository repository; 14 | 15 | setUp(() { 16 | repository = CredentialRepositoryImpl(mockApiService); 17 | }); 18 | test( 19 | "When getting user list successfully, it emits corresponding user list", 20 | () async { 21 | when(mockApiService.getUsers()).thenAnswer((_) async => [ 22 | UserResponse('test@email.com', 'test_user'), 23 | ]); 24 | 25 | final result = await repository.getUsers(); 26 | expect(result.length, 1); 27 | expect(result[0].email, 'test@email.com'); 28 | expect(result[0].username, 'test_user'); 29 | }); 30 | 31 | test("When getting user list failed, it returns NetworkExceptions error", 32 | () async { 33 | when(mockApiService.getUsers()).thenThrow(MockDioError()); 34 | 35 | expect( 36 | () => repository.getUsers(), 37 | throwsA(isA()), 38 | ); 39 | }); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /bricks/template/__brick__/test/domain/usecases/get_users_use_case_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | import 'package:{{project_name.snakeCase()}}/domain/usecases/base/base_use_case.dart'; 4 | import 'package:{{project_name.snakeCase()}}/domain/usecases/get_users_use_case.dart'; 5 | 6 | import '../../mocks/generate_mocks.mocks.dart'; 7 | import '../../mocks/data/remote/models/responses/user_response_mocks.dart'; 8 | 9 | void main() { 10 | group('GetUsersUseCase', () { 11 | late MockCredentialRepository mockRepository; 12 | late GetUsersUseCase getUsersUseCase; 13 | 14 | setUp(() { 15 | mockRepository = MockCredentialRepository(); 16 | getUsersUseCase = GetUsersUseCase(mockRepository); 17 | }); 18 | 19 | test('When getting users successfully, it returns Success result', 20 | () async { 21 | final expectedResult = [UserResponseMocks.mock().toUser()]; 22 | when(mockRepository.getUsers()).thenAnswer((_) async => expectedResult); 23 | final result = await getUsersUseCase.call(); 24 | 25 | expect(result, isA()); 26 | expect((result as Success).value, expectedResult); 27 | }); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /bricks/template/__brick__/test/mocks/data/remote/models/responses/user_response_mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:{{project_name.snakeCase()}}/data/remote/models/responses/user_response.dart'; 2 | 3 | class UserResponseMocks { 4 | static UserResponse mock() { 5 | return UserResponse( 6 | "email", 7 | "username", 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /bricks/template/__brick__/test/mocks/generate_mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:{{project_name.snakeCase()}}/data/remote/datasources/api_service.dart'; 3 | import 'package:{{project_name.snakeCase()}}/domain/repositories/credential_repository.dart'; 4 | import 'package:{{project_name.snakeCase()}}/domain/usecases/get_users_use_case.dart'; 5 | import 'package:mockito/annotations.dart'; 6 | 7 | @GenerateMocks([ 8 | ApiService, 9 | CredentialRepository, 10 | DioError, 11 | GetUsersUseCase, 12 | ]) 13 | main() { 14 | // empty class to generate mock repository classes 15 | } 16 | -------------------------------------------------------------------------------- /bricks/template/__brick__/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /bricks/template/brick.yaml: -------------------------------------------------------------------------------- 1 | name: template 2 | description: A new brick created with the Mason CLI. 3 | 4 | # The following defines the version and build number for your brick. 5 | # A version number is three numbers separated by dots, like 1.2.34 6 | # followed by an optional build number (separated by a +). 7 | version: 0.1.0+1 8 | 9 | # The following defines the environment for the current brick. 10 | # It includes the version of mason that the brick requires. 11 | environment: 12 | mason: ">=0.1.0-dev.41 <0.1.0" 13 | 14 | # Variables specify dynamic values that your brick depends on. 15 | # Zero or more variables can be specified for a given brick. 16 | # Each variable has: 17 | # * a type (string, number, boolean, enum, or array) 18 | # * an optional short description 19 | # * an optional default value 20 | # * an optional list of default values (array only) 21 | # * an optional prompt phrase used when asking for the variable 22 | # * a list of values (enums only) 23 | vars: 24 | project_name: 25 | type: string 26 | description: Your project name 27 | default: your_project_name 28 | prompt: Project name? 29 | package_name: 30 | type: string 31 | description: Your package name 32 | default: co.nimblehq.template 33 | prompt: Package name? 34 | app_name: 35 | type: string 36 | description: Your app name 37 | default: Your App Name 38 | prompt: App name? 39 | app_version: 40 | type: string 41 | description: Your app version 42 | default: "0.1.0" 43 | prompt: App version? 44 | build_number: 45 | type: string 46 | description: Your app version 47 | default: "1" 48 | prompt: Build number? 49 | json_field_rename_format: 50 | type: enum 51 | description: Default Json field renaming format with json_serializable (https://pub.dev/packages/json_serializable#build-configuration) 52 | default: snake 53 | prompt: Default Json field renaming format? 54 | values: 55 | - none 56 | - kebab 57 | - snake 58 | - pascal 59 | add_permission_handler: 60 | type: boolean 61 | description: Add permission_handler (https://pub.dev/packages/permission_handler) package to the project with some basic setup 62 | default: false 63 | prompt: Add permission_handler package? 64 | -------------------------------------------------------------------------------- /bricks/template/hooks/.gitignore: -------------------------------------------------------------------------------- 1 | .dart_tool 2 | build 3 | -------------------------------------------------------------------------------- /bricks/template/hooks/hooks_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | Future deleteFileSystemEntities(List entities) async { 4 | entities.forEach((entity) { 5 | if (entity.existsSync()) { 6 | print('Delete ' + entity.path); 7 | if (entity is File) { 8 | entity.deleteSync(); 9 | } else if (entity is Directory) { 10 | entity.deleteSync(recursive: true); 11 | } 12 | } 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /bricks/template/hooks/post_gen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:mason/mason.dart'; 4 | 5 | import 'bundles/permission_handler_bundle.dart'; 6 | import 'hooks_util.dart'; 7 | 8 | Future run(HookContext context) async { 9 | try { 10 | await generateBricks(context); 11 | } catch (e) { 12 | context.logger.err(e.toString()); 13 | } 14 | 15 | // Clean up the output folder, even at the root, to have only the final generated project in the end 16 | print("> Clean up the output folder (post): " + Directory.current.path); 17 | final List entities = [ 18 | Directory(Directory.current.path + '/bricks'), 19 | ]; 20 | await deleteFileSystemEntities(entities); 21 | } 22 | 23 | Future generateBricks(HookContext context) async { 24 | if (context.vars['add_permission_handler'] == true) { 25 | await generatePermissionHandlerBrick(context.vars['project_name']); 26 | } 27 | } 28 | 29 | Future generatePermissionHandlerBrick(String projectName) async { 30 | final generator = await MasonGenerator.fromBundle(permissionHandlerBundle); 31 | await generator.generate(DirectoryGeneratorTarget(Directory.current), 32 | vars: {'project_name': projectName}); 33 | } 34 | -------------------------------------------------------------------------------- /bricks/template/hooks/pre_gen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:mason/mason.dart'; 4 | 5 | import 'bundles/permission_handler_bundle.dart'; 6 | import 'hooks_util.dart'; 7 | 8 | Future run(HookContext context) async { 9 | // Clean up the output folder, even at the root, to have only the final generated project in the end 10 | print("> Clean up the output folder (pre): " + Directory.current.path); 11 | final exclusions = [ 12 | // we should not delete .git 13 | Directory.current.path + '/.git', 14 | // the bricks folder can be deleted at post_gen only 15 | Directory.current.path + '/bricks', 16 | ]; 17 | final entities = Directory.current 18 | .listSync(recursive: false) 19 | .where((entity) => !exclusions.contains(entity.path)) 20 | .toList(); 21 | await deleteFileSystemEntities(entities); 22 | 23 | try { 24 | final additionalVars = {}; 25 | if (context.vars['add_permission_handler'] == true) { 26 | additionalVars.addAll(await addPermissionHandlerVariables()); 27 | } 28 | context.vars = {...context.vars, ...additionalVars}; 29 | } catch (e) { 30 | context.logger.err(e.toString()); 31 | } 32 | } 33 | 34 | Future> addPermissionHandlerVariables() async { 35 | Map vars = {}; 36 | final generator = await MasonGenerator.fromBundle(permissionHandlerBundle); 37 | generator.partials.forEach((filePath, content) { 38 | if (filePath.startsWith('{{~ _')) { 39 | // Remove the special characters from the partial files 40 | final formattedName = filePath 41 | .replaceAll('{', '') 42 | .replaceAll('}', '') 43 | .replaceAll('~ ', '') 44 | .replaceAll('.', '') 45 | .trim(); 46 | vars.putIfAbsent(formattedName, () => String.fromCharCodes(content)); 47 | } 48 | }); 49 | return vars; 50 | } 51 | -------------------------------------------------------------------------------- /bricks/template/hooks/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: template_hooks 2 | 3 | environment: 4 | sdk: ">=2.18.4 <3.0.0" 5 | 6 | dependencies: 7 | mason: ">=0.1.0-dev.41 <0.1.0" 8 | -------------------------------------------------------------------------------- /mason-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_name": "flutter_templates", 3 | "package_name": "co.nimblehq.flutter.template", 4 | "app_name": "Flutter Templates", 5 | "app_version": "1.14.0", 6 | "build_number": "1", 7 | "json_field_rename_format": "snake", 8 | "add_permission_handler": true 9 | } -------------------------------------------------------------------------------- /mason.yaml: -------------------------------------------------------------------------------- 1 | # Register bricks which can be consumed via the Mason CLI. 2 | # https://github.com/felangel/mason 3 | bricks: 4 | template: 5 | path: ./bricks/template/ 6 | 7 | # Other features 8 | permission_handler: 9 | path: ./bricks/permission_handler/ 10 | -------------------------------------------------------------------------------- /sample/.env.sample: -------------------------------------------------------------------------------- 1 | SECRET= 2 | REST_API_ENDPOINT= 3 | -------------------------------------------------------------------------------- /sample/.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Team 2 | # @teamlead is the Team Lead 3 | * @teammember1 @teammember2 4 | 5 | # Engineering Leads 6 | CODEOWNERS @nimblehq/engineering-leads 7 | -------------------------------------------------------------------------------- /sample/.github/ISSUE_TEMPLATE/bug_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug Report" 3 | about: "You found something that is not working. Report it so that it can be fixed. 👷‍" 4 | title: "Fix: " 5 | labels: "type : bug" 6 | --- 7 | 8 | ## Issue 9 | 10 | Describe the issue you are facing. Show us the implementation: screenshots, GIFs, etc. 11 | 12 | ## Expected 13 | 14 | Describe what should be the correct behavior. 15 | 16 | ## Steps to reproduce 17 | 18 | 1. 19 | 2. 20 | 3. 21 | -------------------------------------------------------------------------------- /sample/.github/ISSUE_TEMPLATE/chore_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Chore" 3 | about: "Open a chore issue for a minor update." 4 | title: "Update " 5 | labels: "type : chore" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the update in detail and why it is needed. 11 | 12 | ## Who Benefits? 13 | 14 | Describe who will be the beneficiaries e.g. everyone, specific chapters, clients... 15 | -------------------------------------------------------------------------------- /sample/.github/ISSUE_TEMPLATE/feature_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature" 3 | about: "Open a feature issue to add new functionalities." 4 | title: "Add " 5 | labels: "type : feature" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the big picture of the feature and why it is needed. 11 | 12 | ## Who Benefits? 13 | 14 | Describe who will be the beneficiaries e.g. everyone, specific chapters, clients... 15 | -------------------------------------------------------------------------------- /sample/.github/ISSUE_TEMPLATE/story_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Story" 3 | about: "Open a feature story" 4 | title: "[Type] As a user, I can " 5 | labels: "type : feature" 6 | --- 7 | 8 | ## Why 9 | 10 | Describe the idea of the user story as in what the motive of the user story is. 11 | 12 | ## Acceptance Criteria 13 | 14 | List down how the user story will be tested and what criteria are necessary for the user story to be accepted. 15 | 16 | ## Design 17 | 18 | (Optional) Add design screenshots or Figma links for UI/UX-related stories. 19 | 20 | ## Resources 21 | 22 | (Optional) Add useful resources such as links to documentation, implementation ideas, or best practices. 23 | -------------------------------------------------------------------------------- /sample/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - Close # 2 | 3 | ## What happened 👀 4 | 5 | Provide a description of the **changes** this pull request brings to the codebase. Additionally, when the pull request is still being worked on, a checklist of the planned changes is welcome to track progress. 6 | 7 | ## Insight 📝 8 | 9 | Describe in detail why this solution is the most appropriate, which solution you tried but did not go with, and how to test the changes. References to relevant documentation are welcome as well. 10 | 11 | ## Proof Of Work 📹 12 | 13 | Show us the implementation: screenshots, GIFs, etc. 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/.github/PULL_REQUEST_TEMPLATE/release_template.md: -------------------------------------------------------------------------------- 1 | Link to the milestone on Github e.g. https://github.com/nimblehq/sample/milestone/41?closed=1 2 | or Link to the project management tool for the release 3 | 4 | ## Features 5 | 6 | Provide the ID and title of the issue in the section for each type (feature, chore and bug). The link is optional. 7 | 8 | - [#1] As a user, I can log in or 9 | - [[#1](https://github.com/nimblehq/sample/issues/1)] As a user, I can log in 10 | 11 | ## Chores 12 | - Same structure as in ## Feature 13 | 14 | ## Bugs 15 | - Same structure as in ## Feature 16 | -------------------------------------------------------------------------------- /sample/.github/wiki/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/.github/wiki/.keep -------------------------------------------------------------------------------- /sample/.github/workflows/android_deploy_production.yml: -------------------------------------------------------------------------------- 1 | name: Android - Deploy Production build to Firebase 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build_and_deploy_android: 10 | name: Android - Deploy Production build to Firebase 11 | runs-on: ubuntu-latest 12 | environment: production 13 | timeout-minutes: 30 14 | steps: 15 | - name: Check out 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Java JDK 19 | uses: actions/setup-java@v3 20 | with: 21 | distribution: 'adopt' 22 | java-version: '11' 23 | 24 | - name: Set up Flutter environment 25 | uses: subosito/flutter-action@v2 26 | with: 27 | channel: 'stable' 28 | flutter-version: '3.10.5' 29 | 30 | - name: Get Flutter dependencies 31 | run: flutter pub get 32 | 33 | - name: Run code generator 34 | run: flutter packages pub run build_runner build --delete-conflicting-outputs 35 | 36 | - name: Set up .env 37 | env: 38 | ENV: ${{ secrets.ENV }} 39 | run: | 40 | echo -e "$ENV" > .env 41 | 42 | - name: Set up release signing configs 43 | env: 44 | ANDROID_RELEASE_KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }} 45 | ANDROID_SIGNING_PROPERTIES: ${{ secrets.ANDROID_SIGNING_PROPERTIES }} 46 | run: | 47 | echo $ANDROID_RELEASE_KEYSTORE_BASE64 | base64 --decode > android/config/release.keystore 48 | echo "$ANDROID_SIGNING_PROPERTIES" > android/signing.properties 49 | 50 | # App Bundle requires Firebase connected to Play Store to upload https://appdistribution.page.link/KPoa 51 | - name: Build Android apk 52 | run: flutter build apk --flavor production --release --build-number $GITHUB_RUN_NUMBER 53 | 54 | - name: Find HEAD commit 55 | id: head 56 | run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT 57 | 58 | - name: Build changelog on "main" 59 | id: changelog 60 | uses: mikepenz/release-changelog-builder-action@v4 61 | with: 62 | configuration: ".github/workflows/configs/changelog-config.json" 63 | # Listing PRs from the last tag to the HEAD commit 64 | toTag: ${{ steps.head.outputs.sha }} 65 | token: ${{ secrets.GITHUB_TOKEN }} 66 | 67 | - name: Deploy Android Production to Firebase 68 | uses: wzieba/Firebase-Distribution-Github-Action@v1.5.0 69 | with: 70 | appId: ${{ vars.FIREBASE_ANDROID_APP_ID }} 71 | serviceCredentialsFileContent: ${{ secrets.FIREBASE_DISTRIBUTION_CREDENTIAL_JSON }} 72 | groups: ${{ vars.FIREBASE_DISTRIBUTION_TESTER_GROUPS }} 73 | releaseNotes: ${{ steps.changelog.outputs.changelog }} 74 | file: build/app/outputs/flutter-apk/app-production-release.apk 75 | -------------------------------------------------------------------------------- /sample/.github/workflows/android_deploy_production_to_playstore.yml: -------------------------------------------------------------------------------- 1 | name: Android - Deploy Production build to Play Store 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build_and_deploy_android: 10 | name: Android - Deploy Production build to Play Store 11 | runs-on: ubuntu-latest 12 | environment: production 13 | timeout-minutes: 30 14 | steps: 15 | - name: Check out 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Java JDK 19 | uses: actions/setup-java@v3 20 | with: 21 | distribution: 'adopt' 22 | java-version: '11' 23 | 24 | - name: Set up Flutter environment 25 | uses: subosito/flutter-action@v2 26 | with: 27 | channel: 'stable' 28 | flutter-version: '3.10.5' 29 | 30 | - name: Get Flutter dependencies 31 | run: flutter pub get 32 | 33 | - name: Run code generator 34 | run: flutter packages pub run build_runner build --delete-conflicting-outputs 35 | 36 | - name: Set up .env 37 | env: 38 | ENV: ${{ secrets.ENV }} 39 | run: | 40 | echo -e "$ENV" > .env 41 | 42 | - name: Set up release signing configs 43 | env: 44 | ANDROID_RELEASE_KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }} 45 | ANDROID_SIGNING_PROPERTIES: ${{ secrets.ANDROID_SIGNING_PROPERTIES }} 46 | run: | 47 | echo $ANDROID_RELEASE_KEYSTORE_BASE64 | base64 --decode > android/config/release.keystore 48 | echo "$ANDROID_SIGNING_PROPERTIES" > android/signing.properties 49 | 50 | - name: Build Production App Bundle 51 | run: flutter build appbundle --flavor production --release --build-number $GITHUB_RUN_NUMBER 52 | 53 | - name: Upload Android Production Release bundle to Play Store 54 | uses: r0adkll/upload-google-play@v1 55 | with: 56 | serviceAccountJson: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} 57 | packageName: co.nimblehq.flutter.template 58 | releaseFile: build/app/outputs/bundle/productionRelease/app-production-release.aab 59 | track: internal 60 | userFraction: 1.0 61 | whatsNewDirectory: .github/workflows/whatsnew 62 | -------------------------------------------------------------------------------- /sample/.github/workflows/android_deploy_staging.yml: -------------------------------------------------------------------------------- 1 | name: Android - Deploy Staging build to Firebase 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | 8 | jobs: 9 | build_and_deploy_android: 10 | name: Android - Deploy Staging build to Firebase 11 | runs-on: ubuntu-latest 12 | environment: staging 13 | timeout-minutes: 30 14 | steps: 15 | - name: Check out 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Java JDK 19 | uses: actions/setup-java@v3 20 | with: 21 | distribution: 'adopt' 22 | java-version: '11' 23 | 24 | - name: Set up Flutter environment 25 | uses: subosito/flutter-action@v2 26 | with: 27 | channel: 'stable' 28 | flutter-version: '3.10.5' 29 | 30 | - name: Get Flutter dependencies 31 | run: flutter pub get 32 | 33 | - name: Run code generator 34 | run: flutter packages pub run build_runner build --delete-conflicting-outputs 35 | 36 | - name: Set up .env.staging 37 | env: 38 | ENV: ${{ secrets.ENV }} 39 | run: | 40 | echo -e "$ENV" > .env.staging 41 | 42 | # App Bundle requires Firebase connected to Play Store to upload https://appdistribution.page.link/KPoa 43 | - name: Build Android apk 44 | run: flutter build apk --flavor staging --debug --build-number $GITHUB_RUN_NUMBER 45 | 46 | - name: Find HEAD commit 47 | id: head 48 | run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT 49 | 50 | - name: Limit changelog to the latest pull request only 51 | uses: jossef/action-set-json-field@v2 52 | with: 53 | file: .github/workflows/configs/changelog-config.json 54 | field: max_pull_requests 55 | value: 1 56 | 57 | - name: Build changelog on "develop" 58 | id: changelog 59 | uses: mikepenz/release-changelog-builder-action@v4 60 | with: 61 | configuration: ".github/workflows/configs/changelog-config.json" 62 | # Listing PRs from the last tag to the HEAD commit 63 | toTag: ${{ steps.head.outputs.sha }} 64 | token: ${{ secrets.GITHUB_TOKEN }} 65 | 66 | - name: Deploy Android Staging to Firebase 67 | uses: wzieba/Firebase-Distribution-Github-Action@v1.5.0 68 | with: 69 | appId: ${{ vars.FIREBASE_ANDROID_APP_ID }} 70 | serviceCredentialsFileContent: ${{ secrets.FIREBASE_DISTRIBUTION_CREDENTIAL_JSON }} 71 | groups: ${{ vars.FIREBASE_DISTRIBUTION_TESTER_GROUPS }} 72 | releaseNotes: ${{ steps.changelog.outputs.changelog }} 73 | file: build/app/outputs/flutter-apk/app-staging-debug.apk 74 | -------------------------------------------------------------------------------- /sample/.github/workflows/bump_version.yml: -------------------------------------------------------------------------------- 1 | name: Bump Version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | newVersion: 7 | description: "New version" 8 | required: true 9 | type: string 10 | 11 | jobs: 12 | bump_version: 13 | name: Bump version 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 5 16 | steps: 17 | - name: Check out 18 | uses: actions/checkout@v3 19 | 20 | - name: Set new version 21 | run: | 22 | perl -i -pe 's/^(version:\s+\d+\.\d+\.\d+\+)(\d+)$/"version: ${{ github.event.inputs.newVersion }}+".($2+1)/e' ./pubspec.yaml 23 | 24 | - name: Create pull request 25 | uses: peter-evans/create-pull-request@v4 26 | with: 27 | assignees: ${{ secrets.GITHUB_USER }} 28 | token: ${{ secrets.WIKI_ACTION_TOKEN }} 29 | commit-message: Bump version to ${{ github.event.inputs.newVersion }} 30 | committer: Nimble Bot 31 | branch: chore/bump-version-to-${{ github.event.inputs.newVersion }} 32 | delete-branch: true 33 | title: '[Chore] Bump version to ${{ github.event.inputs.newVersion }}' 34 | labels: | 35 | type : chore 36 | body: | 37 | ## What happened 👀 38 | 39 | Bump version to ${{ github.event.inputs.newVersion }} 40 | 41 | ## Insight 📝 42 | 43 | Automatically created by the Bump Version workflow. 44 | 45 | ## Proof Of Work 📹 46 | 47 | On the Files changed tab 48 | -------------------------------------------------------------------------------- /sample/.github/workflows/configs/changelog-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## ✨ Features", 5 | "labels": [ 6 | "type : feature" 7 | ] 8 | }, 9 | { 10 | "title": "## 🐛 Bug fixes", 11 | "labels": [ 12 | "type : bug" 13 | ] 14 | }, 15 | { 16 | "title": "## 🧹 Chores", 17 | "labels": [ 18 | "type : chore" 19 | ] 20 | }, 21 | { 22 | "title": "## Others", 23 | "exclude_labels": [ 24 | "type : feature", 25 | "type : bug", 26 | "type : chore", 27 | "type : release" 28 | ] 29 | } 30 | ], 31 | "max_pull_requests": 200 32 | } 33 | -------------------------------------------------------------------------------- /sample/.github/workflows/publish_wiki.yml: -------------------------------------------------------------------------------- 1 | # For setup instruction, refer to https://github.com/nimblehq/github-actions-workflows/blob/main/.github/workflows/publish_wiki.yml 2 | name: Publish Wiki 3 | 4 | on: 5 | push: 6 | paths: 7 | - .github/wiki/** 8 | branches: 9 | - develop 10 | 11 | jobs: 12 | publish: 13 | name: Publish Wiki 14 | uses: nimblehq/github-actions-workflows/.github/workflows/publish_wiki.yml@0.1.0 15 | with: 16 | USER_NAME: github-wiki-workflow 17 | USER_EMAIL: ${{ secrets.GH_EMAIL }} 18 | secrets: 19 | USER_TOKEN: ${{ secrets.GH_TOKEN }} 20 | -------------------------------------------------------------------------------- /sample/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | # Trigger the workflow on push or pull request, 4 | # but push action is only for the feature branch 5 | pull_request: 6 | types: [ opened, synchronize, edited, reopened ] 7 | push: 8 | branches-ignore: 9 | - develop 10 | - 'release/**' 11 | jobs: 12 | lint_and_test: 13 | name: Static code analyze & Unit test 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 30 16 | environment: staging 17 | steps: 18 | - name: Check out 19 | uses: actions/checkout@v3 20 | 21 | - name: Set up Flutter environment 22 | uses: subosito/flutter-action@v2 23 | with: 24 | channel: 'stable' 25 | flutter-version: '3.10.5' 26 | 27 | - name: Get Flutter dependencies 28 | run: flutter pub get 29 | 30 | - name: Run code generator 31 | run: flutter packages pub run build_runner build --delete-conflicting-outputs 32 | 33 | - name: Check for any formatting issues in the code 34 | run: dart format --set-exit-if-changed . 35 | 36 | - name: Statically analyze the Dart code for any errors 37 | run: flutter analyze . 38 | 39 | - name: Run widget tests, unit tests 40 | run: flutter test --machine --coverage 41 | 42 | - name: Upload coverage to codecov 43 | uses: codecov/codecov-action@v2 44 | with: 45 | files: ./coverage/lcov.info 46 | flags: unittests # optional 47 | token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 48 | fail_ci_if_error: false 49 | verbose: true 50 | -------------------------------------------------------------------------------- /sample/.github/workflows/whatsnew/whatsnew-en-US: -------------------------------------------------------------------------------- 1 | 2 | Enter or paste your release notes for en-US here 3 | 4 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | # Used by dotenv library to load environment variables. 2 | .env 3 | .env.staging 4 | 5 | # Miscellaneous 6 | *.class 7 | *.log 8 | *.pyc 9 | *.swp 10 | .DS_Store 11 | .atom/ 12 | .buildlog/ 13 | .history 14 | .svn/ 15 | migrate_working_dir/ 16 | 17 | # IntelliJ related 18 | *.iml 19 | *.ipr 20 | *.iws 21 | .idea/ 22 | 23 | # The .vscode folder contains launch configuration and tasks you configure in 24 | # VS Code which you may wish to be included in version control, so this line 25 | # is commented out by default. 26 | .vscode/ 27 | 28 | # Flutter version manager related 29 | .fvm/ 30 | 31 | # Flutter/Dart/Pub related 32 | **/doc/api/ 33 | **/ios/Flutter/.last_build_id 34 | **/ios/.envfile 35 | **/ios/Flutter/tmp.xcconfig 36 | .dart_tool/ 37 | .flutter-plugins 38 | .flutter-plugins-dependencies 39 | .packages 40 | .pub-cache/ 41 | .pub/ 42 | /build/ 43 | 44 | # Web related 45 | lib/generated_plugin_registrant.dart 46 | 47 | # Symbolication related 48 | app.*.symbols 49 | 50 | # Obfuscation related 51 | app.*.map.json 52 | 53 | # Android Studio will place build artifacts here 54 | /android/app/debug 55 | /android/app/profile 56 | /android/app/release 57 | 58 | # Flutter generated files 59 | *.g.dart 60 | *.gen.dart 61 | *.config.dart 62 | *.freezed.dart 63 | *.mocks.dart 64 | -------------------------------------------------------------------------------- /sample/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /sample/README.md: -------------------------------------------------------------------------------- 1 | # Flutter Templates 2 | 3 | [![codecov](https://codecov.io/gh/nimblehq/sample/branch/main/graph/badge.svg?token=ATUNXDX218)](https://codecov.io/gh/nimblehq/sample) 4 | 5 | ## Prerequisite 6 | 7 | - Flutter 3.10.5 8 | - Flutter version manager (recommend): [fvm](https://fvm.app/) 9 | 10 | ## Getting Started 11 | 12 | ### Setup 13 | 14 | - Create these `.env` files in the root directory according to the flavors and add the required 15 | environment variables. The example file is `.env.sample`. 16 | 17 | - Staging: `.env.staging` 18 | 19 | - Production: `.env` 20 | 21 | - To make the Android release build, 22 | 23 | - put the `release.keystore` at the `android/config` folder, 24 | 25 | - create the `signing.properties` file to provide keystore credentials in the `android` folder. The example file is `signing.properties.sample`. 26 | 27 | ### Run 28 | 29 | - Run code generator for JSON models, DI dependencies, etc: 30 | 31 | - `$ fvm flutter packages pub run build_runner build --delete-conflicting-outputs` 32 | 33 | - Run the app with the desired app flavor: 34 | 35 | - `$ fvm flutter run --flavor staging` 36 | - `$ fvm flutter run --flavor production` 37 | 38 | - Check code formatting & static code analyzing: 39 | 40 | - `$ dart format --set-exit-if-changed .` 41 | - `$ fvm flutter analyze .` 42 | 43 | ### Test 44 | 45 | - Run unit testing: 46 | 47 | - `$ fvm flutter test` 48 | - `$ fvm flutter test --machine --coverage` 49 | 50 | - Run integration testing: 51 | 52 | - `$ fvm flutter drive --driver=test_driver/integration_test.dart --target=integration_test/{test_file}.dart --flavor staging` 53 | 54 | - For example: 55 | 56 | `$ fvm flutter drive --driver=test_driver/integration_test.dart --target=integration_test/my_home_page_test.dart --flavor staging` 57 | 58 | ## License 59 | 60 | This project is Copyright (c) 2014 and onwards. It is free software, 61 | and may be redistributed under the terms specified in the [LICENSE] file. 62 | 63 | [LICENSE]: /LICENSE 64 | 65 | ## About 66 | 67 | ![Nimble](https://assets.nimblehq.co/logo/dark/logo-dark-text-160.png) 68 | 69 | This project is maintained and funded by Nimble. 70 | 71 | We love open source and do our part in sharing our work with the community! 72 | See [our other projects][community] or [hire our team][hire] to help build your product. 73 | 74 | [community]: https://github.com/nimblehq 75 | [hire]: https://nimblehq.co/ 76 | -------------------------------------------------------------------------------- /sample/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | 31 | analyzer: 32 | exclude: [ 33 | lib/**.g.dart, 34 | lib/**.gen.dart, 35 | lib/**.config.dart, 36 | lib/**.freezed.dart, 37 | test/**.mocks.dart 38 | ] 39 | -------------------------------------------------------------------------------- /sample/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | signing.properties 12 | config/release.keystore 13 | -------------------------------------------------------------------------------- /sample/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # Keep BuildConfig for Flutter Config https://github.com/ByneappLLC/flutter_config/blob/master/doc/ANDROID.md#android-setup 24 | -keep class co.nimblehq.flutter.template.BuildConfig { *; } 25 | -------------------------------------------------------------------------------- /sample/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /sample/android/app/src/main/kotlin/co/nimblehq/flutter/template/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package co.nimblehq.flutter.template 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /sample/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /sample/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /sample/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Flutter Templates 4 | 5 | -------------------------------------------------------------------------------- /sample/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /sample/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/android/app/src/staging/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Flutter Templates Staging 4 | 5 | -------------------------------------------------------------------------------- /sample/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /sample/android/config/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/android/config/debug.keystore -------------------------------------------------------------------------------- /sample/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /sample/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /sample/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /sample/android/signing.properties.sample: -------------------------------------------------------------------------------- 1 | KEYSTORE_PASSWORD= 2 | KEY_ALIAS= 3 | KEY_PASSWORD= 4 | -------------------------------------------------------------------------------- /sample/assets/fonts/neuzeit.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/assets/fonts/neuzeit.otf -------------------------------------------------------------------------------- /sample/assets/images/nimble_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/assets/images/nimble_logo.png -------------------------------------------------------------------------------- /sample/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | # Full FieldRename's options, refer https://github.com/google/json_serializable.dart/blob/2185e8b80d8d0c12e2adbf897d920b6f5725cded/json_annotation/lib/src/json_serializable.dart#L16-L32 7 | field_rename: "snake" 8 | -------------------------------------------------------------------------------- /sample/codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | bot: [bot_username] # For the private repository, set up a TeamBot https://docs.codecov.com/docs/team-bot, e.g., bob 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | status: 7 | project: off 8 | patch: off 9 | 10 | parsers: 11 | gcov: 12 | branch_detection: 13 | conditional: yes 14 | loop: yes 15 | method: no 16 | macro: no 17 | 18 | comment: 19 | layout: "reach,diff,flags,files,tree" 20 | behavior: default 21 | require_changes: no 22 | 23 | ignore: 24 | - "lib/di" 25 | - "lib/gen" 26 | -------------------------------------------------------------------------------- /sample/integration_test/real_app_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:integration_test/integration_test.dart'; 4 | 5 | import 'utils/test_util.dart'; 6 | 7 | void main() { 8 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 9 | 10 | testWidgets('Real App', (WidgetTester tester) async { 11 | await tester.pumpWidget(TestUtil.pumpWidgetWithRealApp('/')); 12 | await tester.pumpAndSettle(); 13 | 14 | expect(find.widgetWithText(AppBar, 'Flutter Templates testing'), 15 | findsOneWidget); 16 | expect(find.text('This is only for testing'), findsOneWidget); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /sample/integration_test/screens/home_screen_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:sample/app/screens/home/home_screen.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:integration_test/integration_test.dart'; 5 | 6 | import '../utils/test_util.dart'; 7 | 8 | void main() { 9 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | testWidgets('My Home Page widget', (WidgetTester tester) async { 12 | await tester 13 | .pumpWidget(TestUtil.pumpWidgetWithShellApp(const HomeScreen())); 14 | await tester.pumpAndSettle(); 15 | 16 | expect(find.widgetWithText(AppBar, 'Flutter Templates testing'), 17 | findsOneWidget); 18 | expect(find.text('This is only for testing'), findsOneWidget); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /sample/integration_test/utils/test_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_config/flutter_config.dart'; 3 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 4 | import 'package:package_info_plus/package_info_plus.dart'; 5 | import 'package:sample/main.dart'; 6 | 7 | class TestUtil { 8 | /// This is useful when we test the whole app with the real configs(styling, 9 | /// localization, routes, etc) 10 | static Widget pumpWidgetWithRealApp(String initialRoute) { 11 | _initDependencies(); 12 | return MyApp(); 13 | } 14 | 15 | /// We normally use this function to test a specific [widget] without 16 | /// considering much about theming. 17 | static Widget pumpWidgetWithShellApp(Widget widget) { 18 | _initDependencies(); 19 | return MaterialApp( 20 | localizationsDelegates: AppLocalizations.localizationsDelegates, 21 | supportedLocales: AppLocalizations.supportedLocales, 22 | home: widget, 23 | ); 24 | } 25 | 26 | static void _initDependencies() { 27 | PackageInfo.setMockInitialValues( 28 | appName: 'Flutter Templates testing', 29 | packageName: '', 30 | version: '', 31 | buildNumber: '', 32 | buildSignature: ''); 33 | FlutterConfig.loadValueForTesting({'SECRET': 'This is only for testing'}); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | Flutter/tmp.xcconfig 28 | Flutter/.last_build_id 29 | ServiceDefinitions.json 30 | Runner/GeneratedPluginRegistrant.* 31 | Build/ 32 | DerivedData/ 33 | fastlane/README.md 34 | fastlane/report.xml 35 | .envfile 36 | 37 | # Exceptions to above rules. 38 | !default.mode1v3 39 | !default.mode2v3 40 | !default.pbxuser 41 | !default.perspectivev3 42 | -------------------------------------------------------------------------------- /sample/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include? "tmp.xcconfig" 4 | -------------------------------------------------------------------------------- /sample/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include? "tmp.xcconfig" 4 | -------------------------------------------------------------------------------- /sample/ios/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | gem "cocoapods" 5 | 6 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 7 | eval_gemfile(plugins_path) if File.exist?(plugins_path) -------------------------------------------------------------------------------- /sample/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_config (0.0.1): 4 | - Flutter 5 | - integration_test (0.0.1): 6 | - Flutter 7 | - package_info_plus (0.4.5): 8 | - Flutter 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `Flutter`) 12 | - flutter_config (from `.symlinks/plugins/flutter_config/ios`) 13 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 14 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 15 | 16 | EXTERNAL SOURCES: 17 | Flutter: 18 | :path: Flutter 19 | flutter_config: 20 | :path: ".symlinks/plugins/flutter_config/ios" 21 | integration_test: 22 | :path: ".symlinks/plugins/integration_test/ios" 23 | package_info_plus: 24 | :path: ".symlinks/plugins/package_info_plus/ios" 25 | 26 | SPEC CHECKSUMS: 27 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 28 | flutter_config: 2226c1df19c78fe34a05eb7f1363445f18e76fc1 29 | integration_test: 13825b8a9334a850581300559b8839134b124670 30 | package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 31 | 32 | PODFILE CHECKSUM: 22cb4141f6291ab800e0ca928cc1f87e9a69bf64 33 | 34 | COCOAPODS: 1.11.3 35 | -------------------------------------------------------------------------------- /sample/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /sample/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /sample/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /sample/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | $(APP_DISPLAY_NAME) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | sample 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /sample/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /sample/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /sample/ios/fastlane/Constants/Constants.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Constants 4 | ################# 5 | #### PROJECT #### 6 | ################# 7 | 8 | # Workspace path 9 | def self.WORKSPACE_PATH 10 | './Runner.xcworkspace' 11 | end 12 | 13 | # Project path 14 | def self.PROJECT_PATH 15 | './Runner.xcodeproj' 16 | end 17 | 18 | # bundle ID for Staging app 19 | def self.BUNDLE_ID_STAGING 20 | 'co.nimblehq.flutter.template.staging' 21 | end 22 | 23 | # bundle ID for Production app 24 | def self.BUNDLE_ID_PRODUCTION 25 | 'co.nimblehq.flutter.template' 26 | end 27 | 28 | ################# 29 | #### BUILDING ### 30 | ################# 31 | 32 | # a derived data path 33 | def self.DERIVED_DATA_PATH 34 | './DerivedData' 35 | end 36 | 37 | # a build path 38 | def self.BUILD_PATH 39 | './Build' 40 | end 41 | 42 | ################# 43 | #### KEYCHAIN #### 44 | ################# 45 | 46 | # Keychain name 47 | def self.KEYCHAIN_NAME 48 | 'github_action_keychain' 49 | end 50 | 51 | def self.KEYCHAIN_PASSWORD 52 | 'password' 53 | end 54 | 55 | ################# 56 | ### ARCHIVING ### 57 | ################# 58 | # an staging environment scheme name 59 | def self.SCHEME_NAME_STAGING 60 | 'staging' 61 | end 62 | 63 | # a Production environment scheme name 64 | def self.SCHEME_NAME_PRODUCTION 65 | 'production' 66 | end 67 | 68 | # an staging product name 69 | def self.PRODUCT_NAME_STAGING 70 | 'Flutter Templates Staging' 71 | end 72 | 73 | # a staging TestFlight product name 74 | def self.PRODUCT_NAME_STAGING_TEST_FLIGHT 75 | 'Flutter Templates Staging' 76 | end 77 | 78 | # a Production product name 79 | def self.PRODUCT_NAME_PRODUCTION 80 | 'Flutter Templates' 81 | end 82 | 83 | # a main target name 84 | def self.MAIN_TARGET_NAME 85 | 'Runner' 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /sample/ios/fastlane/Constants/Environments.rb: -------------------------------------------------------------------------------- 1 | class Environments 2 | def self.CI 3 | ENV['CI'] 4 | end 5 | 6 | def self.MANUAL_VERSION 7 | ENV['MANUAL_VERSION'] 8 | end 9 | 10 | def self.FASTLANE_USER 11 | ENV['FASTLANE_USER'] 12 | end 13 | 14 | def self.TEAM_ID 15 | ENV['TEAM_ID'] 16 | end 17 | 18 | def self.APP_STORE_KEY_ID 19 | ENV['APP_STORE_KEY_ID'] 20 | end 21 | 22 | def self.APP_STORE_ISSUER_ID 23 | ENV['APP_STORE_ISSUER_ID'] 24 | end 25 | 26 | def self.APP_STORE_CONNECT_API_KEY_BASE64 27 | ENV['APP_STORE_CONNECT_API_KEY_BASE64'] 28 | end 29 | 30 | ################# 31 | ### Firebase ### 32 | ################# 33 | 34 | def self.FIREBASE_CLI_TOKEN 35 | ENV['FIREBASE_CLI_TOKEN'] 36 | end 37 | 38 | def self.FIREBASE_APP_ID 39 | ENV['FIREBASE_APP_ID'] 40 | end 41 | 42 | def self.FIREBASE_TESTER_GROUPS 43 | ENV['FIREBASE_DISTRIBUTION_TESTER_GROUPS'] 44 | end 45 | 46 | def self.GITHUB_RUN_NUMBER 47 | ENV['GITHUB_RUN_NUMBER'] 48 | end 49 | 50 | def self.RELEASE_NOTE_CONTENT 51 | ENV['RELEASE_NOTE_CONTENT'] 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /sample/ios/fastlane/Gymfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | clean(true) 4 | export_team_id(Environments.TEAM_ID) 5 | output_directory(Constants.BUILD_PATH) # .ipa 6 | build_path(Constants.BUILD_PATH) # .xcarchive is stored 7 | derived_data_path(Constants.DERIVED_DATA_PATH) # .app 8 | -------------------------------------------------------------------------------- /sample/ios/fastlane/Managers/BuildManager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BuildManager 4 | def initialize(fastlane:) 5 | @fastlane = fastlane 6 | end 7 | 8 | def build_app_store(scheme, product_name, bundle_identifier, include_bitcode) 9 | @fastlane.gym( 10 | scheme: scheme, 11 | export_method: 'app-store', 12 | export_options: { 13 | provisioningProfiles: { 14 | @bundle_identifier_staging.to_s => "match AppStore #{bundle_identifier}" 15 | } 16 | }, 17 | include_bitcode: include_bitcode, 18 | output_name: product_name 19 | ) 20 | end 21 | 22 | def build_ad_hoc(scheme, product_name, bundle_identifier) 23 | @fastlane.gym( 24 | scheme: scheme, 25 | export_method: 'ad-hoc', 26 | export_options: { 27 | provisioningProfiles: { 28 | @bundle_identifier_staging.to_s => "match Adhoc #{bundle_identifier}" 29 | } 30 | }, 31 | include_bitcode: false, 32 | output_name: product_name, 33 | disable_xcpretty: true 34 | ) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /sample/ios/fastlane/Managers/DistributionManager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DistributionManager 4 | def initialize(fastlane:, build_path:, firebase_token:) 5 | @fastlane = fastlane 6 | @build_path = build_path 7 | @firebase_token = firebase_token 8 | end 9 | 10 | def upload_to_testflight(product_name:, bundle_identifier:) 11 | @fastlane.pilot( 12 | ipa: "#{@build_path}/#{product_name}.ipa", 13 | app_identifier: bundle_identifier, 14 | notify_external_testers: false, 15 | skip_waiting_for_build_processing: true 16 | ) 17 | end 18 | 19 | def upload_to_app_store_connect(product_name:, bundle_identifier:) 20 | @fastlane.deliver( 21 | ipa: "#{@build_path}/#{product_name}.ipa", 22 | app_identifier: bundle_identifier, 23 | force: true, 24 | skip_metadata: true, 25 | skip_screenshots: true, 26 | run_precheck_before_submit: false 27 | ) 28 | end 29 | 30 | def upload_to_firebase(product_name:, firebase_app_id:, notes:, tester_groups:) 31 | ipa_path = "#{@build_path}/#{product_name}.ipa" 32 | @fastlane.firebase_app_distribution( 33 | app: firebase_app_id, 34 | ipa_path: ipa_path, 35 | groups: tester_groups, 36 | firebase_cli_token: @firebase_token, 37 | release_notes: notes 38 | ) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /sample/ios/fastlane/Managers/MatchManager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MatchManager 4 | def initialize( 5 | fastlane:, 6 | keychain_name:, 7 | keychain_password:, 8 | is_ci: 9 | ) 10 | @fastlane = fastlane 11 | @keychain_name = keychain_name 12 | @keychain_password = keychain_password 13 | @is_ci = is_ci 14 | end 15 | 16 | def sync_adhoc_signing(app_identifier:) 17 | if @is_ci 18 | create_ci_keychain 19 | @fastlane.match( 20 | type: 'adhoc', 21 | keychain_name: @keychain_name, 22 | keychain_password: @keychain_password, 23 | app_identifier: app_identifier, 24 | readonly: true 25 | ) 26 | else 27 | @fastlane.match(type: 'adhoc', app_identifier: app_identifier, readonly: true) 28 | end 29 | end 30 | 31 | def sync_app_store_signing(app_identifier:) 32 | if @is_ci 33 | create_ci_keychain 34 | @fastlane.match( 35 | type: 'appstore', 36 | keychain_name: @keychain_name, 37 | keychain_password: @keychain_password, 38 | app_identifier: app_identifier, 39 | readonly: true 40 | ) 41 | else 42 | @fastlane.match(type: 'appstore', app_identifier: app_identifier, readonly: true) 43 | end 44 | end 45 | 46 | def create_ci_keychain 47 | @fastlane.create_keychain( 48 | name: @keychain_name, 49 | password: @keychain_password, 50 | default_keychain: true, 51 | unlock: true, 52 | timeout: 3600, 53 | lock_when_sleeps: false 54 | ) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /sample/ios/fastlane/Matchfile: -------------------------------------------------------------------------------- 1 | git_url("git@github.com:nimblehq/match-certificates.git") 2 | 3 | storage_mode("git") 4 | 5 | # type("appstore") # The default type, can be: appstore, adhoc, enterprise or development 6 | 7 | # app_identifier(["tools.fastlane.app", "tools.fastlane.app2"]) 8 | # username("user@fastlane.tools") # Your Apple Developer Portal username 9 | 10 | # For all available options run `fastlane match --help` 11 | # Remove the # in the beginning of the line to enable the other options 12 | 13 | # The docs are available on https://docs.fastlane.tools/actions/match 14 | readonly(true) 15 | force(false) 16 | -------------------------------------------------------------------------------- /sample/ios/fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-firebase_app_distribution' 6 | -------------------------------------------------------------------------------- /sample/l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart 4 | -------------------------------------------------------------------------------- /sample/lib/app/resources/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColors { 4 | static const Color nimblePrimaryBlue = Color(0xFF201547); 5 | } 6 | -------------------------------------------------------------------------------- /sample/lib/app/screens/home/home_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:sample/app/screens/home/home_view_state.dart'; 3 | import 'package:sample/domain/usecases/base/base_use_case.dart'; 4 | import 'package:sample/domain/usecases/get_users_use_case.dart'; 5 | import 'package:sample/domain/models/user.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | 8 | class HomeViewModel extends StateNotifier { 9 | final GetUsersUseCase _getUsersUseCase; 10 | 11 | HomeViewModel( 12 | this._getUsersUseCase, 13 | ) : super(const HomeViewState.init()); 14 | 15 | final StreamController> _usersStream = StreamController(); 16 | 17 | Stream> get usersStream => _usersStream.stream; 18 | 19 | Future getUsers() async { 20 | final result = await _getUsersUseCase.call(); 21 | if (result is Success>) { 22 | _usersStream.add(result.value); 23 | } else { 24 | // TODO handle error 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/lib/app/screens/home/home_view_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'home_view_state.freezed.dart'; 4 | 5 | @freezed 6 | class HomeViewState with _$HomeViewState { 7 | const factory HomeViewState.init() = _Init; 8 | } 9 | -------------------------------------------------------------------------------- /sample/lib/data/local/secure_storage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | 4 | const _keyAccessToken = 'KEY_ACCESS_TOKEN'; 5 | 6 | abstract class SecureStorage { 7 | Future get accessToken; 8 | 9 | Future storeAccessToken(String accessToken); 10 | 11 | Future clearAll(); 12 | } 13 | 14 | @Singleton(as: SecureStorage) 15 | class SecureStorageImpl extends SecureStorage { 16 | final FlutterSecureStorage _storage; 17 | 18 | SecureStorageImpl(this._storage); 19 | 20 | @override 21 | Future get accessToken => _storage.read(key: _keyAccessToken); 22 | 23 | @override 24 | Future storeAccessToken(String accessToken) { 25 | return _storage.write(key: _keyAccessToken, value: accessToken); 26 | } 27 | 28 | @override 29 | Future clearAll() { 30 | return _storage.deleteAll(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/lib/data/remote/datasources/api_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:sample/data/remote/models/responses/user_response.dart'; 3 | import 'package:retrofit/retrofit.dart'; 4 | 5 | part 'api_service.g.dart'; 6 | 7 | abstract class BaseApiService { 8 | Future> getUsers(); 9 | } 10 | 11 | @RestApi() 12 | abstract class ApiService extends BaseApiService { 13 | factory ApiService(Dio dio, {String baseUrl}) = _ApiService; 14 | 15 | // TODO add API endpoint 16 | @override 17 | @GET('users') 18 | Future> getUsers(); 19 | } 20 | -------------------------------------------------------------------------------- /sample/lib/data/remote/models/requests/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nimblehq/flutter-templates/7ea62bed55bcd0424b915ef6d4796911ad62d4cc/sample/lib/data/remote/models/requests/.keep -------------------------------------------------------------------------------- /sample/lib/data/remote/models/responses/user_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:sample/domain/models/user.dart'; 3 | 4 | part 'user_response.g.dart'; 5 | 6 | @JsonSerializable() 7 | class UserResponse { 8 | final String email; 9 | final String username; 10 | 11 | UserResponse(this.email, this.username); 12 | 13 | factory UserResponse.fromJson(Map json) => 14 | _$UserResponseFromJson(json); 15 | 16 | Map toJson() => _$UserResponseToJson(this); 17 | 18 | User toUser() => User( 19 | email: email, 20 | username: username, 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /sample/lib/data/repositories/credential_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:sample/data/remote/datasources/api_service.dart'; 2 | import 'package:sample/domain/exceptions/network_exceptions.dart'; 3 | import 'package:sample/domain/models/user.dart'; 4 | import 'package:sample/domain/repositories/credential_repository.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | @LazySingleton(as: CredentialRepository) 8 | class CredentialRepositoryImpl extends CredentialRepository { 9 | final BaseApiService _apiService; 10 | 11 | CredentialRepositoryImpl(this._apiService); 12 | 13 | @override 14 | Future> getUsers() async { 15 | try { 16 | final userResponses = await _apiService.getUsers(); 17 | return userResponses 18 | .map((userResponse) => userResponse.toUser()) 19 | .toList(); 20 | } catch (exception) { 21 | throw NetworkExceptions.fromDioException(exception); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/lib/di/di.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | 4 | import 'di.config.dart'; 5 | 6 | final GetIt getIt = GetIt.instance; 7 | 8 | @injectableInit 9 | Future configureInjection() async => getIt.init(); 10 | -------------------------------------------------------------------------------- /sample/lib/di/interceptor/app_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | class AppInterceptor extends Interceptor { 6 | final bool _requireAuthenticate; 7 | final Dio _dio; 8 | 9 | AppInterceptor( 10 | this._requireAuthenticate, 11 | this._dio, 12 | ); 13 | 14 | @override 15 | Future onRequest( 16 | RequestOptions options, RequestInterceptorHandler handler) async { 17 | if (_requireAuthenticate) { 18 | // TODO header authorization here 19 | // options.headers 20 | // .putIfAbsent(HEADER_AUTHORIZATION, () => ""); 21 | } 22 | return super.onRequest(options, handler); 23 | } 24 | 25 | @override 26 | void onError(DioError err, ErrorInterceptorHandler handler) { 27 | final statusCode = err.response?.statusCode; 28 | if ((statusCode == HttpStatus.forbidden || 29 | statusCode == HttpStatus.unauthorized) && 30 | _requireAuthenticate) { 31 | _doRefreshToken(err, handler); 32 | } else { 33 | handler.next(err); 34 | } 35 | } 36 | 37 | Future _doRefreshToken( 38 | DioError err, 39 | ErrorInterceptorHandler handler, 40 | ) async { 41 | try { 42 | // TODO Request new token 43 | 44 | // if (result is Success) { 45 | // TODO Update new token header 46 | // err.requestOptions.headers[_headerAuthorization] = newToken; 47 | 48 | // Create request with new access token 49 | final options = Options( 50 | method: err.requestOptions.method, 51 | headers: err.requestOptions.headers); 52 | final newRequest = await _dio.request( 53 | "${err.requestOptions.baseUrl}${err.requestOptions.path}", 54 | options: options, 55 | data: err.requestOptions.data, 56 | queryParameters: err.requestOptions.queryParameters); 57 | handler.resolve(newRequest); 58 | // } else { 59 | // handler.next(err); 60 | // } 61 | } catch (exception) { 62 | if (exception is DioError) { 63 | handler.next(exception); 64 | } else { 65 | handler.next(err); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sample/lib/di/module/network_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:sample/data/remote/datasources/api_service.dart'; 2 | import 'package:sample/env.dart'; 3 | import 'package:sample/di/provider/dio_provider.dart'; 4 | import 'package:injectable/injectable.dart'; 5 | 6 | @module 7 | abstract class NetworkModule { 8 | @Singleton(as: BaseApiService) 9 | ApiService provideApiService(DioProvider dioProvider) { 10 | return ApiService( 11 | dioProvider.getDio(), 12 | baseUrl: Env.restApiEndpoint, 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/lib/di/module/storage_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 2 | import 'package:injectable/injectable.dart'; 3 | 4 | @module 5 | abstract class StorageModule { 6 | @singleton 7 | FlutterSecureStorage get flutterSecureStorage => const FlutterSecureStorage( 8 | aOptions: AndroidOptions(encryptedSharedPreferences: true)); 9 | } 10 | -------------------------------------------------------------------------------- /sample/lib/di/provider/dio_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:sample/di/interceptor/app_interceptor.dart'; 4 | import 'package:injectable/injectable.dart'; 5 | 6 | const String headerContentType = 'Content-Type'; 7 | const String defaultContentType = 'application/json; charset=utf-8'; 8 | 9 | @Singleton() 10 | class DioProvider { 11 | Dio? _dio; 12 | 13 | Dio getDio() { 14 | _dio ??= _createDio(); 15 | return _dio!; 16 | } 17 | 18 | Dio _createDio({bool requireAuthenticate = false}) { 19 | final dio = Dio(); 20 | final appInterceptor = AppInterceptor( 21 | requireAuthenticate, 22 | dio, 23 | ); 24 | final interceptors = []; 25 | interceptors.add(appInterceptor); 26 | if (!kReleaseMode) { 27 | interceptors.add(LogInterceptor( 28 | request: true, 29 | responseBody: true, 30 | requestBody: true, 31 | requestHeader: true, 32 | )); 33 | } 34 | 35 | return dio 36 | ..options.connectTimeout = const Duration(seconds: 3000) 37 | ..options.receiveTimeout = const Duration(seconds: 5000) 38 | ..options.headers = {headerContentType: defaultContentType} 39 | ..interceptors.addAll(interceptors); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sample/lib/domain/models/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class User extends Equatable { 4 | final String email; 5 | final String username; 6 | 7 | const User({ 8 | required this.email, 9 | required this.username, 10 | }); 11 | 12 | @override 13 | bool? get stringify => true; 14 | 15 | @override 16 | List get props => [ 17 | email, 18 | username, 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /sample/lib/domain/repositories/credential_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:sample/domain/models/user.dart'; 2 | 3 | abstract class CredentialRepository { 4 | Future> getUsers(); 5 | } 6 | -------------------------------------------------------------------------------- /sample/lib/domain/usecases/base/base_use_case.dart: -------------------------------------------------------------------------------- 1 | part 'use_case_result.dart'; 2 | 3 | abstract class BaseUseCase { 4 | const BaseUseCase(); 5 | } 6 | 7 | abstract class UseCase extends BaseUseCase> { 8 | const UseCase() : super(); 9 | 10 | Future> call(P params); 11 | } 12 | 13 | abstract class NoParamsUseCase extends BaseUseCase> { 14 | const NoParamsUseCase() : super(); 15 | 16 | Future> call(); 17 | } 18 | -------------------------------------------------------------------------------- /sample/lib/domain/usecases/base/use_case_result.dart: -------------------------------------------------------------------------------- 1 | part of 'base_use_case.dart'; 2 | 3 | abstract class Result { 4 | Result._(); 5 | } 6 | 7 | class Success extends Result { 8 | final T value; 9 | 10 | Success(this.value) : super._(); 11 | } 12 | 13 | class UseCaseException implements Exception { 14 | final dynamic actualException; 15 | 16 | UseCaseException(this.actualException); 17 | } 18 | 19 | class Failed extends Result { 20 | final UseCaseException exception; 21 | 22 | Failed(this.exception) : super._(); 23 | } 24 | -------------------------------------------------------------------------------- /sample/lib/domain/usecases/get_users_use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:sample/domain/exceptions/network_exceptions.dart'; 2 | import 'package:sample/domain/repositories/credential_repository.dart'; 3 | import 'package:sample/domain/usecases/base/base_use_case.dart'; 4 | import 'package:sample/domain/models/user.dart'; 5 | import 'package:injectable/injectable.dart'; 6 | 7 | @Injectable() 8 | class GetUsersUseCase extends NoParamsUseCase> { 9 | final CredentialRepository _credentialRepository; 10 | 11 | GetUsersUseCase(this._credentialRepository); 12 | 13 | @override 14 | Future>> call() async { 15 | return _credentialRepository 16 | .getUsers() 17 | .then((value) => 18 | Success(value) as Result>) // ignore: unnecessary_cast 19 | .onError( 20 | (err, stackTrace) => Failed(UseCaseException(err))); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/lib/env.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_config/flutter_config.dart'; 2 | 3 | class Env { 4 | static String get restApiEndpoint { 5 | return FlutterConfig.get('REST_API_ENDPOINT'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sample/lib/l10n/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Hello!" 3 | } 4 | -------------------------------------------------------------------------------- /sample/lib/l10n/app_th.arb: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "สวัสดี" 3 | } 4 | -------------------------------------------------------------------------------- /sample/lib/l10n/app_vi.arb: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "Xin chào!" 3 | } 4 | -------------------------------------------------------------------------------- /sample/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:go_router/go_router.dart'; 5 | import 'package:flutter_config/flutter_config.dart'; 6 | import 'package:sample/di/di.dart'; 7 | import 'package:sample/gen/assets.gen.dart'; 8 | import 'package:sample/app/screens/home/home_screen.dart'; 9 | 10 | void main() async { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | await FlutterConfig.loadEnvVariables(); 13 | await configureInjection(); 14 | runApp( 15 | ProviderScope( 16 | child: MyApp(), 17 | ), 18 | ); 19 | } 20 | 21 | const routePathRootScreen = '/'; 22 | const routePathSecondScreen = 'second'; 23 | 24 | class MyApp extends StatelessWidget { 25 | MyApp({Key? key}) : super(key: key); 26 | 27 | final GoRouter _router = GoRouter( 28 | routes: [ 29 | GoRoute( 30 | path: routePathRootScreen, 31 | builder: (BuildContext context, GoRouterState state) => 32 | const HomeScreen(), 33 | routes: [ 34 | GoRoute( 35 | path: routePathSecondScreen, 36 | builder: (BuildContext context, GoRouterState state) => 37 | const SecondScreen(), 38 | ), 39 | ], 40 | ), 41 | ], 42 | ); 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return MaterialApp.router( 47 | theme: ThemeData( 48 | primarySwatch: Colors.blue, 49 | brightness: Brightness.light, 50 | fontFamily: Assets.fonts.neuzeit, 51 | ), 52 | localizationsDelegates: AppLocalizations.localizationsDelegates, 53 | supportedLocales: AppLocalizations.supportedLocales, 54 | routeInformationProvider: _router.routeInformationProvider, 55 | routeInformationParser: _router.routeInformationParser, 56 | routerDelegate: _router.routerDelegate, 57 | ); 58 | } 59 | } 60 | 61 | class SecondScreen extends StatelessWidget { 62 | const SecondScreen({ 63 | Key? key, 64 | }) : super(key: key); 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | return Scaffold( 69 | appBar: AppBar( 70 | title: const Text("Second Screen"), 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /sample/lib/utils/wrappers/permission_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:permission_handler/permission_handler.dart' 2 | as permission_handler; 3 | 4 | abstract class PermissionWrapper { 5 | Future requestCameraPermission(); 6 | 7 | Future isCameraPermissionDenied(); 8 | 9 | Future isCameraPermissionPermanentlyDenied(); 10 | } 11 | 12 | class PermissionWrapperImpl extends PermissionWrapper { 13 | @override 14 | Future requestCameraPermission() { 15 | return permission_handler.Permission.camera.request().then( 16 | (status) => status == permission_handler.PermissionStatus.granted); 17 | } 18 | 19 | @override 20 | Future isCameraPermissionDenied() { 21 | return permission_handler.Permission.camera.isDenied; 22 | } 23 | 24 | @override 25 | Future isCameraPermissionPermanentlyDenied() { 26 | return permission_handler.Permission.camera.isPermanentlyDenied; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/test/app/screens/home/home_view_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:sample/app/screens/home/home_view_model.dart'; 5 | import 'package:sample/domain/usecases/base/base_use_case.dart'; 6 | import 'package:sample/app/screens/home/home_screen.dart'; 7 | 8 | import '../../../mocks/generate_mocks.mocks.dart'; 9 | import '../../../mocks/data/remote/models/responses/user_response_mocks.dart'; 10 | 11 | void main() { 12 | group("HomeViewModelTest", () { 13 | late ProviderContainer container; 14 | late MockGetUsersUseCase mockGetUsersUseCase; 15 | 16 | setUp(() { 17 | TestWidgetsFlutterBinding.ensureInitialized(); 18 | mockGetUsersUseCase = MockGetUsersUseCase(); 19 | 20 | container = ProviderContainer( 21 | overrides: [ 22 | homeViewModelProvider.overrideWith((ref) => HomeViewModel( 23 | mockGetUsersUseCase, 24 | )), 25 | ], 26 | ); 27 | addTearDown(container.dispose); 28 | }); 29 | 30 | test('When calling get user list successfully, it returns correctly', 31 | () async { 32 | final expectedResult = [UserResponseMocks.mock().toUser()]; 33 | when(mockGetUsersUseCase.call()) 34 | .thenAnswer((_) async => Success(expectedResult)); 35 | 36 | final usersStream = 37 | container.read(homeViewModelProvider.notifier).usersStream; 38 | expect(usersStream, emitsInOrder([expectedResult])); 39 | 40 | container.read(homeViewModelProvider.notifier).getUsers(); 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /sample/test/data/repositories/credential_repository_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:sample/domain/exceptions/network_exceptions.dart'; 2 | import 'package:sample/domain/repositories/credential_repository.dart'; 3 | import 'package:sample/data/repositories/credential_repository_impl.dart'; 4 | import 'package:sample/data/remote/models/responses/user_response.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:mockito/mockito.dart'; 7 | 8 | import '../../mocks/generate_mocks.mocks.dart'; 9 | 10 | void main() { 11 | group('CredentialRepository', () { 12 | MockApiService mockApiService = MockApiService(); 13 | late CredentialRepository repository; 14 | 15 | setUp(() { 16 | repository = CredentialRepositoryImpl(mockApiService); 17 | }); 18 | test( 19 | "When getting user list successfully, it emits corresponding user list", 20 | () async { 21 | when(mockApiService.getUsers()).thenAnswer((_) async => [ 22 | UserResponse('test@email.com', 'test_user'), 23 | ]); 24 | 25 | final result = await repository.getUsers(); 26 | expect(result.length, 1); 27 | expect(result[0].email, 'test@email.com'); 28 | expect(result[0].username, 'test_user'); 29 | }); 30 | 31 | test("When getting user list failed, it returns NetworkExceptions error", 32 | () async { 33 | when(mockApiService.getUsers()).thenThrow(MockDioError()); 34 | 35 | expect( 36 | () => repository.getUsers(), 37 | throwsA(isA()), 38 | ); 39 | }); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /sample/test/domain/usecases/get_users_use_case_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | import 'package:sample/domain/usecases/base/base_use_case.dart'; 4 | import 'package:sample/domain/usecases/get_users_use_case.dart'; 5 | 6 | import '../../mocks/generate_mocks.mocks.dart'; 7 | import '../../mocks/data/remote/models/responses/user_response_mocks.dart'; 8 | 9 | void main() { 10 | group('GetUsersUseCase', () { 11 | late MockCredentialRepository mockRepository; 12 | late GetUsersUseCase getUsersUseCase; 13 | 14 | setUp(() { 15 | mockRepository = MockCredentialRepository(); 16 | getUsersUseCase = GetUsersUseCase(mockRepository); 17 | }); 18 | 19 | test('When getting users successfully, it returns Success result', 20 | () async { 21 | final expectedResult = [UserResponseMocks.mock().toUser()]; 22 | when(mockRepository.getUsers()).thenAnswer((_) async => expectedResult); 23 | final result = await getUsersUseCase.call(); 24 | 25 | expect(result, isA()); 26 | expect((result as Success).value, expectedResult); 27 | }); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /sample/test/mocks/data/remote/models/responses/user_response_mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:sample/data/remote/models/responses/user_response.dart'; 2 | 3 | class UserResponseMocks { 4 | static UserResponse mock() { 5 | return UserResponse( 6 | "email", 7 | "username", 8 | ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sample/test/mocks/generate_mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:sample/data/remote/datasources/api_service.dart'; 3 | import 'package:sample/domain/repositories/credential_repository.dart'; 4 | import 'package:sample/domain/usecases/get_users_use_case.dart'; 5 | import 'package:mockito/annotations.dart'; 6 | 7 | @GenerateMocks([ 8 | ApiService, 9 | CredentialRepository, 10 | DioError, 11 | GetUsersUseCase, 12 | ]) 13 | main() { 14 | // empty class to generate mock repository classes 15 | } 16 | -------------------------------------------------------------------------------- /sample/test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | --------------------------------------------------------------------------------