├── .github └── workflows │ └── dart.yml ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── SECURITY.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── gradoid │ │ │ │ └── scalable_flutter_app_starter │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── fonts │ ├── Rubik-Black.ttf │ ├── Rubik-BlackItalic.ttf │ ├── Rubik-Bold.ttf │ ├── Rubik-BoldItalic.ttf │ ├── Rubik-ExtraBold.ttf │ ├── Rubik-ExtraBoldItalic.ttf │ ├── Rubik-Italic.ttf │ ├── Rubik-Light.ttf │ ├── Rubik-LightItalic.ttf │ ├── Rubik-Medium.ttf │ ├── Rubik-MediumItalic.ttf │ ├── Rubik-Regular.ttf │ ├── Rubik-SemiBold.ttf │ └── Rubik-SemiBoldItalic.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── 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 ├── lib ├── core │ ├── app │ │ ├── app.dart │ │ ├── di.dart │ │ └── style.dart │ ├── extension │ │ ├── context.dart │ │ ├── context_user.dart │ │ └── string.dart │ ├── logger │ │ ├── logger.dart │ │ └── loggy_types.dart │ ├── navigation │ │ ├── route.dart │ │ └── router.dart │ ├── ui │ │ ├── dialog │ │ │ ├── confirmation_dialog.dart │ │ │ └── dialogs.dart │ │ ├── input │ │ │ └── input_field.dart │ │ └── widget │ │ │ ├── labeled_text_button.dart │ │ │ ├── loading_overlay.dart │ │ │ ├── responsive.dart │ │ │ └── url_image.dart │ └── util │ │ ├── urls.dart │ │ └── validators.dart ├── feature │ ├── auth │ │ ├── bloc │ │ │ ├── auth_cubit.dart │ │ │ └── auth_state.dart │ │ ├── repository │ │ │ └── auth_repository.dart │ │ └── ui │ │ │ └── page │ │ │ ├── auth_page.dart │ │ │ └── splash_page.dart │ ├── home │ │ └── ui │ │ │ └── page │ │ │ └── home_page.dart │ ├── settings │ │ └── ui │ │ │ ├── page │ │ │ └── settings_page.dart │ │ │ └── widget │ │ │ ├── app_version.dart │ │ │ └── settings_tile.dart │ └── user │ │ ├── bloc │ │ ├── user_cubit.dart │ │ └── user_state.dart │ │ ├── model │ │ └── user.dart │ │ ├── provider │ │ └── user_mock_provider.dart │ │ ├── repository │ │ └── user_repository.dart │ │ └── ui │ │ └── widget │ │ ├── profile_tab.dart │ │ └── user_image.dart └── main.dart ├── pubspec.lock ├── pubspec.yaml └── web ├── favicon.png ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html └── manifest.json /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Dart 7 | 8 | on: 9 | push: 10 | branches: [ "*" ] 11 | pull_request: 12 | branches: [ "*" ] 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | # Flutter Actions 22 | - uses: subosito/flutter-action@v2 23 | with: 24 | channel: 'stable' 25 | - run: flutter --version 26 | 27 | - name: Install dependencies 28 | run: flutter pub get 29 | 30 | # Formatting Check 31 | - name: Verify formatting 32 | run: dart format -o none --set-exit-if-changed lib 33 | 34 | # Analyze Code 35 | - name: Analyze project source 36 | run: flutter analyze 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /.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: "ead455963c12b453cdb2358cad34969c76daf180" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: ead455963c12b453cdb2358cad34969c76daf180 17 | base_revision: ead455963c12b453cdb2358cad34969c76daf180 18 | - platform: android 19 | create_revision: ead455963c12b453cdb2358cad34969c76daf180 20 | base_revision: ead455963c12b453cdb2358cad34969c76daf180 21 | - platform: ios 22 | create_revision: ead455963c12b453cdb2358cad34969c76daf180 23 | base_revision: ead455963c12b453cdb2358cad34969c76daf180 24 | - platform: web 25 | create_revision: ead455963c12b453cdb2358cad34969c76daf180 26 | base_revision: ead455963c12b453cdb2358cad34969c76daf180 27 | 28 | # User provided section 29 | 30 | # List of Local paths (relative to this file) that should be 31 | # ignored by the migrate tool. 32 | # 33 | # Files that are not part of the templates will be ignored by default. 34 | unmanaged_files: 35 | - 'lib/main.dart' 36 | - 'ios/Runner.xcodeproj/project.pbxproj' 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gradoid DOO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scalable Flutter App Starter 2 | 3 | 👋 Hey, welcome to Scalable Flutter App. 4 | 5 | Watch/star this repo to be notified when updates are pushed. 6 | 7 | ## Table of Contents 8 | 9 | 10 | * [Scalable Flutter App Starter](#scalable-flutter-app-starter) 11 | * [Table of Contents](#table-of-contents) 12 | * [What is Scalable Flutter App?](#what-is-scalable-flutter-app) 13 | * [Requirements](#requirements) 14 | * [About the Author](#about-the-author) 15 | * [Features](#features) 16 | * [Scalable Flutter App Pro](#scalable-flutter-app-pro) 17 | * [Docs](#docs) 18 | * [Code Architecture](#code-architecture) 19 | * [Styling](#styling) 20 | * [Google Fonts](#google-fonts) 21 | * [Useful GitHub Pull Request Settings](#useful-github-pull-request-settings) 22 | * [FAQ](#faq) 23 | * [Why bloc and not X?](#why-bloc-and-not-x) 24 | * [Who is Scalable Flutter App for?](#who-is-scalable-flutter-app-for) 25 | * [Where to learn Flutter basics?](#where-to-learn-flutter-basics) 26 | * [What if I don't need a specific feature?](#what-if-i-dont-need-a-specific-feature) 27 | * [What if I want more?](#what-if-i-want-more) 28 | * [Resources](#resources) 29 | * [Feedback](#feedback) 30 | 31 | 32 | ## What is Scalable Flutter App? 33 | 34 | Scalable Flutter App is a starter template for Flutter apps. 35 | 36 | It's designed to be scalable and easy to maintain. And should save you weeks of development time. 37 | 38 | To get started, click on the green "Use this template" button on the top-right. 39 | 40 | ## Requirements 41 | 42 | Always keep up to date: 43 | 44 | - Flutter 45 | - Cocoapods 46 | - Firebase CLI 47 | 48 | ## About the Author 49 | 50 | I'm Milos Jokic and I: 51 | 52 | - built my first mobile app in 2007 53 | - grew my apps to 214k users and $106,140 revenue. 54 | - built apps for 10 Toptal clients. 55 | 56 | And now I run a 6-figure devs agency where we launch MVPs in 4 weeks using Flutter & Firebase. 57 | 58 | You can find me on: 59 | 60 | - [LinkedIn](https://www.linkedin.com/in/milos-jokic/) 61 | - [Twitter](https://twitter.com/miloshjokic) 62 | - [Newsletter](http://flutterpreneur.com/) 63 | 64 | # Features 65 | 66 | | Feature | Starter | Pro | 67 | |----------------------------------------------------------------------|---------|-----| 68 | | Platforms: Android, iOS, web | ✅ | ✅ | 69 | | Scalable Architecture using [flutter_bloc](https://bloclibrary.dev/) | ✅ | ✅ | 70 | | Navigation using [go_router](https://pub.dev/packages/go_router) | ✅ | ✅ | 71 | | Scalable App Styling | ✅ | ✅ | 72 | | GitHub Actions - code and formatting check | ✅ | ✅ | 73 | | Responsive Design | ✅ | ✅ | 74 | | UserMockProvider | ✅ | ✅ | 75 | | Profile Page | ✅ | ✅ | 76 | | Settings Page: sign out, app version... | ✅ | ✅ | 77 | | Legal: Terms, Policy, Data Deletion | ✅ | ✅ | 78 | | Google Fonts | ✅ | ✅ | 79 | | Sign in and Sign Up Pages | ✅ | ✅ | 80 | | Input Validators | ✅ | ✅ | 81 | | Cached network image | ✅ | ✅ | 82 | | Email Support | ❌ | ✅ | 83 | | Lifetime Updates | ❌ | ✅ | 84 | | Firebase Project Integration | ❌ | ✅ | 85 | | Firebase Authentication | ❌ | ✅ | 86 | | Firebase Remote Config | ❌ | ✅ | 87 | | Firebase Crashlytics | ❌ | ✅ | 88 | | Firebase Firestore | ❌ | ✅ | 89 | | Firebase Analytics | ❌ | ✅ | 90 | | Firebase Storage | ❌ | ✅ | 91 | | Google Sign In | ❌ | ✅ | 92 | | Apple Sign In | ❌ | ✅ | 93 | | Common Cubits | ❌ | ✅ | 94 | | In App Purchases (RevenueCat) | ❌ | ✅ | 95 | | App Store Review Request | ❌ | ✅ | 96 | | Local Notifications | ❌ | ✅ | 97 | | Remote Notifications (Firebase) | ❌ | ✅ | 98 | | HTTP Requests | ❌ | ✅ | 99 | | Local Storage | ❌ | ✅ | 100 | | Permissions | ❌ | ✅ | 101 | | Environments | ❌ | ✅ | 102 | | Localization | ❌ | ✅ | 103 | | Dark Mode | ❌ | ✅ | 104 | | Connectivity check | ❌ | ✅ | 105 | | Hive - local database | ❌ | ✅ | 106 | | MixPanel - analytics | ❌ | ✅ | 107 | | Android - Release Signing | ❌ | ✅ | 108 | | Onboarding | ❌ | ✅ | 109 | | Forgot Password | ❌ | ✅ | 110 | | 🎉 BONUS: Scalable Firebase Backend Template | ❌ | ✅ | 111 | 112 | ## Scalable Flutter App Pro 113 | 114 | [Grab Scalable Flutter App - Pro Here](https://scalableflutterapp.com/) 115 | 116 | # Docs 117 | 118 | ## Code Architecture 119 | 120 | The code architecture is based on 121 | [flutter_bloc architecture proposal](https://bloclibrary.dev/#/architecture). 122 | 123 | There are 4 layers: 124 | 125 | 1. UI (Flutter Widgets) 126 | 2. BLoC (stateful business logic) 127 | 3. Repository (high-level API) 128 | 4. Provider (low-level implementation) 129 | 130 | And there's only 1 communication rule that we must follow: 131 | 132 | _**The layer can only call the one layer below it.**_ 133 | 134 | That means that: 135 | 136 | - UI can only call BLoC 137 | - BLoC can only call Repository 138 | - Repository can only call Provider 139 | - Provider can only call external services (Firebase, HTTP, etc.) 140 | 141 | And we avoid same-layer communication (as it creates interdependencies): 142 | 143 | - `UserRepository` calling `AuthRepository` is _**not**_ allowed. 144 | - `UserCubit` calling `UserRepository` and `AuthRepository` is allowed. 145 | 146 | When creating Providers, Repositories, and Cubits we follow this rule: 147 | 148 | - Providers are created top-level (so that they can be used in multiple Repositories) 149 | - Repositories are created top-level (so that they can be used in multiple Cubits) 150 | - Cubits are created in the router builder callbacks (so that they're accessible only where needed) 151 | - Cubits that are used in multiple screens are created top-level 152 | 153 | ## Styling 154 | 155 | Styling is based on [Google's Material Design](https://material.io/design). 156 | 157 | App-wide styling is defined in `core/app/style.dart` and is easy to update. 158 | 159 | Here's a quick tip on custom Widget params. There are 2 Widget param types: 160 | 161 | - data (user, title, ...) 162 | - style (colors, paddings, ...) 163 | 164 | Our custom Widgets should only hava data params. 165 | 166 | And the style should be done app-wide (in `style.dart`). 167 | 168 | That way all of our UI is consistent and easy to update. 169 | 170 | ## Google Fonts 171 | 172 | To change the font: 173 | 174 | 1. Go to [Google Fonts](https://fonts.google.com/) and select a font. 175 | 2. Download the font files. 176 | 3. Add the font files to `assets/fonts` (remove the old ones). 177 | 4. Update `style.dart` with the new font (i.e. `return GoogleFonts.rubikTextTheme(textTheme)`). 178 | 179 | ## Useful GitHub Pull Request Settings 180 | 181 | I've found that turning on these 2 settings in GitHub repo settings helps a lot: 182 | 183 | 1. `Always suggest updating pull request branches` 184 | 2. `Automatically delete head branches` 185 | 186 | # FAQ 187 | 188 | ## Why bloc and not X? 189 | 190 | While GetX, Provider, Riverpod, MobX, Redux, etc. are all great solutions, 191 | most of them are too forgiving. They allow us to access and change state globally. 192 | 193 | Whereas [flutter_bloc](https://bloclibrary.dev/) forces us to have `BuildContext` 194 | in order to access and change the state. The stricter the rules, the harder it is to make mistakes. 195 | 196 | And flutter_bloc has a great [architecture proposal](https://bloclibrary.dev/#/architecture) that 197 | scales well. 198 | 199 | ## Who is Scalable Flutter App for? 200 | 201 | Scalable Flutter App is for developers, agencies, and founders who want to: 202 | 203 | - build scalable Flutter apps 204 | - save weeks of development time 205 | - learn best practices 206 | 207 | ## Where to learn Flutter basics? 208 | 209 | I can only recommend what I've used myself: 210 | 211 | - [Flutter Codelabs](https://docs.flutter.dev/codelabs) 212 | - [Flutter YouTube](https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw) 213 | - [Effective Dart](https://dart.dev/guides/language/effective-dart) 214 | - and just keep building apps and getting better with each one :) 215 | 216 | ## What if I don't need a specific feature? 217 | 218 | If you don't need a feature: 219 | 220 | - delete its imported package in `pubspec.yaml` 221 | - delete the code that uses the feature 222 | 223 | ## What if I want more? 224 | 225 | If you want Firebase integration, notifications, in-app purchases and more, 226 | [get the Pro version here](https://scalableflutterapp.com/): 227 | 228 | # Resources 229 | 230 | Build your app icon in minutes (free): [Icon Kitchen](https://icon.kitchen/) 231 | 232 | Cool illustrations that match your app's colors (free): [unDraw](https://undraw.co/illustrations) 233 | 234 | CI/CD for mobile apps (free & paid): [Codemagic](https://codemagic.io/) 235 | 236 | Want me to launch your MVP in 4 weeks (premium)? 237 | [Go to App Launch Program](https://applaunchprogram.com/) 238 | 239 | Need a Flutter Expert (paid)? [Go to Flutter Devs Board](https://flutterdevsboard.com/) 240 | 241 | # Feedback 242 | 243 | Found an issue or want to request a feature? Open 244 | an [issue](https://github.com/Gradoid/scalable_flutter_app_starter/issues) 245 | 246 | Have a question? Ask me on [LinkedIn](https://www.linkedin.com/in/milos-jokic/) 247 | or [Twitter](https://twitter.com/miloshjokic). 248 | 249 | Enjoying Scalable Flutter App? [Leave a testimonial](https://testimonial.to/scalable-flutter-app) 250 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you find a security vulnerability please send me an email 6 | to milos@gradoid.com 7 | 8 | Don't disclose the details anywhere else. 9 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - "lib/l10n/**" 6 | - "lib/firebase_options.dart" 7 | language: 8 | strict-casts: true 9 | strict-inference: true 10 | strict-raw-types: true 11 | errors: 12 | missing_return: error 13 | 14 | linter: 15 | rules: 16 | - always_declare_return_types 17 | - always_put_control_body_on_new_line 18 | - always_put_required_named_parameters_first 19 | - always_require_non_null_named_parameters 20 | # - always_specify_types 21 | - always_use_package_imports 22 | - annotate_overrides 23 | # - avoid_annotating_with_dynamic 24 | - avoid_as 25 | - avoid_bool_literals_in_conditional_expressions 26 | # - avoid_catches_without_on_clauses 27 | - avoid_catching_errors 28 | - avoid_classes_with_only_static_members 29 | - avoid_double_and_int_checks 30 | - avoid_dynamic_calls 31 | - avoid_empty_else 32 | - avoid_equals_and_hash_code_on_mutable_classes 33 | - avoid_escaping_inner_quotes 34 | - avoid_field_initializers_in_const_classes 35 | - avoid_final_parameters 36 | - avoid_function_literals_in_foreach_calls 37 | - avoid_implementing_value_types 38 | - avoid_init_to_null 39 | - avoid_js_rounded_ints 40 | - avoid_multiple_declarations_per_line 41 | - avoid_null_checks_in_equality_operators 42 | - avoid_positional_boolean_parameters 43 | - avoid_print 44 | - avoid_private_typedef_functions 45 | # - avoid_redundant_argument_values 46 | - avoid_relative_lib_imports 47 | - avoid_renaming_method_parameters 48 | - avoid_return_types_on_setters 49 | - avoid_returning_null 50 | - avoid_returning_null_for_future 51 | - avoid_returning_null_for_void 52 | - avoid_returning_this 53 | - avoid_setters_without_getters 54 | - avoid_shadowing_type_parameters 55 | - avoid_single_cascade_in_expression_statements 56 | - avoid_slow_async_io 57 | - avoid_type_to_string 58 | - avoid_types_as_parameter_names 59 | - avoid_types_on_closure_parameters 60 | - avoid_unnecessary_containers 61 | - avoid_unused_constructor_parameters 62 | - avoid_void_async 63 | - avoid_web_libraries_in_flutter 64 | - await_only_futures 65 | - camel_case_extensions 66 | - camel_case_types 67 | - cancel_subscriptions 68 | - cascade_invocations 69 | - cast_nullable_to_non_nullable 70 | - close_sinks 71 | - collection_methods_unrelated_type 72 | - combinators_ordering 73 | - comment_references 74 | - conditional_uri_does_not_exist 75 | - constant_identifier_names 76 | - control_flow_in_finally 77 | - curly_braces_in_flow_control_structures 78 | - dangling_library_doc_comments 79 | - depend_on_referenced_packages 80 | - deprecated_consistency 81 | - deprecated_member_use_from_same_package 82 | # - diagnostic_describe_all_properties 83 | - directives_ordering 84 | # - discarded_futures 85 | - do_not_use_environment 86 | - empty_catches 87 | - empty_constructor_bodies 88 | - empty_statements 89 | - enable_null_safety 90 | - eol_at_end_of_file 91 | - exhaustive_cases 92 | - file_names 93 | # - flutter_style_todos 94 | - hash_and_equals 95 | - implementation_imports 96 | - implicit_call_tearoffs 97 | - implicit_reopen 98 | - invalid_case_patterns 99 | - invariant_booleans 100 | - join_return_with_assignment 101 | - leading_newlines_in_multiline_strings 102 | - library_annotations 103 | - library_names 104 | - library_prefixes 105 | - library_private_types_in_public_api 106 | - lines_longer_than_80_chars 107 | - literal_only_boolean_expressions 108 | - matching_super_parameters 109 | - missing_whitespace_between_adjacent_strings 110 | - no_adjacent_strings_in_list 111 | - no_default_cases 112 | - no_duplicate_case_values 113 | - no_leading_underscores_for_library_prefixes 114 | - no_leading_underscores_for_local_identifiers 115 | - no_literal_bool_comparisons 116 | - no_logic_in_create_state 117 | # - no_runtimeType_toString 118 | - non_constant_identifier_names 119 | - noop_primitive_operations 120 | - null_check_on_nullable_type_parameter 121 | - null_closures 122 | - omit_local_variable_types 123 | - one_member_abstracts 124 | - only_throw_errors 125 | - overridden_fields 126 | - package_api_docs 127 | - package_names 128 | - package_prefixed_library_names 129 | # - parameter_assignments 130 | - prefer_adjacent_string_concatenation 131 | - prefer_asserts_in_initializer_lists 132 | - prefer_asserts_with_message 133 | - prefer_bool_in_asserts 134 | - prefer_collection_literals 135 | - prefer_conditional_assignment 136 | - prefer_const_constructors 137 | - prefer_const_constructors_in_immutables 138 | - prefer_const_declarations 139 | - prefer_const_literals_to_create_immutables 140 | - prefer_constructors_over_static_methods 141 | - prefer_contains 142 | # - prefer_double_quotes 143 | - prefer_equal_for_default_values 144 | # - prefer_expression_function_bodies 145 | - prefer_final_fields 146 | - prefer_final_in_for_each 147 | - prefer_final_locals 148 | # - prefer_final_parameters 149 | - prefer_for_elements_to_map_fromIterable 150 | - prefer_foreach 151 | - prefer_function_declarations_over_variables 152 | - prefer_generic_function_type_aliases 153 | - prefer_if_elements_to_conditional_expressions 154 | - prefer_if_null_operators 155 | - prefer_initializing_formals 156 | - prefer_inlined_adds 157 | - prefer_int_literals 158 | - prefer_interpolation_to_compose_strings 159 | - prefer_is_empty 160 | - prefer_is_not_empty 161 | - prefer_is_not_operator 162 | - prefer_iterable_whereType 163 | - prefer_mixin 164 | - prefer_null_aware_method_calls 165 | - prefer_null_aware_operators 166 | # - prefer_relative_imports 167 | - prefer_single_quotes 168 | - prefer_spread_collections 169 | - prefer_typing_uninitialized_variables 170 | - prefer_void_to_null 171 | - provide_deprecation_message 172 | # - public_member_api_docs 173 | - recursive_getters 174 | - require_trailing_commas 175 | - secure_pubspec_urls 176 | - sized_box_for_whitespace 177 | - sized_box_shrink_expand 178 | - slash_for_doc_comments 179 | - sort_child_properties_last 180 | - sort_constructors_first 181 | # - sort_pub_dependencies 182 | - sort_unnamed_constructors_first 183 | - super_goes_last 184 | - test_types_in_equals 185 | - throw_in_finally 186 | - tighten_type_of_initializing_formals 187 | - type_annotate_public_apis 188 | - type_init_formals 189 | - type_literal_in_constant_pattern 190 | - unawaited_futures 191 | - unnecessary_await_in_return 192 | - unnecessary_brace_in_string_interps 193 | # - unnecessary_breaks 194 | - unnecessary_const 195 | - unnecessary_constructor_name 196 | # - unnecessary_final 197 | - unnecessary_getters_setters 198 | - unnecessary_lambdas 199 | - unnecessary_late 200 | - unnecessary_library_directive 201 | - unnecessary_new 202 | - unnecessary_null_aware_assignments 203 | - unnecessary_null_aware_operator_on_extension_on_nullable 204 | # - unnecessary_null_checks 205 | - unnecessary_null_in_if_null_operators 206 | - unnecessary_nullable_for_final_variable_declarations 207 | - unnecessary_overrides 208 | - unnecessary_parenthesis 209 | - unnecessary_raw_strings 210 | - unnecessary_statements 211 | - unnecessary_string_escapes 212 | - unnecessary_string_interpolations 213 | - unnecessary_this 214 | - unnecessary_to_list_in_spreads 215 | - unreachable_from_main 216 | - unrelated_type_equality_checks 217 | - unsafe_html 218 | - use_build_context_synchronously 219 | - use_colored_box 220 | - use_decorated_box 221 | - use_enums 222 | - use_full_hex_values_for_flutter_colors 223 | - use_function_type_syntax_for_parameters 224 | - use_if_null_to_convert_nulls_to_bools 225 | - use_is_even_rather_than_modulo 226 | - use_key_in_widget_constructors 227 | - use_late_for_private_fields_and_variables 228 | - use_named_constants 229 | - use_raw_strings 230 | - use_rethrow_when_possible 231 | - use_setters_to_change_properties 232 | - use_string_buffers 233 | - use_string_in_part_of_directives 234 | - use_super_parameters 235 | - use_test_throws_matchers 236 | - use_to_and_as_if_applicable 237 | - valid_regexps 238 | - void_checks 239 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.gradoid.scalable_flutter_app_starter" 27 | compileSdkVersion flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | applicationId "com.gradoid.scalable_flutter_app_starter" 45 | minSdkVersion 21 46 | targetSdkVersion flutter.targetSdkVersion 47 | versionCode flutterVersionCode.toInteger() 48 | versionName flutterVersionName 49 | } 50 | 51 | buildTypes { 52 | release { 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies {} 63 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/gradoid/scalable_flutter_app_starter/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gradoid.scalable_flutter_app_starter 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | plugins { 14 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 15 | } 16 | } 17 | 18 | include ":app" 19 | 20 | apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" 21 | -------------------------------------------------------------------------------- /assets/fonts/Rubik-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-BlackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-ExtraBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-Light.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-LightItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Rubik-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/assets/fonts/Rubik-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - package_info_plus (0.4.5): 7 | - Flutter 8 | - path_provider_foundation (0.0.1): 9 | - Flutter 10 | - FlutterMacOS 11 | - sqflite (0.0.3): 12 | - Flutter 13 | - FMDB (>= 2.7.5) 14 | - url_launcher_ios (0.0.1): 15 | - Flutter 16 | 17 | DEPENDENCIES: 18 | - Flutter (from `Flutter`) 19 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 20 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 21 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 22 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 23 | 24 | SPEC REPOS: 25 | trunk: 26 | - FMDB 27 | 28 | EXTERNAL SOURCES: 29 | Flutter: 30 | :path: Flutter 31 | package_info_plus: 32 | :path: ".symlinks/plugins/package_info_plus/ios" 33 | path_provider_foundation: 34 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 35 | sqflite: 36 | :path: ".symlinks/plugins/sqflite/ios" 37 | url_launcher_ios: 38 | :path: ".symlinks/plugins/url_launcher_ios/ios" 39 | 40 | SPEC CHECKSUMS: 41 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 42 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 43 | package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 44 | path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 45 | sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a 46 | url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 47 | 48 | PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 49 | 50 | COCOAPODS: 1.13.0 51 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 630B5AC7FF1B956DF0E30834 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A8D7BFA751DC0BA4B969C72 /* Pods_RunnerTests.framework */; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 9199938A0624F52767B1C08E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F80489C416A31DDD0914ADCD /* Pods_Runner.framework */; }; 16 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 17 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 18 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 27 | remoteInfo = Runner; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXCopyFilesBuildPhase section */ 32 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 33 | isa = PBXCopyFilesBuildPhase; 34 | buildActionMask = 2147483647; 35 | dstPath = ""; 36 | dstSubfolderSpec = 10; 37 | files = ( 38 | ); 39 | name = "Embed Frameworks"; 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXCopyFilesBuildPhase section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 46 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 47 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 48 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 3A8D7BFA751DC0BA4B969C72 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 51 | 7171565D52CA0E671F33BB43 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 52 | 72690D8812F57253BFD77E15 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 53 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 54 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 55 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 56 | 952C136FD3F7701496E261AC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 57 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 58 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 59 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 61 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 98B2241F46233771B85D48AA /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 65 | B3FF5ECD8B5ACDF4C9539A50 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 66 | E9BBAA757F171E3F7F3538B5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 67 | F80489C416A31DDD0914ADCD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 7A4676E1EE146A0B57ED05FD /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 630B5AC7FF1B956DF0E30834 /* Pods_RunnerTests.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | 9199938A0624F52767B1C08E /* Pods_Runner.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | 05339F912ED14820E0A80B4B /* Frameworks */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | F80489C416A31DDD0914ADCD /* Pods_Runner.framework */, 94 | 3A8D7BFA751DC0BA4B969C72 /* Pods_RunnerTests.framework */, 95 | ); 96 | name = Frameworks; 97 | sourceTree = ""; 98 | }; 99 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 103 | ); 104 | path = RunnerTests; 105 | sourceTree = ""; 106 | }; 107 | 9740EEB11CF90186004384FC /* Flutter */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 111 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 112 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 113 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 114 | ); 115 | name = Flutter; 116 | sourceTree = ""; 117 | }; 118 | 97C146E51CF9000F007C117D = { 119 | isa = PBXGroup; 120 | children = ( 121 | 9740EEB11CF90186004384FC /* Flutter */, 122 | 97C146F01CF9000F007C117D /* Runner */, 123 | 97C146EF1CF9000F007C117D /* Products */, 124 | 331C8082294A63A400263BE5 /* RunnerTests */, 125 | D179142475C7BECD4717B280 /* Pods */, 126 | 05339F912ED14820E0A80B4B /* Frameworks */, 127 | ); 128 | sourceTree = ""; 129 | }; 130 | 97C146EF1CF9000F007C117D /* Products */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 97C146EE1CF9000F007C117D /* Runner.app */, 134 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 135 | ); 136 | name = Products; 137 | sourceTree = ""; 138 | }; 139 | 97C146F01CF9000F007C117D /* Runner */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 143 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 144 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 145 | 97C147021CF9000F007C117D /* Info.plist */, 146 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 147 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 148 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 149 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 150 | ); 151 | path = Runner; 152 | sourceTree = ""; 153 | }; 154 | D179142475C7BECD4717B280 /* Pods */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | E9BBAA757F171E3F7F3538B5 /* Pods-Runner.debug.xcconfig */, 158 | 7171565D52CA0E671F33BB43 /* Pods-Runner.release.xcconfig */, 159 | B3FF5ECD8B5ACDF4C9539A50 /* Pods-Runner.profile.xcconfig */, 160 | 952C136FD3F7701496E261AC /* Pods-RunnerTests.debug.xcconfig */, 161 | 98B2241F46233771B85D48AA /* Pods-RunnerTests.release.xcconfig */, 162 | 72690D8812F57253BFD77E15 /* Pods-RunnerTests.profile.xcconfig */, 163 | ); 164 | name = Pods; 165 | path = Pods; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXGroup section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 174 | buildPhases = ( 175 | CB16D5182305CE7F11E87CD0 /* [CP] Check Pods Manifest.lock */, 176 | 331C807D294A63A400263BE5 /* Sources */, 177 | 331C807F294A63A400263BE5 /* Resources */, 178 | 7A4676E1EE146A0B57ED05FD /* Frameworks */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 184 | ); 185 | name = RunnerTests; 186 | productName = RunnerTests; 187 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 188 | productType = "com.apple.product-type.bundle.unit-test"; 189 | }; 190 | 97C146ED1CF9000F007C117D /* Runner */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 193 | buildPhases = ( 194 | 7338F12878752B1DA80B12E4 /* [CP] Check Pods Manifest.lock */, 195 | 9740EEB61CF901F6004384FC /* Run Script */, 196 | 97C146EA1CF9000F007C117D /* Sources */, 197 | 97C146EB1CF9000F007C117D /* Frameworks */, 198 | 97C146EC1CF9000F007C117D /* Resources */, 199 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 200 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 201 | 5E02D5EEA2F4A4F13F19FF8E /* [CP] Embed Pods Frameworks */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | ); 207 | name = Runner; 208 | productName = Runner; 209 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 210 | productType = "com.apple.product-type.application"; 211 | }; 212 | /* End PBXNativeTarget section */ 213 | 214 | /* Begin PBXProject section */ 215 | 97C146E61CF9000F007C117D /* Project object */ = { 216 | isa = PBXProject; 217 | attributes = { 218 | BuildIndependentTargetsInParallel = YES; 219 | LastUpgradeCheck = 1430; 220 | ORGANIZATIONNAME = ""; 221 | TargetAttributes = { 222 | 331C8080294A63A400263BE5 = { 223 | CreatedOnToolsVersion = 14.0; 224 | TestTargetID = 97C146ED1CF9000F007C117D; 225 | }; 226 | 97C146ED1CF9000F007C117D = { 227 | CreatedOnToolsVersion = 7.3.1; 228 | LastSwiftMigration = 1100; 229 | }; 230 | }; 231 | }; 232 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 233 | compatibilityVersion = "Xcode 9.3"; 234 | developmentRegion = en; 235 | hasScannedForEncodings = 0; 236 | knownRegions = ( 237 | en, 238 | Base, 239 | ); 240 | mainGroup = 97C146E51CF9000F007C117D; 241 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 242 | projectDirPath = ""; 243 | projectRoot = ""; 244 | targets = ( 245 | 97C146ED1CF9000F007C117D /* Runner */, 246 | 331C8080294A63A400263BE5 /* RunnerTests */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | 331C807F294A63A400263BE5 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | 97C146EC1CF9000F007C117D /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 264 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 265 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 266 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | /* End PBXResourcesBuildPhase section */ 271 | 272 | /* Begin PBXShellScriptBuildPhase section */ 273 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 274 | isa = PBXShellScriptBuildPhase; 275 | alwaysOutOfDate = 1; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | ); 279 | inputPaths = ( 280 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 281 | ); 282 | name = "Thin Binary"; 283 | outputPaths = ( 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | shellPath = /bin/sh; 287 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 288 | }; 289 | 5E02D5EEA2F4A4F13F19FF8E /* [CP] Embed Pods Frameworks */ = { 290 | isa = PBXShellScriptBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | ); 294 | inputFileListPaths = ( 295 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 296 | ); 297 | name = "[CP] Embed Pods Frameworks"; 298 | outputFileListPaths = ( 299 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | shellPath = /bin/sh; 303 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 304 | showEnvVarsInLog = 0; 305 | }; 306 | 7338F12878752B1DA80B12E4 /* [CP] Check Pods Manifest.lock */ = { 307 | isa = PBXShellScriptBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | ); 311 | inputFileListPaths = ( 312 | ); 313 | inputPaths = ( 314 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 315 | "${PODS_ROOT}/Manifest.lock", 316 | ); 317 | name = "[CP] Check Pods Manifest.lock"; 318 | outputFileListPaths = ( 319 | ); 320 | outputPaths = ( 321 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | shellPath = /bin/sh; 325 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 326 | showEnvVarsInLog = 0; 327 | }; 328 | 9740EEB61CF901F6004384FC /* Run Script */ = { 329 | isa = PBXShellScriptBuildPhase; 330 | alwaysOutOfDate = 1; 331 | buildActionMask = 2147483647; 332 | files = ( 333 | ); 334 | inputPaths = ( 335 | ); 336 | name = "Run Script"; 337 | outputPaths = ( 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | shellPath = /bin/sh; 341 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 342 | }; 343 | CB16D5182305CE7F11E87CD0 /* [CP] Check Pods Manifest.lock */ = { 344 | isa = PBXShellScriptBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | ); 348 | inputFileListPaths = ( 349 | ); 350 | inputPaths = ( 351 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 352 | "${PODS_ROOT}/Manifest.lock", 353 | ); 354 | name = "[CP] Check Pods Manifest.lock"; 355 | outputFileListPaths = ( 356 | ); 357 | outputPaths = ( 358 | "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", 359 | ); 360 | runOnlyForDeploymentPostprocessing = 0; 361 | shellPath = /bin/sh; 362 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 363 | showEnvVarsInLog = 0; 364 | }; 365 | /* End PBXShellScriptBuildPhase section */ 366 | 367 | /* Begin PBXSourcesBuildPhase section */ 368 | 331C807D294A63A400263BE5 /* Sources */ = { 369 | isa = PBXSourcesBuildPhase; 370 | buildActionMask = 2147483647; 371 | files = ( 372 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 373 | ); 374 | runOnlyForDeploymentPostprocessing = 0; 375 | }; 376 | 97C146EA1CF9000F007C117D /* Sources */ = { 377 | isa = PBXSourcesBuildPhase; 378 | buildActionMask = 2147483647; 379 | files = ( 380 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 381 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | /* End PBXSourcesBuildPhase section */ 386 | 387 | /* Begin PBXTargetDependency section */ 388 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 389 | isa = PBXTargetDependency; 390 | target = 97C146ED1CF9000F007C117D /* Runner */; 391 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 392 | }; 393 | /* End PBXTargetDependency section */ 394 | 395 | /* Begin PBXVariantGroup section */ 396 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 397 | isa = PBXVariantGroup; 398 | children = ( 399 | 97C146FB1CF9000F007C117D /* Base */, 400 | ); 401 | name = Main.storyboard; 402 | sourceTree = ""; 403 | }; 404 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 405 | isa = PBXVariantGroup; 406 | children = ( 407 | 97C147001CF9000F007C117D /* Base */, 408 | ); 409 | name = LaunchScreen.storyboard; 410 | sourceTree = ""; 411 | }; 412 | /* End PBXVariantGroup section */ 413 | 414 | /* Begin XCBuildConfiguration section */ 415 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | ALWAYS_SEARCH_USER_PATHS = NO; 419 | CLANG_ANALYZER_NONNULL = YES; 420 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 421 | CLANG_CXX_LIBRARY = "libc++"; 422 | CLANG_ENABLE_MODULES = YES; 423 | CLANG_ENABLE_OBJC_ARC = YES; 424 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 425 | CLANG_WARN_BOOL_CONVERSION = YES; 426 | CLANG_WARN_COMMA = YES; 427 | CLANG_WARN_CONSTANT_CONVERSION = YES; 428 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 429 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 430 | CLANG_WARN_EMPTY_BODY = YES; 431 | CLANG_WARN_ENUM_CONVERSION = YES; 432 | CLANG_WARN_INFINITE_RECURSION = YES; 433 | CLANG_WARN_INT_CONVERSION = YES; 434 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 436 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 437 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 438 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 439 | CLANG_WARN_STRICT_PROTOTYPES = YES; 440 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 441 | CLANG_WARN_UNREACHABLE_CODE = YES; 442 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 443 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 444 | COPY_PHASE_STRIP = NO; 445 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 446 | ENABLE_NS_ASSERTIONS = NO; 447 | ENABLE_STRICT_OBJC_MSGSEND = YES; 448 | GCC_C_LANGUAGE_STANDARD = gnu99; 449 | GCC_NO_COMMON_BLOCKS = YES; 450 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 451 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 452 | GCC_WARN_UNDECLARED_SELECTOR = YES; 453 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 454 | GCC_WARN_UNUSED_FUNCTION = YES; 455 | GCC_WARN_UNUSED_VARIABLE = YES; 456 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 457 | MTL_ENABLE_DEBUG_INFO = NO; 458 | SDKROOT = iphoneos; 459 | SUPPORTED_PLATFORMS = iphoneos; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | VALIDATE_PRODUCT = YES; 462 | }; 463 | name = Profile; 464 | }; 465 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 466 | isa = XCBuildConfiguration; 467 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 468 | buildSettings = { 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | CLANG_ENABLE_MODULES = YES; 471 | CODE_SIGN_IDENTITY = "Apple Development"; 472 | CODE_SIGN_STYLE = Automatic; 473 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 474 | DEVELOPMENT_TEAM = G8XYTCFNVW; 475 | ENABLE_BITCODE = NO; 476 | INFOPLIST_FILE = Runner/Info.plist; 477 | LD_RUNPATH_SEARCH_PATHS = ( 478 | "$(inherited)", 479 | "@executable_path/Frameworks", 480 | ); 481 | PRODUCT_BUNDLE_IDENTIFIER = com.gradoid.scalableFlutterAppStarter; 482 | PRODUCT_NAME = "$(TARGET_NAME)"; 483 | PROVISIONING_PROFILE_SPECIFIER = ""; 484 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 485 | SWIFT_VERSION = 5.0; 486 | VERSIONING_SYSTEM = "apple-generic"; 487 | }; 488 | name = Profile; 489 | }; 490 | 331C8088294A63A400263BE5 /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | baseConfigurationReference = 952C136FD3F7701496E261AC /* Pods-RunnerTests.debug.xcconfig */; 493 | buildSettings = { 494 | BUNDLE_LOADER = "$(TEST_HOST)"; 495 | CODE_SIGN_STYLE = Automatic; 496 | CURRENT_PROJECT_VERSION = 1; 497 | GENERATE_INFOPLIST_FILE = YES; 498 | MARKETING_VERSION = 1.0; 499 | PRODUCT_BUNDLE_IDENTIFIER = com.gradoid.scalableFlutterAppStarter.RunnerTests; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 502 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 503 | SWIFT_VERSION = 5.0; 504 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 505 | }; 506 | name = Debug; 507 | }; 508 | 331C8089294A63A400263BE5 /* Release */ = { 509 | isa = XCBuildConfiguration; 510 | baseConfigurationReference = 98B2241F46233771B85D48AA /* Pods-RunnerTests.release.xcconfig */; 511 | buildSettings = { 512 | BUNDLE_LOADER = "$(TEST_HOST)"; 513 | CODE_SIGN_STYLE = Automatic; 514 | CURRENT_PROJECT_VERSION = 1; 515 | GENERATE_INFOPLIST_FILE = YES; 516 | MARKETING_VERSION = 1.0; 517 | PRODUCT_BUNDLE_IDENTIFIER = com.gradoid.scalableFlutterAppStarter.RunnerTests; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SWIFT_VERSION = 5.0; 520 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 521 | }; 522 | name = Release; 523 | }; 524 | 331C808A294A63A400263BE5 /* Profile */ = { 525 | isa = XCBuildConfiguration; 526 | baseConfigurationReference = 72690D8812F57253BFD77E15 /* Pods-RunnerTests.profile.xcconfig */; 527 | buildSettings = { 528 | BUNDLE_LOADER = "$(TEST_HOST)"; 529 | CODE_SIGN_STYLE = Automatic; 530 | CURRENT_PROJECT_VERSION = 1; 531 | GENERATE_INFOPLIST_FILE = YES; 532 | MARKETING_VERSION = 1.0; 533 | PRODUCT_BUNDLE_IDENTIFIER = com.gradoid.scalableFlutterAppStarter.RunnerTests; 534 | PRODUCT_NAME = "$(TARGET_NAME)"; 535 | SWIFT_VERSION = 5.0; 536 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 537 | }; 538 | name = Profile; 539 | }; 540 | 97C147031CF9000F007C117D /* Debug */ = { 541 | isa = XCBuildConfiguration; 542 | buildSettings = { 543 | ALWAYS_SEARCH_USER_PATHS = NO; 544 | CLANG_ANALYZER_NONNULL = YES; 545 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 546 | CLANG_CXX_LIBRARY = "libc++"; 547 | CLANG_ENABLE_MODULES = YES; 548 | CLANG_ENABLE_OBJC_ARC = YES; 549 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 550 | CLANG_WARN_BOOL_CONVERSION = YES; 551 | CLANG_WARN_COMMA = YES; 552 | CLANG_WARN_CONSTANT_CONVERSION = YES; 553 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 554 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 555 | CLANG_WARN_EMPTY_BODY = YES; 556 | CLANG_WARN_ENUM_CONVERSION = YES; 557 | CLANG_WARN_INFINITE_RECURSION = YES; 558 | CLANG_WARN_INT_CONVERSION = YES; 559 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 560 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 561 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 562 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 563 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 564 | CLANG_WARN_STRICT_PROTOTYPES = YES; 565 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 566 | CLANG_WARN_UNREACHABLE_CODE = YES; 567 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 568 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 569 | COPY_PHASE_STRIP = NO; 570 | DEBUG_INFORMATION_FORMAT = dwarf; 571 | ENABLE_STRICT_OBJC_MSGSEND = YES; 572 | ENABLE_TESTABILITY = YES; 573 | GCC_C_LANGUAGE_STANDARD = gnu99; 574 | GCC_DYNAMIC_NO_PIC = NO; 575 | GCC_NO_COMMON_BLOCKS = YES; 576 | GCC_OPTIMIZATION_LEVEL = 0; 577 | GCC_PREPROCESSOR_DEFINITIONS = ( 578 | "DEBUG=1", 579 | "$(inherited)", 580 | ); 581 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 582 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 583 | GCC_WARN_UNDECLARED_SELECTOR = YES; 584 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 585 | GCC_WARN_UNUSED_FUNCTION = YES; 586 | GCC_WARN_UNUSED_VARIABLE = YES; 587 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 588 | MTL_ENABLE_DEBUG_INFO = YES; 589 | ONLY_ACTIVE_ARCH = YES; 590 | SDKROOT = iphoneos; 591 | TARGETED_DEVICE_FAMILY = "1,2"; 592 | }; 593 | name = Debug; 594 | }; 595 | 97C147041CF9000F007C117D /* Release */ = { 596 | isa = XCBuildConfiguration; 597 | buildSettings = { 598 | ALWAYS_SEARCH_USER_PATHS = NO; 599 | CLANG_ANALYZER_NONNULL = YES; 600 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 601 | CLANG_CXX_LIBRARY = "libc++"; 602 | CLANG_ENABLE_MODULES = YES; 603 | CLANG_ENABLE_OBJC_ARC = YES; 604 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 605 | CLANG_WARN_BOOL_CONVERSION = YES; 606 | CLANG_WARN_COMMA = YES; 607 | CLANG_WARN_CONSTANT_CONVERSION = YES; 608 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 609 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 610 | CLANG_WARN_EMPTY_BODY = YES; 611 | CLANG_WARN_ENUM_CONVERSION = YES; 612 | CLANG_WARN_INFINITE_RECURSION = YES; 613 | CLANG_WARN_INT_CONVERSION = YES; 614 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 615 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 616 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 617 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 618 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 619 | CLANG_WARN_STRICT_PROTOTYPES = YES; 620 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 621 | CLANG_WARN_UNREACHABLE_CODE = YES; 622 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 623 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 624 | COPY_PHASE_STRIP = NO; 625 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 626 | ENABLE_NS_ASSERTIONS = NO; 627 | ENABLE_STRICT_OBJC_MSGSEND = YES; 628 | GCC_C_LANGUAGE_STANDARD = gnu99; 629 | GCC_NO_COMMON_BLOCKS = YES; 630 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 631 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 632 | GCC_WARN_UNDECLARED_SELECTOR = YES; 633 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 634 | GCC_WARN_UNUSED_FUNCTION = YES; 635 | GCC_WARN_UNUSED_VARIABLE = YES; 636 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 637 | MTL_ENABLE_DEBUG_INFO = NO; 638 | SDKROOT = iphoneos; 639 | SUPPORTED_PLATFORMS = iphoneos; 640 | SWIFT_COMPILATION_MODE = wholemodule; 641 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 642 | TARGETED_DEVICE_FAMILY = "1,2"; 643 | VALIDATE_PRODUCT = YES; 644 | }; 645 | name = Release; 646 | }; 647 | 97C147061CF9000F007C117D /* Debug */ = { 648 | isa = XCBuildConfiguration; 649 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 650 | buildSettings = { 651 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 652 | CLANG_ENABLE_MODULES = YES; 653 | CODE_SIGN_IDENTITY = "Apple Development"; 654 | CODE_SIGN_STYLE = Automatic; 655 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 656 | DEVELOPMENT_TEAM = G8XYTCFNVW; 657 | ENABLE_BITCODE = NO; 658 | INFOPLIST_FILE = Runner/Info.plist; 659 | LD_RUNPATH_SEARCH_PATHS = ( 660 | "$(inherited)", 661 | "@executable_path/Frameworks", 662 | ); 663 | PRODUCT_BUNDLE_IDENTIFIER = com.gradoid.scalableFlutterAppStarter; 664 | PRODUCT_NAME = "$(TARGET_NAME)"; 665 | PROVISIONING_PROFILE_SPECIFIER = ""; 666 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 667 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 668 | SWIFT_VERSION = 5.0; 669 | VERSIONING_SYSTEM = "apple-generic"; 670 | }; 671 | name = Debug; 672 | }; 673 | 97C147071CF9000F007C117D /* Release */ = { 674 | isa = XCBuildConfiguration; 675 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 676 | buildSettings = { 677 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 678 | CLANG_ENABLE_MODULES = YES; 679 | CODE_SIGN_IDENTITY = "Apple Development"; 680 | CODE_SIGN_STYLE = Automatic; 681 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 682 | DEVELOPMENT_TEAM = G8XYTCFNVW; 683 | ENABLE_BITCODE = NO; 684 | INFOPLIST_FILE = Runner/Info.plist; 685 | LD_RUNPATH_SEARCH_PATHS = ( 686 | "$(inherited)", 687 | "@executable_path/Frameworks", 688 | ); 689 | PRODUCT_BUNDLE_IDENTIFIER = com.gradoid.scalableFlutterAppStarter; 690 | PRODUCT_NAME = "$(TARGET_NAME)"; 691 | PROVISIONING_PROFILE_SPECIFIER = ""; 692 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 693 | SWIFT_VERSION = 5.0; 694 | VERSIONING_SYSTEM = "apple-generic"; 695 | }; 696 | name = Release; 697 | }; 698 | /* End XCBuildConfiguration section */ 699 | 700 | /* Begin XCConfigurationList section */ 701 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 702 | isa = XCConfigurationList; 703 | buildConfigurations = ( 704 | 331C8088294A63A400263BE5 /* Debug */, 705 | 331C8089294A63A400263BE5 /* Release */, 706 | 331C808A294A63A400263BE5 /* Profile */, 707 | ); 708 | defaultConfigurationIsVisible = 0; 709 | defaultConfigurationName = Release; 710 | }; 711 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 712 | isa = XCConfigurationList; 713 | buildConfigurations = ( 714 | 97C147031CF9000F007C117D /* Debug */, 715 | 97C147041CF9000F007C117D /* Release */, 716 | 249021D3217E4FDB00AE95B9 /* Profile */, 717 | ); 718 | defaultConfigurationIsVisible = 0; 719 | defaultConfigurationName = Release; 720 | }; 721 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 722 | isa = XCConfigurationList; 723 | buildConfigurations = ( 724 | 97C147061CF9000F007C117D /* Debug */, 725 | 97C147071CF9000F007C117D /* Release */, 726 | 249021D4217E4FDB00AE95B9 /* Profile */, 727 | ); 728 | defaultConfigurationIsVisible = 0; 729 | defaultConfigurationName = Release; 730 | }; 731 | /* End XCConfigurationList section */ 732 | }; 733 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 734 | } 735 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Scalable Flutter App Starter 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | scalable_flutter_app_starter 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 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/core/app/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/app/di.dart'; 3 | import 'package:scalable_flutter_app_starter/core/app/style.dart'; 4 | import 'package:scalable_flutter_app_starter/core/navigation/router.dart'; 5 | 6 | class ScalableFlutterApp extends StatelessWidget { 7 | const ScalableFlutterApp({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return DI( 12 | child: MaterialApp.router( 13 | title: 'Scalable Flutter App Starter', 14 | theme: lightTheme, 15 | debugShowCheckedModeBanner: false, 16 | routerConfig: router, 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/core/app/di.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:scalable_flutter_app_starter/feature/auth/repository/auth_repository.dart'; 4 | import 'package:scalable_flutter_app_starter/feature/user/bloc/user_cubit.dart'; 5 | import 'package:scalable_flutter_app_starter/feature/user/provider/user_mock_provider.dart'; 6 | import 'package:scalable_flutter_app_starter/feature/user/repository/user_repository.dart'; 7 | 8 | class DI extends StatelessWidget { 9 | const DI({ 10 | required this.child, 11 | super.key, 12 | }); 13 | 14 | final Widget child; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return _ProviderDI( 19 | child: _RepositoryDI( 20 | child: _BlocDI( 21 | child: child, 22 | ), 23 | ), 24 | ); 25 | } 26 | } 27 | 28 | class _ProviderDI extends StatelessWidget { 29 | const _ProviderDI({required this.child}); 30 | 31 | final Widget child; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return MultiRepositoryProvider( 36 | providers: [ 37 | RepositoryProvider( 38 | create: (context) => UserMockProvider(), 39 | ), 40 | ], 41 | child: child, 42 | ); 43 | } 44 | } 45 | 46 | class _RepositoryDI extends StatelessWidget { 47 | const _RepositoryDI({required this.child}); 48 | 49 | final Widget child; 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return MultiRepositoryProvider( 54 | providers: [ 55 | RepositoryProvider( 56 | create: (context) => UserRepository( 57 | userProvider: context.read(), 58 | ), 59 | ), 60 | RepositoryProvider( 61 | create: (context) => AuthRepository( 62 | userProvider: context.read(), 63 | ), 64 | ), 65 | ], 66 | child: child, 67 | ); 68 | } 69 | } 70 | 71 | class _BlocDI extends StatelessWidget { 72 | const _BlocDI({required this.child}); 73 | 74 | final Widget child; 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return MultiBlocProvider( 79 | providers: [ 80 | BlocProvider( 81 | create: (context) => UserCubit( 82 | userRepository: context.read(), 83 | ), 84 | ), 85 | ], 86 | child: child, 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/core/app/style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | 4 | final lightTheme = _getTheme(); 5 | 6 | const _primary = Colors.indigo; 7 | const _secondary = Colors.amber; 8 | 9 | const _background = Color(0xFFF5F5F5); 10 | const _lightest = Colors.white; 11 | const _darkest = Colors.black; 12 | const _darker = Colors.black87; 13 | const _divider = Colors.grey; 14 | const _disabled = Colors.grey; 15 | 16 | const _red = Colors.red; 17 | 18 | final _lightColorScheme = ColorScheme( 19 | brightness: Brightness.light, 20 | // Primary 21 | primary: _primary, 22 | onPrimary: _lightest, 23 | primaryContainer: _primary.withOpacity(0.2), 24 | onPrimaryContainer: _lightest, 25 | // Secondary 26 | secondary: _secondary, 27 | onSecondary: _darkest, 28 | secondaryContainer: _secondary.withOpacity(0.2), 29 | onSecondaryContainer: _darkest, 30 | // Error 31 | error: _red, 32 | onError: _lightest, 33 | // Background 34 | background: _background, 35 | onBackground: _darkest, 36 | // Surface 37 | surface: _lightest, 38 | onSurface: _darkest, 39 | // Outline 40 | outline: _divider, 41 | ); 42 | 43 | ThemeData _getTheme() { 44 | final colorScheme = _lightColorScheme; 45 | final textTheme = _getTextTheme(colorScheme); 46 | final primaryTextTheme = textTheme.apply( 47 | displayColor: colorScheme.onPrimary, 48 | bodyColor: colorScheme.onPrimary, 49 | ); 50 | 51 | final buttonShape = RoundedRectangleBorder( 52 | borderRadius: BorderRadius.circular(16), 53 | ); 54 | const buttonPadding = EdgeInsets.symmetric( 55 | horizontal: 24, 56 | vertical: 12, 57 | ); 58 | final buttonTextStyle = textTheme.titleMedium; 59 | 60 | return ThemeData( 61 | useMaterial3: true, 62 | brightness: Brightness.light, 63 | colorScheme: colorScheme, 64 | textTheme: textTheme, 65 | primaryTextTheme: primaryTextTheme, 66 | scaffoldBackgroundColor: colorScheme.background, 67 | disabledColor: _disabled, 68 | dividerTheme: const DividerThemeData( 69 | color: _divider, 70 | space: 1, 71 | thickness: 1, 72 | ), 73 | chipTheme: ChipThemeData( 74 | labelStyle: textTheme.labelSmall, 75 | side: const BorderSide( 76 | width: 0, 77 | ), 78 | ), 79 | cardTheme: const CardTheme( 80 | clipBehavior: Clip.antiAlias, 81 | elevation: 0, 82 | shape: RoundedRectangleBorder( 83 | borderRadius: BorderRadius.all(Radius.circular(6)), 84 | side: BorderSide( 85 | width: 1, 86 | color: _divider, 87 | ), 88 | ), 89 | color: _background, 90 | surfaceTintColor: Colors.transparent, 91 | margin: EdgeInsets.zero, 92 | ), 93 | popupMenuTheme: PopupMenuThemeData( 94 | color: _background, 95 | surfaceTintColor: colorScheme.background, 96 | ), 97 | bottomSheetTheme: const BottomSheetThemeData( 98 | showDragHandle: false, 99 | backgroundColor: _background, 100 | surfaceTintColor: Colors.transparent, 101 | shape: RoundedRectangleBorder( 102 | borderRadius: BorderRadius.vertical( 103 | top: Radius.circular(20), 104 | ), 105 | ), 106 | ), 107 | bottomNavigationBarTheme: const BottomNavigationBarThemeData( 108 | type: BottomNavigationBarType.fixed, 109 | showSelectedLabels: true, 110 | showUnselectedLabels: true, 111 | ), 112 | navigationRailTheme: const NavigationRailThemeData( 113 | labelType: NavigationRailLabelType.all, 114 | groupAlignment: 0, 115 | ), 116 | appBarTheme: AppBarTheme( 117 | titleTextStyle: textTheme.titleLarge, 118 | backgroundColor: _background, 119 | ), 120 | dialogTheme: DialogTheme( 121 | backgroundColor: colorScheme.background, 122 | surfaceTintColor: colorScheme.background, 123 | titleTextStyle: textTheme.titleLarge, 124 | ), 125 | snackBarTheme: SnackBarThemeData( 126 | behavior: SnackBarBehavior.floating, 127 | backgroundColor: _darkest, 128 | contentTextStyle: primaryTextTheme.bodyLarge, 129 | shape: const RoundedRectangleBorder( 130 | borderRadius: BorderRadius.all(Radius.circular(8)), 131 | ), 132 | ), 133 | listTileTheme: ListTileThemeData( 134 | iconColor: colorScheme.primary, 135 | ), 136 | inputDecorationTheme: InputDecorationTheme( 137 | filled: true, 138 | floatingLabelBehavior: FloatingLabelBehavior.auto, 139 | contentPadding: const EdgeInsets.symmetric( 140 | horizontal: 24, 141 | vertical: 16, 142 | ), 143 | border: const OutlineInputBorder( 144 | borderRadius: BorderRadius.all(Radius.circular(8)), 145 | borderSide: BorderSide.none, 146 | ), 147 | hintStyle: textTheme.bodyLarge, 148 | labelStyle: textTheme.bodyLarge!.copyWith( 149 | color: Colors.black38, 150 | fontWeight: FontWeight.normal, 151 | ), 152 | ), 153 | floatingActionButtonTheme: FloatingActionButtonThemeData( 154 | backgroundColor: colorScheme.secondary, 155 | foregroundColor: Colors.white, 156 | iconSize: 24, 157 | shape: RoundedRectangleBorder( 158 | borderRadius: BorderRadius.circular(60), 159 | ), 160 | ), 161 | elevatedButtonTheme: ElevatedButtonThemeData( 162 | style: ElevatedButton.styleFrom( 163 | shape: buttonShape, 164 | padding: buttonPadding, 165 | backgroundColor: colorScheme.primary, 166 | foregroundColor: colorScheme.onPrimary, 167 | textStyle: buttonTextStyle, 168 | elevation: 2, 169 | ), 170 | ), 171 | outlinedButtonTheme: OutlinedButtonThemeData( 172 | style: OutlinedButton.styleFrom( 173 | shape: buttonShape, 174 | padding: buttonPadding, 175 | side: BorderSide( 176 | color: colorScheme.primary, 177 | width: 1, 178 | ), 179 | foregroundColor: colorScheme.primary, 180 | textStyle: buttonTextStyle, 181 | elevation: 0, 182 | ), 183 | ), 184 | textButtonTheme: TextButtonThemeData( 185 | style: TextButton.styleFrom( 186 | shape: buttonShape, 187 | padding: buttonPadding, 188 | foregroundColor: colorScheme.primary, 189 | textStyle: buttonTextStyle, 190 | ), 191 | ), 192 | ); 193 | } 194 | 195 | TextTheme _getTextTheme(ColorScheme colorScheme) { 196 | const headlineColor = _darker; 197 | const headlineWeight = FontWeight.w900; 198 | const headlineHeight = 1.2; 199 | const headlineLetterSpacing = 2.5; 200 | 201 | const titleColor = _darkest; 202 | const titleWeight = FontWeight.bold; 203 | const titleHeight = 1.2; 204 | const titleLetterSpacing = -0.96; 205 | 206 | const bodyColor = _darker; 207 | const bodyWeight = FontWeight.normal; 208 | const bodyHeight = 1.5; 209 | const bodyLetterSpacing = 0.0; 210 | 211 | const labelColor = titleColor; 212 | 213 | const textTheme = TextTheme( 214 | // Headline 215 | headlineLarge: TextStyle( 216 | fontSize: 24, 217 | height: headlineHeight, 218 | letterSpacing: headlineLetterSpacing, 219 | color: headlineColor, 220 | fontWeight: headlineWeight, 221 | ), 222 | headlineMedium: TextStyle( 223 | fontSize: 20, 224 | height: headlineHeight, 225 | letterSpacing: headlineLetterSpacing, 226 | color: headlineColor, 227 | fontWeight: headlineWeight, 228 | ), 229 | headlineSmall: TextStyle( 230 | fontSize: 18, 231 | height: headlineHeight, 232 | letterSpacing: headlineLetterSpacing, 233 | color: headlineColor, 234 | fontWeight: headlineWeight, 235 | ), 236 | 237 | // Title 238 | titleLarge: TextStyle( 239 | fontSize: 20, 240 | height: titleHeight, 241 | letterSpacing: titleLetterSpacing, 242 | color: titleColor, 243 | fontWeight: titleWeight, 244 | ), 245 | titleMedium: TextStyle( 246 | fontSize: 18, 247 | height: titleHeight, 248 | letterSpacing: titleLetterSpacing, 249 | color: titleColor, 250 | fontWeight: titleWeight, 251 | ), 252 | titleSmall: TextStyle( 253 | fontSize: 14, 254 | height: titleHeight, 255 | letterSpacing: titleLetterSpacing, 256 | color: titleColor, 257 | fontWeight: titleWeight, 258 | ), 259 | 260 | // Body 261 | bodyLarge: TextStyle( 262 | fontSize: 16, 263 | height: bodyHeight, 264 | letterSpacing: bodyLetterSpacing, 265 | color: bodyColor, 266 | fontWeight: bodyWeight, 267 | ), 268 | bodyMedium: TextStyle( 269 | fontSize: 14, 270 | height: bodyHeight, 271 | letterSpacing: bodyLetterSpacing, 272 | color: bodyColor, 273 | fontWeight: bodyWeight, 274 | ), 275 | bodySmall: TextStyle( 276 | fontSize: 12, 277 | height: bodyHeight, 278 | color: bodyColor, 279 | fontWeight: bodyWeight, 280 | ), 281 | 282 | // Label 283 | labelLarge: TextStyle( 284 | fontSize: 16, 285 | height: bodyHeight, 286 | letterSpacing: bodyLetterSpacing, 287 | color: labelColor, 288 | fontWeight: bodyWeight, 289 | ), 290 | labelMedium: TextStyle( 291 | fontSize: 14, 292 | height: bodyHeight, 293 | letterSpacing: bodyLetterSpacing, 294 | color: labelColor, 295 | fontWeight: bodyWeight, 296 | ), 297 | labelSmall: TextStyle( 298 | fontSize: 12, 299 | height: bodyHeight, 300 | letterSpacing: bodyLetterSpacing, 301 | color: labelColor, 302 | fontWeight: bodyWeight, 303 | ), 304 | ); 305 | 306 | return GoogleFonts.rubikTextTheme(textTheme); 307 | } 308 | -------------------------------------------------------------------------------- /lib/core/extension/context.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/ui/widget/responsive.dart'; 3 | 4 | extension Context on BuildContext { 5 | TextTheme get textTheme => Theme.of(this).textTheme; 6 | 7 | TextTheme get primaryTextTheme => Theme.of(this).primaryTextTheme; 8 | 9 | ColorScheme get colorScheme => Theme.of(this).colorScheme; 10 | 11 | void closeKeyboard() => FocusScope.of(this).unfocus(); 12 | 13 | void showSnackBarMessage( 14 | String message, { 15 | bool isError = false, 16 | }) { 17 | final theme = Theme.of(this); 18 | final Color? foregroundColor; 19 | final Color? backgroundColor; 20 | if (isError) { 21 | foregroundColor = theme.colorScheme.onError; 22 | backgroundColor = theme.colorScheme.error; 23 | } else { 24 | foregroundColor = null; 25 | backgroundColor = null; 26 | } 27 | 28 | ScaffoldMessenger.of(this).showSnackBar( 29 | SnackBar( 30 | backgroundColor: backgroundColor, 31 | content: Text( 32 | message, 33 | style: TextStyle(color: foregroundColor), 34 | ), 35 | ), 36 | ); 37 | } 38 | 39 | bool get isWide { 40 | final maxWidth = MediaQuery.sizeOf(this).width; 41 | return maxWidth > desktopWidthBreakpoint; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/core/extension/context_user.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:scalable_flutter_app_starter/feature/user/bloc/user_cubit.dart'; 4 | import 'package:scalable_flutter_app_starter/feature/user/model/user.dart'; 5 | 6 | extension BuildContextUserExt on BuildContext { 7 | User? get watchCurrentUser { 8 | final userState = watch().state; 9 | if (userState is! UserLoaded) { 10 | return null; 11 | } 12 | 13 | return userState.user; 14 | } 15 | 16 | User? get getCurrentUser { 17 | final userState = read().state; 18 | if (userState is! UserLoaded) { 19 | return null; 20 | } 21 | 22 | return userState.user; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/core/extension/string.dart: -------------------------------------------------------------------------------- 1 | extension StringExtension on String { 2 | bool isValidEmail() { 3 | return RegExp( 4 | r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+", 5 | ).hasMatch(this); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/core/logger/logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:loggy/loggy.dart' as loggy; 2 | 3 | typedef LoggerFunction = void Function( 4 | String message, [ 5 | dynamic error, 6 | StackTrace? stackTrace, 7 | ]); 8 | 9 | final LoggerFunction logDebug = loggy.logDebug; 10 | final LoggerFunction logInfo = loggy.logInfo; 11 | final LoggerFunction logWarning = loggy.logWarning; 12 | final LoggerFunction logError = loggy.logError; 13 | -------------------------------------------------------------------------------- /lib/core/logger/loggy_types.dart: -------------------------------------------------------------------------------- 1 | import 'package:loggy/loggy.dart'; 2 | 3 | mixin ModelLoggy implements LoggyType { 4 | @override 5 | Loggy get loggy => Loggy('MODEL: $runtimeType'); 6 | } 7 | 8 | mixin UiLoggy implements LoggyType { 9 | @override 10 | Loggy get loggy => Loggy('UI: $runtimeType'); 11 | } 12 | 13 | mixin BlocLoggy implements LoggyType { 14 | @override 15 | Loggy get loggy => Loggy('BLOC: $runtimeType'); 16 | } 17 | 18 | mixin RepositoryLoggy implements LoggyType { 19 | @override 20 | Loggy get loggy => 21 | Loggy('REPOSITORY: $runtimeType'); 22 | } 23 | 24 | mixin ProviderLoggy implements LoggyType { 25 | @override 26 | Loggy get loggy => 27 | Loggy('PROVIDER: $runtimeType'); 28 | } 29 | -------------------------------------------------------------------------------- /lib/core/navigation/route.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | 4 | enum AppRoute { 5 | splash('/'), 6 | home('/home'), 7 | settings('/settings'), 8 | auth('/auth'), 9 | ; 10 | 11 | const AppRoute(this.path); 12 | 13 | final String path; 14 | } 15 | 16 | extension AppRouteNavigation on AppRoute { 17 | void go(BuildContext context) => context.go(path); 18 | 19 | void push(BuildContext context) => context.push(path); 20 | } 21 | -------------------------------------------------------------------------------- /lib/core/navigation/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'package:scalable_flutter_app_starter/core/navigation/route.dart'; 4 | import 'package:scalable_flutter_app_starter/feature/auth/bloc/auth_cubit.dart'; 5 | import 'package:scalable_flutter_app_starter/feature/auth/repository/auth_repository.dart'; 6 | import 'package:scalable_flutter_app_starter/feature/auth/ui/page/auth_page.dart'; 7 | import 'package:scalable_flutter_app_starter/feature/auth/ui/page/splash_page.dart'; 8 | import 'package:scalable_flutter_app_starter/feature/home/ui/page/home_page.dart'; 9 | import 'package:scalable_flutter_app_starter/feature/settings/ui/page/settings_page.dart'; 10 | import 'package:scalable_flutter_app_starter/feature/user/repository/user_repository.dart'; 11 | 12 | final GoRouter router = GoRouter( 13 | routes: [ 14 | GoRoute( 15 | path: AppRoute.splash.path, 16 | builder: (context, state) => const SplashPage(), 17 | ), 18 | GoRoute( 19 | path: AppRoute.home.path, 20 | builder: (context, state) => const HomePage(), 21 | ), 22 | GoRoute( 23 | path: AppRoute.settings.path, 24 | builder: (context, state) => const SettingsPage(), 25 | ), 26 | GoRoute( 27 | path: AppRoute.auth.path, 28 | builder: (context, state) { 29 | return BlocProvider( 30 | create: (context) => AuthCubit( 31 | userRepository: context.read(), 32 | authRepository: context.read(), 33 | ), 34 | child: const AuthPage(), 35 | ); 36 | }, 37 | ), 38 | ], 39 | ); 40 | -------------------------------------------------------------------------------- /lib/core/ui/dialog/confirmation_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:go_router/go_router.dart'; 7 | import 'package:scalable_flutter_app_starter/core/extension/context.dart'; 8 | 9 | class ConfirmationDialog extends StatelessWidget { 10 | const ConfirmationDialog({ 11 | required this.title, 12 | required this.message, 13 | required this.confirmText, 14 | required this.isDestructive, 15 | super.key, 16 | }); 17 | 18 | final String title; 19 | final String message; 20 | final String confirmText; 21 | final bool isDestructive; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return !kIsWeb && Platform.isIOS 26 | ? _IosConfirmationDialog( 27 | title: title, 28 | message: message, 29 | confirmText: confirmText, 30 | isDestructive: isDestructive, 31 | ) 32 | : _AndroidConfirmationDialog( 33 | title: title, 34 | message: message, 35 | confirmText: confirmText, 36 | isDestructive: isDestructive, 37 | ); 38 | } 39 | } 40 | 41 | class _AndroidConfirmationDialog extends StatelessWidget { 42 | const _AndroidConfirmationDialog({ 43 | required this.title, 44 | required this.message, 45 | required this.confirmText, 46 | required this.isDestructive, 47 | }); 48 | 49 | final String title; 50 | final String message; 51 | final String confirmText; 52 | final bool isDestructive; 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return AlertDialog( 57 | title: Text(title), 58 | content: Text(message), 59 | actions: [ 60 | TextButton( 61 | style: TextButton.styleFrom( 62 | foregroundColor: context.colorScheme.onBackground, 63 | ), 64 | onPressed: () => context.pop(), 65 | child: const Text('Cancel'), 66 | ), 67 | TextButton( 68 | style: TextButton.styleFrom( 69 | foregroundColor: isDestructive 70 | ? context.colorScheme.error 71 | : context.colorScheme.onBackground, 72 | ), 73 | onPressed: () => context.pop(true), 74 | child: Text(confirmText), 75 | ), 76 | ], 77 | ); 78 | } 79 | } 80 | 81 | class _IosConfirmationDialog extends StatelessWidget { 82 | const _IosConfirmationDialog({ 83 | required this.title, 84 | required this.message, 85 | required this.confirmText, 86 | required this.isDestructive, 87 | }); 88 | 89 | final String title; 90 | final String message; 91 | final String confirmText; 92 | final bool isDestructive; 93 | 94 | @override 95 | Widget build(BuildContext context) { 96 | const OutlinedBorder buttonShape = RoundedRectangleBorder( 97 | borderRadius: BorderRadius.zero, 98 | ); 99 | 100 | return CupertinoAlertDialog( 101 | title: Text(title), 102 | content: Text(message), 103 | actions: [ 104 | TextButton( 105 | style: TextButton.styleFrom( 106 | foregroundColor: context.colorScheme.onBackground, 107 | shape: buttonShape, 108 | ), 109 | onPressed: () => context.pop(), 110 | child: const Text('Cancel'), 111 | ), 112 | TextButton( 113 | style: TextButton.styleFrom( 114 | foregroundColor: isDestructive 115 | ? context.colorScheme.error 116 | : context.colorScheme.onBackground, 117 | shape: buttonShape, 118 | ), 119 | onPressed: () => context.pop(true), 120 | child: Text(confirmText), 121 | ), 122 | ], 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/core/ui/dialog/dialogs.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:scalable_flutter_app_starter/core/extension/context.dart'; 4 | import 'package:scalable_flutter_app_starter/core/navigation/route.dart'; 5 | import 'package:scalable_flutter_app_starter/core/ui/dialog/confirmation_dialog.dart'; 6 | import 'package:scalable_flutter_app_starter/feature/user/bloc/user_cubit.dart'; 7 | 8 | sealed class Dialogs { 9 | const Dialogs._(); 10 | 11 | static Future showDeleteAccountConfirmationDialog( 12 | BuildContext context, 13 | ) async { 14 | final confirmed = await _showConfirmationDialog( 15 | context, 16 | title: 'Delete Account', 17 | message: 'Are you sure you want to delete your account forever? ' 18 | 'It can take up to 30 days. This cannot be undone.', 19 | confirmText: 'Delete My Account', 20 | isDestructive: true, 21 | ); 22 | 23 | if (confirmed && context.mounted) { 24 | // TODO: implement delete account 25 | context.showSnackBarMessage('Request submitted.'); 26 | } 27 | } 28 | 29 | static Future showLogOutConfirmationDialog( 30 | BuildContext context, 31 | ) async { 32 | final confirmed = await _showConfirmationDialog( 33 | context, 34 | title: 'Sign Out', 35 | message: 'Are you sure you want to sign out?', 36 | confirmText: 'Sign Out', 37 | isDestructive: true, 38 | ); 39 | 40 | if (confirmed && context.mounted) { 41 | context.read().logOut(); 42 | AppRoute.home.go(context); 43 | } 44 | } 45 | 46 | static Future _showConfirmationDialog( 47 | BuildContext context, { 48 | required String title, 49 | required String message, 50 | required String confirmText, 51 | bool isDestructive = false, 52 | }) async { 53 | return await showDialog( 54 | context: context, 55 | builder: (context) => ConfirmationDialog( 56 | title: title, 57 | message: message, 58 | confirmText: confirmText, 59 | isDestructive: isDestructive, 60 | ), 61 | ) ?? 62 | false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/core/ui/input/input_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/util/validators.dart'; 3 | 4 | class InputField extends StatefulWidget { 5 | const InputField({ 6 | required this.controller, 7 | required this.label, 8 | required this.textInputAction, 9 | this.keyboardType = TextInputType.text, 10 | this.autofillHints, 11 | this.validator, 12 | this.onFieldSubmitted, 13 | super.key, 14 | }); 15 | 16 | const InputField.name({ 17 | required TextEditingController controller, 18 | String label = 'Name', 19 | TextInputAction textInputAction = TextInputAction.next, 20 | Key? key, 21 | }) : this( 22 | key: key, 23 | controller: controller, 24 | label: label, 25 | textInputAction: textInputAction, 26 | keyboardType: TextInputType.name, 27 | autofillHints: const [AutofillHints.name], 28 | validator: Validators.required, 29 | ); 30 | 31 | const InputField.email({ 32 | required TextEditingController controller, 33 | String label = 'Email', 34 | TextInputAction textInputAction = TextInputAction.next, 35 | Key? key, 36 | }) : this( 37 | key: key, 38 | controller: controller, 39 | label: label, 40 | textInputAction: textInputAction, 41 | keyboardType: TextInputType.emailAddress, 42 | autofillHints: const [AutofillHints.email], 43 | validator: Validators.email, 44 | ); 45 | 46 | const InputField.password({ 47 | required TextEditingController controller, 48 | String label = 'Password', 49 | TextInputAction textInputAction = TextInputAction.next, 50 | ValueChanged? onFieldSubmitted, 51 | Key? key, 52 | }) : this( 53 | key: key, 54 | controller: controller, 55 | label: label, 56 | textInputAction: textInputAction, 57 | keyboardType: TextInputType.visiblePassword, 58 | autofillHints: const [AutofillHints.password], 59 | validator: Validators.password, 60 | onFieldSubmitted: onFieldSubmitted, 61 | ); 62 | 63 | final TextEditingController controller; 64 | final String label; 65 | final TextInputAction textInputAction; 66 | final TextInputType keyboardType; 67 | final List? autofillHints; 68 | final FormFieldValidator? validator; 69 | final ValueChanged? onFieldSubmitted; 70 | 71 | @override 72 | State createState() => _InputFieldState(); 73 | } 74 | 75 | class _InputFieldState extends State { 76 | late bool _obscureText; 77 | late bool _isPassword; 78 | 79 | @override 80 | void initState() { 81 | _isPassword = widget.keyboardType == TextInputType.visiblePassword; 82 | _obscureText = _isPassword; 83 | super.initState(); 84 | } 85 | 86 | @override 87 | void didUpdateWidget(covariant InputField oldWidget) { 88 | if (oldWidget.keyboardType != widget.keyboardType) { 89 | _isPassword = widget.keyboardType == TextInputType.visiblePassword; 90 | _obscureText = _isPassword; 91 | } 92 | super.didUpdateWidget(oldWidget); 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context) { 97 | final validator = 98 | widget.validator ?? Validators.getValidator(widget.keyboardType); 99 | 100 | return TextFormField( 101 | controller: widget.controller, 102 | keyboardType: widget.keyboardType, 103 | textInputAction: widget.textInputAction, 104 | obscureText: _obscureText, 105 | validator: validator, 106 | autofillHints: widget.autofillHints, 107 | onFieldSubmitted: widget.onFieldSubmitted, 108 | decoration: InputDecoration( 109 | labelText: widget.label, 110 | suffixIcon: _isPassword 111 | ? IconButton( 112 | onPressed: () => setState(() => _obscureText = !_obscureText), 113 | icon: _obscureText 114 | ? const Icon(Icons.visibility_off) 115 | : const Icon(Icons.visibility_sharp), 116 | ) 117 | : null, 118 | ), 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/core/ui/widget/labeled_text_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/extension/context.dart'; 3 | 4 | class LabeledTextButton extends StatelessWidget { 5 | const LabeledTextButton({ 6 | required this.label, 7 | required this.action, 8 | required this.onTap, 9 | super.key, 10 | }); 11 | 12 | final String label; 13 | final String action; 14 | final VoidCallback onTap; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return TextButton( 19 | onPressed: onTap, 20 | child: RichText( 21 | textAlign: TextAlign.center, 22 | text: TextSpan( 23 | text: '$label ', 24 | style: context.textTheme.labelMedium, 25 | children: [ 26 | TextSpan( 27 | text: action, 28 | style: context.textTheme.labelMedium!.copyWith( 29 | color: context.colorScheme.primary, 30 | fontWeight: FontWeight.bold, 31 | ), 32 | ), 33 | ], 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/core/ui/widget/loading_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingOverlay extends StatelessWidget { 4 | const LoadingOverlay({ 5 | required this.loading, 6 | required this.child, 7 | super.key, 8 | }); 9 | 10 | final bool loading; 11 | final Widget child; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | if (!loading) { 16 | return child; 17 | } 18 | 19 | return Stack( 20 | children: [ 21 | child, 22 | const ColoredBox( 23 | color: Colors.black54, 24 | child: Center( 25 | child: CircularProgressIndicator(), 26 | ), 27 | ), 28 | ], 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/core/ui/widget/responsive.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const desktopWidthBreakpoint = 1280.0; 4 | const mobileWidthBreakpoint = 600.0; 5 | 6 | class ConstrainedWidth extends StatelessWidget { 7 | const ConstrainedWidth.desktop({ 8 | required this.child, 9 | super.key, 10 | }) : maxWidth = desktopWidthBreakpoint; 11 | 12 | const ConstrainedWidth.mobile({ 13 | required this.child, 14 | super.key, 15 | }) : maxWidth = mobileWidthBreakpoint; 16 | 17 | final double maxWidth; 18 | final Widget child; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Center( 23 | child: ConstrainedBox( 24 | constraints: BoxConstraints( 25 | maxWidth: maxWidth, 26 | ), 27 | child: child, 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/core/ui/widget/url_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | enum UrlImageShape { 5 | circle, 6 | rectangle, 7 | } 8 | 9 | class UrlImage extends StatelessWidget { 10 | const UrlImage({ 11 | required this.url, 12 | required this.width, 13 | required this.height, 14 | this.fit = BoxFit.cover, 15 | super.key, 16 | }) : shape = UrlImageShape.rectangle; 17 | 18 | const UrlImage.square({ 19 | required this.url, 20 | required double size, 21 | this.fit = BoxFit.cover, 22 | super.key, 23 | }) : width = size, 24 | height = size, 25 | shape = UrlImageShape.rectangle; 26 | 27 | const UrlImage.circle({ 28 | required this.url, 29 | required double size, 30 | this.fit = BoxFit.cover, 31 | super.key, 32 | }) : width = size, 33 | height = size, 34 | shape = UrlImageShape.circle; 35 | 36 | final String url; 37 | final double width; 38 | final double height; 39 | final UrlImageShape shape; 40 | final BoxFit fit; 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | final Widget image = CachedNetworkImage( 45 | imageUrl: url, 46 | width: width, 47 | height: height, 48 | fit: fit, 49 | ); 50 | 51 | return switch (shape) { 52 | UrlImageShape.circle => ClipOval(child: image), 53 | UrlImageShape.rectangle => image, 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/core/util/urls.dart: -------------------------------------------------------------------------------- 1 | import 'package:url_launcher/url_launcher.dart'; 2 | 3 | class Urls { 4 | Urls._(); 5 | 6 | static const _termsOfService = 'https://scalableflutterapp.com/'; 7 | static const _privacyPolicy = 'https://scalableflutterapp.com/'; 8 | static const _buyPro = 'https://scalableflutterapp.com/'; 9 | 10 | static void showTermsOfService() => _show(_termsOfService); 11 | 12 | static void showPrivacyPolicy() => _show(_privacyPolicy); 13 | 14 | static void showBuyPro() => _show(_buyPro); 15 | 16 | static void _show(String url) { 17 | launchUrl(Uri.parse(url)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/core/util/validators.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/extension/string.dart'; 3 | 4 | abstract class Validators { 5 | Validators._(); 6 | 7 | static FormFieldValidator? getValidator(TextInputType? keyboardType) { 8 | return switch (keyboardType) { 9 | TextInputType.emailAddress => Validators.email, 10 | TextInputType.number => Validators.number, 11 | _ => null, 12 | }; 13 | } 14 | 15 | static String? required(String? input) { 16 | if (input?.trim().isEmpty ?? true) { 17 | return 'Required'; 18 | } 19 | 20 | return null; 21 | } 22 | 23 | static String? requiredTyped(T? input) { 24 | if (input == null) { 25 | return 'Required'; 26 | } 27 | 28 | return null; 29 | } 30 | 31 | static String? email(String? email) { 32 | if (email == null || email.isEmpty) { 33 | return 'Required'; 34 | } 35 | 36 | if (!email.isValidEmail()) { 37 | return 'Enter valid email'; 38 | } 39 | 40 | return null; 41 | } 42 | 43 | static String? password(String? password) { 44 | if (password == null || password.isEmpty) { 45 | return 'Required'; 46 | } 47 | 48 | if (password.length < 6) { 49 | return 'Password must be at least 6 characters long'; 50 | } 51 | 52 | if (!password.contains(RegExp('[A-Z]'))) { 53 | return 'Password must contain at least one capital letter'; 54 | } 55 | 56 | return null; 57 | } 58 | 59 | static String? number(String? input) { 60 | if (input == null) { 61 | return 'Required'; 62 | } 63 | 64 | final number = num.tryParse(input); 65 | if (number == null) { 66 | return 'Enter valid number'; 67 | } 68 | 69 | return null; 70 | } 71 | 72 | static String? positiveInteger(String? input) { 73 | if (input == null) { 74 | return 'Required'; 75 | } 76 | 77 | final integer = int.tryParse(input); 78 | if (integer == null || integer <= 0) { 79 | return 'Enter positive integer'; 80 | } 81 | 82 | return null; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/feature/auth/bloc/auth_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:scalable_flutter_app_starter/core/logger/loggy_types.dart'; 4 | import 'package:scalable_flutter_app_starter/feature/auth/repository/auth_repository.dart'; 5 | import 'package:scalable_flutter_app_starter/feature/user/model/user.dart'; 6 | import 'package:scalable_flutter_app_starter/feature/user/repository/user_repository.dart'; 7 | 8 | part 'auth_state.dart'; 9 | 10 | class AuthCubit extends Cubit with BlocLoggy { 11 | AuthCubit({ 12 | required this.authRepository, 13 | required this.userRepository, 14 | }) : super(const AuthInitial()); 15 | 16 | final AuthRepository authRepository; 17 | final UserRepository userRepository; 18 | 19 | Future signUpWithEmailAndPassword({ 20 | required String email, 21 | required String password, 22 | }) async { 23 | emit(const AuthLoading()); 24 | 25 | try { 26 | final user = await authRepository.signUpWithEmailAndPassword( 27 | email: email, 28 | password: password, 29 | ); 30 | 31 | emit(AuthSuccess(user: user)); 32 | } catch (e, s) { 33 | loggy.error('signUpWithEmailAndPassword error', e, s); 34 | emit(AuthFailure(errorMessage: e.toString())); 35 | } 36 | } 37 | 38 | Future signInWithEmailAndPassword({ 39 | required String email, 40 | required String password, 41 | }) async { 42 | emit(const AuthLoading()); 43 | try { 44 | final user = await authRepository.signInWithEmailAndPassword( 45 | email: email, 46 | password: password, 47 | ); 48 | 49 | emit(AuthSuccess(user: user)); 50 | } catch (e, s) { 51 | loggy.error('signInWithEmailAndPassword error', e, s); 52 | emit(AuthFailure(errorMessage: e.toString())); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/feature/auth/bloc/auth_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_cubit.dart'; 2 | 3 | @immutable 4 | sealed class AuthState { 5 | const AuthState(); 6 | } 7 | 8 | class AuthInitial extends AuthState { 9 | const AuthInitial(); 10 | } 11 | 12 | class AuthLoading extends AuthState { 13 | const AuthLoading(); 14 | } 15 | 16 | class AuthSuccess extends AuthState { 17 | const AuthSuccess({required this.user}); 18 | 19 | final User? user; 20 | } 21 | 22 | class AuthFailure extends AuthState { 23 | const AuthFailure({required this.errorMessage}); 24 | 25 | final String errorMessage; 26 | } 27 | -------------------------------------------------------------------------------- /lib/feature/auth/repository/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:scalable_flutter_app_starter/feature/user/model/user.dart'; 2 | import 'package:scalable_flutter_app_starter/feature/user/provider/user_mock_provider.dart'; 3 | 4 | class AuthRepository { 5 | const AuthRepository({ 6 | required this.userProvider, 7 | }); 8 | 9 | final UserMockProvider userProvider; 10 | 11 | Future signUpWithEmailAndPassword({ 12 | required String email, 13 | required String password, 14 | }) async { 15 | return userProvider.triggerLoggedIn(); 16 | } 17 | 18 | Future signInWithEmailAndPassword({ 19 | required String email, 20 | required String password, 21 | }) async { 22 | return userProvider.triggerLoggedIn(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/feature/auth/ui/page/auth_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:scalable_flutter_app_starter/core/extension/context.dart'; 4 | import 'package:scalable_flutter_app_starter/core/navigation/route.dart'; 5 | import 'package:scalable_flutter_app_starter/core/ui/input/input_field.dart'; 6 | import 'package:scalable_flutter_app_starter/core/ui/widget/labeled_text_button.dart'; 7 | import 'package:scalable_flutter_app_starter/core/ui/widget/loading_overlay.dart'; 8 | import 'package:scalable_flutter_app_starter/core/ui/widget/responsive.dart'; 9 | import 'package:scalable_flutter_app_starter/feature/auth/bloc/auth_cubit.dart'; 10 | 11 | class AuthPage extends StatefulWidget { 12 | const AuthPage({super.key}); 13 | 14 | @override 15 | State createState() => _AuthPageState(); 16 | } 17 | 18 | class _AuthPageState extends State { 19 | final _emailController = TextEditingController(); 20 | final _passwordController = TextEditingController(); 21 | final _formKey = GlobalKey(); 22 | 23 | bool _isSignUp = true; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return BlocConsumer( 28 | listener: _onAuthState, 29 | builder: (context, state) { 30 | return LoadingOverlay( 31 | loading: state is AuthLoading, 32 | child: Scaffold( 33 | appBar: AppBar(), 34 | body: ConstrainedWidth.mobile( 35 | child: Form( 36 | key: _formKey, 37 | child: SafeArea( 38 | child: Padding( 39 | padding: const EdgeInsets.all(16), 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.stretch, 42 | children: [ 43 | const Spacer(), 44 | Text( 45 | _isSignUp 46 | ? 'Sign up to continue' 47 | : 'Sign in to continue', 48 | style: context.textTheme.titleLarge, 49 | ), 50 | const SizedBox(height: 16), 51 | InputField.email( 52 | controller: _emailController, 53 | ), 54 | const SizedBox(height: 16), 55 | InputField.password( 56 | controller: _passwordController, 57 | textInputAction: TextInputAction.done, 58 | onFieldSubmitted: (_) => _submit(), 59 | ), 60 | const SizedBox(height: 32), 61 | ElevatedButton( 62 | onPressed: _submit, 63 | child: Text(_isSignUp ? 'Sign Up' : 'Sign In'), 64 | ), 65 | const SizedBox(height: 8), 66 | LabeledTextButton( 67 | label: _isSignUp 68 | ? 'Already have an account?' 69 | : 'Don’t have an account?', 70 | action: _isSignUp ? 'Sign in' : 'Sign up', 71 | onTap: () => setState(() => _isSignUp = !_isSignUp), 72 | ), 73 | const Spacer(), 74 | ], 75 | ), 76 | ), 77 | ), 78 | ), 79 | ), 80 | ), 81 | ); 82 | }, 83 | ); 84 | } 85 | 86 | void _onAuthState(BuildContext context, AuthState state) { 87 | if (state is AuthFailure) { 88 | context.showSnackBarMessage( 89 | state.errorMessage, 90 | isError: true, 91 | ); 92 | return; 93 | } 94 | 95 | if (state is AuthSuccess) { 96 | if (state.user != null) { 97 | AppRoute.home.go(context); 98 | } 99 | } 100 | } 101 | 102 | void _submit() { 103 | if (!_formKey.currentState!.validate()) { 104 | return; 105 | } 106 | 107 | context.closeKeyboard(); 108 | 109 | final email = _emailController.text.trim(); 110 | final password = _passwordController.text.trim(); 111 | 112 | if (_isSignUp) { 113 | context.read().signUpWithEmailAndPassword( 114 | email: email, 115 | password: password, 116 | ); 117 | } else { 118 | context.read().signInWithEmailAndPassword( 119 | email: email, 120 | password: password, 121 | ); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/feature/auth/ui/page/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:scalable_flutter_app_starter/core/extension/context.dart'; 4 | import 'package:scalable_flutter_app_starter/core/navigation/route.dart'; 5 | import 'package:scalable_flutter_app_starter/feature/user/bloc/user_cubit.dart'; 6 | 7 | class SplashPage extends StatelessWidget { 8 | const SplashPage({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return BlocListener( 13 | listener: _onUserState, 14 | child: Scaffold( 15 | body: Center( 16 | child: Text( 17 | 'Scalable\nFlutter\nApp', 18 | textAlign: TextAlign.center, 19 | style: context.textTheme.headlineLarge, 20 | ), 21 | ), 22 | ), 23 | ); 24 | } 25 | 26 | void _onUserState(BuildContext context, UserState userState) { 27 | if (userState is! UserLoaded) { 28 | // User not loaded yet 29 | return; 30 | } 31 | 32 | AppRoute.home.go(context); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/feature/home/ui/page/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/extension/context.dart'; 3 | import 'package:scalable_flutter_app_starter/feature/user/ui/widget/profile_tab.dart'; 4 | 5 | class HomePage extends StatefulWidget { 6 | const HomePage({super.key}); 7 | 8 | @override 9 | State createState() => _HomePageState(); 10 | } 11 | 12 | class _HomePageState extends State { 13 | int _selectedIndex = 0; 14 | 15 | final _tabs = <_HomeTab>[ 16 | _HomeTab( 17 | label: 'Home', 18 | icon: Icons.home, 19 | builder: (context) => const Center(child: Text('Home')), 20 | ), 21 | _HomeTab( 22 | label: 'Explore', 23 | icon: Icons.explore, 24 | builder: (context) => const Center(child: Text('Explore')), 25 | ), 26 | _HomeTab( 27 | label: 'Profile', 28 | icon: Icons.person, 29 | builder: (context) => const ProfileTab(), 30 | ), 31 | ]; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | final Widget body; 36 | final Widget? bottomNavigationBar; 37 | final content = _tabs[_selectedIndex].builder(context); 38 | 39 | if (context.isWide) { 40 | body = Row( 41 | children: [ 42 | NavigationRail( 43 | selectedIndex: _selectedIndex, 44 | onDestinationSelected: (index) { 45 | setState(() => _selectedIndex = index); 46 | }, 47 | destinations: [ 48 | for (final tab in _tabs) 49 | NavigationRailDestination( 50 | label: Text(tab.label), 51 | icon: Icon(tab.icon), 52 | ), 53 | ], 54 | ), 55 | Expanded(child: content), 56 | ], 57 | ); 58 | bottomNavigationBar = null; 59 | } else { 60 | body = SafeArea(child: content); 61 | bottomNavigationBar = BottomNavigationBar( 62 | currentIndex: _selectedIndex, 63 | onTap: (index) => setState(() => _selectedIndex = index), 64 | items: [ 65 | for (final tab in _tabs) 66 | BottomNavigationBarItem( 67 | label: tab.label, 68 | icon: Icon(tab.icon), 69 | ), 70 | ], 71 | ); 72 | } 73 | 74 | return Scaffold( 75 | body: body, 76 | bottomNavigationBar: bottomNavigationBar, 77 | ); 78 | } 79 | } 80 | 81 | class _HomeTab { 82 | const _HomeTab({ 83 | required this.label, 84 | required this.icon, 85 | required this.builder, 86 | }); 87 | 88 | final String label; 89 | final IconData icon; 90 | final WidgetBuilder builder; 91 | } 92 | -------------------------------------------------------------------------------- /lib/feature/settings/ui/page/settings_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/ui/dialog/dialogs.dart'; 3 | import 'package:scalable_flutter_app_starter/core/util/urls.dart'; 4 | import 'package:scalable_flutter_app_starter/feature/settings/ui/widget/app_version.dart'; 5 | import 'package:scalable_flutter_app_starter/feature/settings/ui/widget/settings_tile.dart'; 6 | 7 | class SettingsPage extends StatelessWidget { 8 | const SettingsPage({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | title: const Text('Settings'), 15 | ), 16 | body: Column( 17 | crossAxisAlignment: CrossAxisAlignment.stretch, 18 | children: [ 19 | const SettingsTile( 20 | icon: Icons.security_outlined, 21 | label: 'Privacy Policy', 22 | onTap: Urls.showPrivacyPolicy, 23 | ), 24 | const SettingsTile( 25 | icon: Icons.fact_check_outlined, 26 | label: 'Terms of Service', 27 | onTap: Urls.showTermsOfService, 28 | ), 29 | SettingsTile( 30 | icon: Icons.delete_outlined, 31 | label: 'Delete Account', 32 | onTap: () => Dialogs.showDeleteAccountConfirmationDialog(context), 33 | ), 34 | SettingsTile( 35 | icon: Icons.logout_outlined, 36 | label: 'Sign Out', 37 | onTap: () => Dialogs.showLogOutConfirmationDialog(context), 38 | ), 39 | const Spacer(), 40 | const Padding( 41 | padding: EdgeInsets.all(16), 42 | child: AppVersion(), 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/feature/settings/ui/widget/app_version.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:package_info_plus/package_info_plus.dart'; 3 | import 'package:scalable_flutter_app_starter/core/ui/widget/labeled_text_button.dart'; 4 | import 'package:scalable_flutter_app_starter/core/util/urls.dart'; 5 | 6 | class AppVersion extends StatelessWidget { 7 | const AppVersion({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final theme = Theme.of(context); 12 | 13 | return FutureBuilder( 14 | future: PackageInfo.fromPlatform(), 15 | builder: (context, snapshot) { 16 | if (!snapshot.hasData) { 17 | return const SizedBox(); 18 | } 19 | 20 | final data = snapshot.data!; 21 | 22 | return Column( 23 | children: [ 24 | Text( 25 | data.appName, 26 | style: theme.textTheme.titleMedium, 27 | ), 28 | const SizedBox(height: 2), 29 | Text( 30 | 'v${data.version} (${data.buildNumber})', 31 | style: theme.textTheme.bodySmall, 32 | ), 33 | const LabeledTextButton( 34 | label: 'Get the PRO version', 35 | action: 'GET IT NOW', 36 | onTap: Urls.showBuyPro, 37 | ), 38 | ], 39 | ); 40 | }, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/feature/settings/ui/widget/settings_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SettingsTile extends StatelessWidget { 4 | const SettingsTile({ 5 | required this.icon, 6 | required this.label, 7 | required this.onTap, 8 | super.key, 9 | }); 10 | 11 | final IconData icon; 12 | final String label; 13 | final VoidCallback onTap; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ListTile( 18 | leading: Icon(icon), 19 | title: Text(label), 20 | onTap: onTap, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/feature/user/bloc/user_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:scalable_flutter_app_starter/core/logger/loggy_types.dart'; 6 | import 'package:scalable_flutter_app_starter/feature/user/model/user.dart'; 7 | import 'package:scalable_flutter_app_starter/feature/user/repository/user_repository.dart'; 8 | 9 | part 'user_state.dart'; 10 | 11 | class UserCubit extends Cubit with BlocLoggy { 12 | UserCubit({ 13 | required this.userRepository, 14 | }) : super(const UserInitial()) { 15 | _load(); 16 | } 17 | 18 | final UserRepository userRepository; 19 | 20 | StreamSubscription? _userSubscription; 21 | 22 | @override 23 | Future close() { 24 | _userSubscription?.cancel(); 25 | return super.close(); 26 | } 27 | 28 | void logOut() { 29 | userRepository.logOut(); 30 | } 31 | 32 | void _load() { 33 | _userSubscription = userRepository.getUserStream().listen(_onUser); 34 | } 35 | 36 | void _onUser(User? user) { 37 | loggy.info('new user: ${user?.id}'); 38 | emit(UserLoaded(user: user)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/feature/user/bloc/user_state.dart: -------------------------------------------------------------------------------- 1 | part of 'user_cubit.dart'; 2 | 3 | @immutable 4 | sealed class UserState { 5 | const UserState(); 6 | } 7 | 8 | class UserInitial extends UserState { 9 | const UserInitial(); 10 | } 11 | 12 | class UserLoaded extends UserState { 13 | const UserLoaded({ 14 | required this.user, 15 | }); 16 | 17 | final User? user; 18 | } 19 | -------------------------------------------------------------------------------- /lib/feature/user/model/user.dart: -------------------------------------------------------------------------------- 1 | class User { 2 | const User({ 3 | required this.id, 4 | required this.name, 5 | required this.email, 6 | required this.imageUrl, 7 | required this.createdAt, 8 | }); 9 | 10 | final String id; 11 | final String name; 12 | final String email; 13 | final String imageUrl; 14 | final DateTime createdAt; 15 | } 16 | -------------------------------------------------------------------------------- /lib/feature/user/provider/user_mock_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:scalable_flutter_app_starter/feature/user/model/user.dart'; 4 | 5 | User get _mockUser => User( 6 | id: 'john-doe', 7 | name: 'John Doe', 8 | email: 'john@test.com', 9 | imageUrl: 10 | 'https://images.unsplash.com/photo-1528892952291-009c663ce843?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', 11 | createdAt: DateTime.now(), 12 | ); 13 | 14 | class UserMockProvider { 15 | UserMockProvider() { 16 | triggerNotLoggedIn(); 17 | } 18 | 19 | final _userStream = StreamController.broadcast(); 20 | 21 | Stream getUserStream() => _userStream.stream; 22 | 23 | Future triggerLoggedIn() async { 24 | await _networkDelay(); 25 | 26 | final user = _mockUser; 27 | _userStream.add(user); 28 | return user; 29 | } 30 | 31 | Future triggerNotLoggedIn() async { 32 | await _networkDelay(); 33 | _userStream.add(null); 34 | } 35 | 36 | void triggerLoggedOut() { 37 | _userStream.add(null); 38 | } 39 | 40 | /// Simulate network delay 41 | Future _networkDelay() async { 42 | await Future.delayed(const Duration(seconds: 2)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/feature/user/repository/user_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:scalable_flutter_app_starter/feature/user/model/user.dart'; 2 | import 'package:scalable_flutter_app_starter/feature/user/provider/user_mock_provider.dart'; 3 | 4 | class UserRepository { 5 | const UserRepository({ 6 | required this.userProvider, 7 | }); 8 | 9 | final UserMockProvider userProvider; 10 | 11 | Stream getUserStream() { 12 | return userProvider.getUserStream(); 13 | } 14 | 15 | void logOut() { 16 | userProvider.triggerLoggedOut(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/feature/user/ui/widget/profile_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/extension/context.dart'; 3 | import 'package:scalable_flutter_app_starter/core/extension/context_user.dart'; 4 | import 'package:scalable_flutter_app_starter/core/navigation/route.dart'; 5 | import 'package:scalable_flutter_app_starter/feature/user/ui/widget/user_image.dart'; 6 | 7 | class ProfileTab extends StatelessWidget { 8 | const ProfileTab({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final user = context.watchCurrentUser; 13 | if (user == null) { 14 | return Center( 15 | child: ElevatedButton( 16 | onPressed: () => AppRoute.auth.push(context), 17 | child: const Text('Sign In'), 18 | ), 19 | ); 20 | } 21 | 22 | return Scaffold( 23 | appBar: AppBar( 24 | actions: [ 25 | IconButton( 26 | onPressed: () => AppRoute.settings.push(context), 27 | icon: const Icon(Icons.settings), 28 | ), 29 | ], 30 | ), 31 | body: Center( 32 | child: Column( 33 | children: [ 34 | UserImage.medium(user.imageUrl), 35 | const SizedBox(height: 16), 36 | Text( 37 | user.name, 38 | textAlign: TextAlign.center, 39 | style: context.textTheme.titleMedium, 40 | ), 41 | const SizedBox(height: 4), 42 | Text( 43 | user.email, 44 | textAlign: TextAlign.center, 45 | style: context.textTheme.bodyLarge, 46 | ), 47 | const SizedBox(height: 16), 48 | ], 49 | ), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/feature/user/ui/widget/user_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scalable_flutter_app_starter/core/ui/widget/url_image.dart'; 3 | 4 | class UserImage extends StatelessWidget { 5 | const UserImage.small(this.url, {super.key}) : size = 32; 6 | 7 | const UserImage.medium(this.url, {super.key}) : size = 48; 8 | 9 | const UserImage.large(this.url, {super.key}) : size = 64; 10 | 11 | final String url; 12 | final double size; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return UrlImage.circle( 17 | url: url, 18 | size: size, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:loggy/loggy.dart'; 6 | import 'package:scalable_flutter_app_starter/core/app/app.dart'; 7 | 8 | void main() { 9 | WidgetsFlutterBinding.ensureInitialized(); 10 | _initLoggy(); 11 | _initGoogleFonts(); 12 | 13 | runApp(const ScalableFlutterApp()); 14 | } 15 | 16 | void _initLoggy() { 17 | Loggy.initLoggy( 18 | logOptions: const LogOptions( 19 | LogLevel.all, 20 | stackTraceLevel: LogLevel.warning, 21 | ), 22 | logPrinter: const PrettyPrinter(), 23 | ); 24 | } 25 | 26 | void _initGoogleFonts() { 27 | GoogleFonts.config.allowRuntimeFetching = false; 28 | 29 | LicenseRegistry.addLicense(() async* { 30 | final license = await rootBundle.loadString('google_fonts/OFL.txt'); 31 | yield LicenseEntryWithLineBreaks(['google_fonts'], license); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | bloc: 13 | dependency: transitive 14 | description: 15 | name: bloc 16 | sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "8.1.2" 20 | boolean_selector: 21 | dependency: transitive 22 | description: 23 | name: boolean_selector 24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.1.1" 28 | cached_network_image: 29 | dependency: "direct main" 30 | description: 31 | name: cached_network_image 32 | sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "3.3.0" 36 | cached_network_image_platform_interface: 37 | dependency: transitive 38 | description: 39 | name: cached_network_image_platform_interface 40 | sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "3.0.0" 44 | cached_network_image_web: 45 | dependency: transitive 46 | description: 47 | name: cached_network_image_web 48 | sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.1.0" 52 | characters: 53 | dependency: transitive 54 | description: 55 | name: characters 56 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.3.0" 60 | clock: 61 | dependency: transitive 62 | description: 63 | name: clock 64 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.1.1" 68 | collection: 69 | dependency: transitive 70 | description: 71 | name: collection 72 | sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.17.2" 76 | crypto: 77 | dependency: transitive 78 | description: 79 | name: crypto 80 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "3.0.3" 84 | cupertino_icons: 85 | dependency: "direct main" 86 | description: 87 | name: cupertino_icons 88 | sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "1.0.6" 92 | fake_async: 93 | dependency: transitive 94 | description: 95 | name: fake_async 96 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "1.3.1" 100 | ffi: 101 | dependency: transitive 102 | description: 103 | name: ffi 104 | sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "2.1.0" 108 | file: 109 | dependency: transitive 110 | description: 111 | name: file 112 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "7.0.0" 116 | flutter: 117 | dependency: "direct main" 118 | description: flutter 119 | source: sdk 120 | version: "0.0.0" 121 | flutter_bloc: 122 | dependency: "direct main" 123 | description: 124 | name: flutter_bloc 125 | sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae 126 | url: "https://pub.dev" 127 | source: hosted 128 | version: "8.1.3" 129 | flutter_cache_manager: 130 | dependency: transitive 131 | description: 132 | name: flutter_cache_manager 133 | sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" 134 | url: "https://pub.dev" 135 | source: hosted 136 | version: "3.3.1" 137 | flutter_lints: 138 | dependency: "direct dev" 139 | description: 140 | name: flutter_lints 141 | sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 142 | url: "https://pub.dev" 143 | source: hosted 144 | version: "2.0.3" 145 | flutter_loggy: 146 | dependency: "direct main" 147 | description: 148 | name: flutter_loggy 149 | sha256: "21b515977deefe37817cce35b0e420c7cde930b9dcfdcbeb05730ed24ee74e3a" 150 | url: "https://pub.dev" 151 | source: hosted 152 | version: "2.0.2" 153 | flutter_test: 154 | dependency: "direct dev" 155 | description: flutter 156 | source: sdk 157 | version: "0.0.0" 158 | flutter_web_plugins: 159 | dependency: transitive 160 | description: flutter 161 | source: sdk 162 | version: "0.0.0" 163 | go_router: 164 | dependency: "direct main" 165 | description: 166 | name: go_router 167 | sha256: "1bd28265168045adb2df3da178bdbd44d37dddca0e7cfc867f8665d4f6b31f0c" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "11.1.3" 171 | google_fonts: 172 | dependency: "direct main" 173 | description: 174 | name: google_fonts 175 | sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "6.1.0" 179 | http: 180 | dependency: transitive 181 | description: 182 | name: http 183 | sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "1.1.0" 187 | http_parser: 188 | dependency: transitive 189 | description: 190 | name: http_parser 191 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "4.0.2" 195 | lints: 196 | dependency: transitive 197 | description: 198 | name: lints 199 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "2.1.1" 203 | logging: 204 | dependency: transitive 205 | description: 206 | name: logging 207 | sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" 208 | url: "https://pub.dev" 209 | source: hosted 210 | version: "1.2.0" 211 | loggy: 212 | dependency: "direct main" 213 | description: 214 | name: loggy 215 | sha256: "981e03162bbd3a5a843026f75f73d26e4a0d8aa035ae060456ca7b30dfd1e339" 216 | url: "https://pub.dev" 217 | source: hosted 218 | version: "2.0.3" 219 | matcher: 220 | dependency: transitive 221 | description: 222 | name: matcher 223 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 224 | url: "https://pub.dev" 225 | source: hosted 226 | version: "0.12.16" 227 | material_color_utilities: 228 | dependency: transitive 229 | description: 230 | name: material_color_utilities 231 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 232 | url: "https://pub.dev" 233 | source: hosted 234 | version: "0.5.0" 235 | meta: 236 | dependency: transitive 237 | description: 238 | name: meta 239 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 240 | url: "https://pub.dev" 241 | source: hosted 242 | version: "1.9.1" 243 | nested: 244 | dependency: transitive 245 | description: 246 | name: nested 247 | sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" 248 | url: "https://pub.dev" 249 | source: hosted 250 | version: "1.0.0" 251 | octo_image: 252 | dependency: transitive 253 | description: 254 | name: octo_image 255 | sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" 256 | url: "https://pub.dev" 257 | source: hosted 258 | version: "2.0.0" 259 | package_info_plus: 260 | dependency: "direct main" 261 | description: 262 | name: package_info_plus 263 | sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" 264 | url: "https://pub.dev" 265 | source: hosted 266 | version: "4.2.0" 267 | package_info_plus_platform_interface: 268 | dependency: transitive 269 | description: 270 | name: package_info_plus_platform_interface 271 | sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" 272 | url: "https://pub.dev" 273 | source: hosted 274 | version: "2.0.1" 275 | path: 276 | dependency: transitive 277 | description: 278 | name: path 279 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 280 | url: "https://pub.dev" 281 | source: hosted 282 | version: "1.8.3" 283 | path_provider: 284 | dependency: transitive 285 | description: 286 | name: path_provider 287 | sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa 288 | url: "https://pub.dev" 289 | source: hosted 290 | version: "2.1.1" 291 | path_provider_android: 292 | dependency: transitive 293 | description: 294 | name: path_provider_android 295 | sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" 296 | url: "https://pub.dev" 297 | source: hosted 298 | version: "2.2.0" 299 | path_provider_foundation: 300 | dependency: transitive 301 | description: 302 | name: path_provider_foundation 303 | sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" 304 | url: "https://pub.dev" 305 | source: hosted 306 | version: "2.3.1" 307 | path_provider_linux: 308 | dependency: transitive 309 | description: 310 | name: path_provider_linux 311 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 312 | url: "https://pub.dev" 313 | source: hosted 314 | version: "2.2.1" 315 | path_provider_platform_interface: 316 | dependency: transitive 317 | description: 318 | name: path_provider_platform_interface 319 | sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" 320 | url: "https://pub.dev" 321 | source: hosted 322 | version: "2.1.1" 323 | path_provider_windows: 324 | dependency: transitive 325 | description: 326 | name: path_provider_windows 327 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" 328 | url: "https://pub.dev" 329 | source: hosted 330 | version: "2.2.1" 331 | platform: 332 | dependency: transitive 333 | description: 334 | name: platform 335 | sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" 336 | url: "https://pub.dev" 337 | source: hosted 338 | version: "3.1.3" 339 | plugin_platform_interface: 340 | dependency: transitive 341 | description: 342 | name: plugin_platform_interface 343 | sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d 344 | url: "https://pub.dev" 345 | source: hosted 346 | version: "2.1.6" 347 | provider: 348 | dependency: transitive 349 | description: 350 | name: provider 351 | sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f 352 | url: "https://pub.dev" 353 | source: hosted 354 | version: "6.0.5" 355 | rxdart: 356 | dependency: transitive 357 | description: 358 | name: rxdart 359 | sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" 360 | url: "https://pub.dev" 361 | source: hosted 362 | version: "0.27.7" 363 | sky_engine: 364 | dependency: transitive 365 | description: flutter 366 | source: sdk 367 | version: "0.0.99" 368 | source_span: 369 | dependency: transitive 370 | description: 371 | name: source_span 372 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 373 | url: "https://pub.dev" 374 | source: hosted 375 | version: "1.10.0" 376 | sprintf: 377 | dependency: transitive 378 | description: 379 | name: sprintf 380 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 381 | url: "https://pub.dev" 382 | source: hosted 383 | version: "7.0.0" 384 | sqflite: 385 | dependency: transitive 386 | description: 387 | name: sqflite 388 | sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" 389 | url: "https://pub.dev" 390 | source: hosted 391 | version: "2.3.0" 392 | sqflite_common: 393 | dependency: transitive 394 | description: 395 | name: sqflite_common 396 | sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" 397 | url: "https://pub.dev" 398 | source: hosted 399 | version: "2.5.0" 400 | stack_trace: 401 | dependency: transitive 402 | description: 403 | name: stack_trace 404 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 405 | url: "https://pub.dev" 406 | source: hosted 407 | version: "1.11.0" 408 | stream_channel: 409 | dependency: transitive 410 | description: 411 | name: stream_channel 412 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 413 | url: "https://pub.dev" 414 | source: hosted 415 | version: "2.1.1" 416 | string_scanner: 417 | dependency: transitive 418 | description: 419 | name: string_scanner 420 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 421 | url: "https://pub.dev" 422 | source: hosted 423 | version: "1.2.0" 424 | synchronized: 425 | dependency: transitive 426 | description: 427 | name: synchronized 428 | sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" 429 | url: "https://pub.dev" 430 | source: hosted 431 | version: "3.1.0" 432 | term_glyph: 433 | dependency: transitive 434 | description: 435 | name: term_glyph 436 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 437 | url: "https://pub.dev" 438 | source: hosted 439 | version: "1.2.1" 440 | test_api: 441 | dependency: transitive 442 | description: 443 | name: test_api 444 | sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" 445 | url: "https://pub.dev" 446 | source: hosted 447 | version: "0.6.0" 448 | typed_data: 449 | dependency: transitive 450 | description: 451 | name: typed_data 452 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 453 | url: "https://pub.dev" 454 | source: hosted 455 | version: "1.3.2" 456 | url_launcher: 457 | dependency: "direct main" 458 | description: 459 | name: url_launcher 460 | sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" 461 | url: "https://pub.dev" 462 | source: hosted 463 | version: "6.1.14" 464 | url_launcher_android: 465 | dependency: transitive 466 | description: 467 | name: url_launcher_android 468 | sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330 469 | url: "https://pub.dev" 470 | source: hosted 471 | version: "6.1.0" 472 | url_launcher_ios: 473 | dependency: transitive 474 | description: 475 | name: url_launcher_ios 476 | sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f" 477 | url: "https://pub.dev" 478 | source: hosted 479 | version: "6.1.5" 480 | url_launcher_linux: 481 | dependency: transitive 482 | description: 483 | name: url_launcher_linux 484 | sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e 485 | url: "https://pub.dev" 486 | source: hosted 487 | version: "3.0.6" 488 | url_launcher_macos: 489 | dependency: transitive 490 | description: 491 | name: url_launcher_macos 492 | sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88 493 | url: "https://pub.dev" 494 | source: hosted 495 | version: "3.0.7" 496 | url_launcher_platform_interface: 497 | dependency: transitive 498 | description: 499 | name: url_launcher_platform_interface 500 | sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618" 501 | url: "https://pub.dev" 502 | source: hosted 503 | version: "2.1.5" 504 | url_launcher_web: 505 | dependency: transitive 506 | description: 507 | name: url_launcher_web 508 | sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5" 509 | url: "https://pub.dev" 510 | source: hosted 511 | version: "2.0.20" 512 | url_launcher_windows: 513 | dependency: transitive 514 | description: 515 | name: url_launcher_windows 516 | sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069" 517 | url: "https://pub.dev" 518 | source: hosted 519 | version: "3.0.8" 520 | uuid: 521 | dependency: transitive 522 | description: 523 | name: uuid 524 | sha256: b715b8d3858b6fa9f68f87d20d98830283628014750c2b09b6f516c1da4af2a7 525 | url: "https://pub.dev" 526 | source: hosted 527 | version: "4.1.0" 528 | vector_math: 529 | dependency: transitive 530 | description: 531 | name: vector_math 532 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 533 | url: "https://pub.dev" 534 | source: hosted 535 | version: "2.1.4" 536 | web: 537 | dependency: transitive 538 | description: 539 | name: web 540 | sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 541 | url: "https://pub.dev" 542 | source: hosted 543 | version: "0.1.4-beta" 544 | win32: 545 | dependency: transitive 546 | description: 547 | name: win32 548 | sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" 549 | url: "https://pub.dev" 550 | source: hosted 551 | version: "5.0.9" 552 | xdg_directories: 553 | dependency: transitive 554 | description: 555 | name: xdg_directories 556 | sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" 557 | url: "https://pub.dev" 558 | source: hosted 559 | version: "1.0.3" 560 | sdks: 561 | dart: ">=3.1.3 <4.0.0" 562 | flutter: ">=3.13.0" 563 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: scalable_flutter_app_starter 2 | description: A scalable Flutter app. 3 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: '>=3.1.3 <4.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | # Common 15 | flutter_bloc: ^8.1.3 16 | loggy: ^2.0.3 17 | flutter_loggy: ^2.0.2 18 | go_router: ^11.1.3 19 | package_info_plus: ^4.2.0 20 | url_launcher: ^6.1.14 21 | google_fonts: ^6.1.0 22 | cached_network_image: ^3.3.0 23 | 24 | # iOS 25 | cupertino_icons: ^1.0.2 26 | 27 | dev_dependencies: 28 | flutter_test: 29 | sdk: flutter 30 | 31 | flutter_lints: ^2.0.0 32 | 33 | flutter: 34 | uses-material-design: true 35 | 36 | assets: 37 | - assets/fonts/ 38 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gradoid/scalable_flutter_app_starter/6ea6a75d3341bf2183860fccae184103978e732b/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | scalable_flutter_app_starter 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scalable_flutter_app_starter", 3 | "short_name": "scalable_flutter_app_starter", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A scalable Flutter app.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | --------------------------------------------------------------------------------