├── .firebaserc ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .metadata ├── .run ├── Prod.run.xml ├── Staging.run.xml └── widgetbook.dart.run.xml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── gdglagos │ │ │ │ └── devfestlg │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-night-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-night │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── ic_launcher.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_monochrome.png │ │ │ ├── values-night-v31 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v31 │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── fonts │ ├── GoogleSansDisplay-Bold.ttf │ └── GoogleSansDisplay-Regular.ttf ├── icons │ ├── google-logo.svg │ └── logo.svg └── images │ ├── devfest-banner.png │ ├── google-logo.png │ ├── logo-dark.png │ ├── logo-light.png │ ├── lyft-logo.png │ ├── splash-dark.png │ ├── splash-light.png │ ├── spotify-logo.png │ └── uber-logo.png ├── codemagic.yaml ├── firebase.json ├── 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 │ │ │ ├── appstore-1024.png │ │ │ ├── appstore-20.png │ │ │ ├── appstore-20@2x.png │ │ │ ├── appstore-20@3x.png │ │ │ ├── appstore-29.png │ │ │ ├── appstore-29@2x.png │ │ │ ├── appstore-29@3x.png │ │ │ ├── appstore-40.png │ │ │ ├── appstore-40@2x.png │ │ │ ├── appstore-40@3x.png │ │ │ ├── appstore-60@2x.png │ │ │ ├── appstore-60@3x.png │ │ │ ├── appstore-76.png │ │ │ ├── appstore-76@2x.png │ │ │ └── appstore-83.5@2x.png │ │ ├── LaunchBackground.imageset │ │ │ ├── Contents.json │ │ │ ├── background.png │ │ │ └── darkbackground.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── GoogleService-Info.plist │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ └── Runner.entitlements ├── RunnerTests │ └── RunnerTests.swift └── firebase_app_id_file.json ├── lib ├── app.dart ├── core │ ├── constants.dart │ ├── data │ │ ├── data.dart │ │ ├── devfest_repository.dart │ │ ├── devfest_repository_impl.dart │ │ └── dto │ │ │ ├── add_multiple_rsvp_request.dart │ │ │ ├── agenda_response.dart │ │ │ ├── categories_response.dart │ │ │ ├── dto.dart │ │ │ ├── login_request.dart │ │ │ ├── rsvp_session_request.dart │ │ │ ├── rsvp_sessions_response.dart │ │ │ ├── sessions_response.dart │ │ │ ├── speakers_response.dart │ │ │ └── update_token_request.dart │ ├── enums │ │ ├── devfest_day.dart │ │ ├── status.dart │ │ └── tab_item.dart │ ├── exceptions │ │ ├── client_exception.dart │ │ ├── devfest_exception.dart │ │ ├── empty_exception.dart │ │ ├── exceptions.dart │ │ ├── invalid_ticket_id_exception.dart │ │ ├── object_parse_exception.dart │ │ ├── server_exception.dart │ │ └── user_not_registered_exception.dart │ ├── icons.dart │ ├── images.dart │ ├── network │ │ ├── data_transformer.dart │ │ ├── network.dart │ │ └── network_client.dart │ ├── providers │ │ ├── current_tab_provider.dart │ │ ├── providers.dart │ │ ├── theme_manager.dart │ │ └── theme_providers.dart │ ├── router │ │ ├── module_provider.dart │ │ ├── navigator.dart │ │ ├── router.dart │ │ └── routes.dart │ ├── services │ │ ├── firebase_notification_manager.dart │ │ └── local_notification_manager.dart │ ├── size_util.dart │ ├── themes │ │ ├── bottom_nav_theme.dart │ │ ├── button_theme.dart │ │ ├── colors.dart │ │ ├── outlined_button_theme.dart │ │ ├── text_field_theme.dart │ │ ├── text_theme.dart │ │ ├── theme_data.dart │ │ ├── theme_widget.dart │ │ └── themes.dart │ ├── ui_state_model │ │ ├── ui_state.dart │ │ ├── ui_state_model.dart │ │ └── view_state.dart │ ├── utils.dart │ └── widgets │ │ ├── animated_indexed_stack.dart │ │ ├── bottom_nav.dart │ │ ├── buttons.dart │ │ ├── chips.dart │ │ ├── loading_widgets.dart │ │ ├── on_screen_loader.dart │ │ ├── schedule_tab_bar.dart │ │ ├── switcher.dart │ │ ├── text_field.dart │ │ └── widgets.dart ├── features │ ├── agenda │ │ ├── model │ │ │ └── agenda_model.dart │ │ └── pages │ │ │ ├── agenda.dart │ │ │ └── agenda_base.dart │ ├── favourites │ │ └── pages │ │ │ ├── favourites.dart │ │ │ └── favourites_base.dart │ ├── home │ │ ├── pages │ │ │ └── home.dart │ │ └── widgets │ │ │ ├── header_delegate.dart │ │ │ ├── more_tile.dart │ │ │ ├── schedule_tile.dart │ │ │ ├── session_category_chip.dart │ │ │ ├── speaker_action_card.dart │ │ │ ├── speakers_chip.dart │ │ │ └── sponsors_chip.dart │ ├── more │ │ └── pages │ │ │ ├── more.dart │ │ │ └── more_base.dart │ ├── onboarding │ │ ├── application │ │ │ ├── application.dart │ │ │ ├── auth │ │ │ │ ├── auth_value_objects.dart │ │ │ │ ├── ui_state.dart │ │ │ │ └── view_model.dart │ │ │ └── controllers.dart │ │ ├── pages │ │ │ ├── authentication.dart │ │ │ └── onboarding.dart │ │ └── widgets │ │ │ └── title_tile.dart │ ├── profile │ │ ├── application │ │ │ ├── ui_state.dart │ │ │ └── view_model.dart │ │ └── pages │ │ │ └── profile.dart │ ├── schedule │ │ ├── application │ │ │ ├── application.dart │ │ │ ├── controllers.dart │ │ │ ├── session_detail │ │ │ │ ├── ui_state.dart │ │ │ │ └── view_model.dart │ │ │ └── sessions │ │ │ │ ├── ui_state.dart │ │ │ │ └── view_model.dart │ │ └── pages │ │ │ ├── schedule.dart │ │ │ ├── schedule_base.dart │ │ │ └── session.dart │ ├── speakers │ │ ├── application │ │ │ ├── application.dart │ │ │ ├── controllers.dart │ │ │ ├── speaker_detail │ │ │ │ ├── ui_state.dart │ │ │ │ └── view_model.dart │ │ │ └── speakers │ │ │ │ ├── ui_state.dart │ │ │ │ └── view_model.dart │ │ └── page │ │ │ ├── speaker_details.dart │ │ │ ├── speakers.dart │ │ │ └── speakers_base.dart │ └── splash │ │ └── splash.dart ├── firebase_options.dart ├── main_prod.dart ├── main_staging.dart └── widgetbook.dart ├── pubspec.lock ├── pubspec.yaml ├── shorebird.yaml ├── shots └── gdg-logo.png ├── test ├── commons.dart └── features │ ├── onboarding │ └── application │ │ ├── value_objects_test.dart │ │ └── view_model_test.dart │ ├── profile │ └── application │ │ └── view_model_test.dart │ ├── schedule │ └── application │ │ ├── session_detail_view_model_test.dart │ │ └── sessions_view_model_test.dart │ └── speakers │ └── application │ ├── speakers_detail_view_model_test.dart │ └── speakers_view_model_test.dart └── web ├── favicon.png ├── icons ├── Icon-maskable-192.png ├── Icon-maskable-512.png ├── README.txt ├── apple-touch-icon.png ├── favicon.ico ├── icon-192-maskable.png ├── icon-192.png ├── icon-512-maskable.png └── icon-512.png ├── index.html ├── manifest.json └── splash └── img ├── dark-background.png └── light-background.png /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "devfestlagos-2022" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master, dev] 6 | paths: 7 | - 'lib/**' 8 | - 'pubspec.yaml' 9 | - '.github/**' 10 | - 'android/**' 11 | - 'ios/**' 12 | - 'assets/**' 13 | - 'web/**' 14 | pull_request: 15 | branches: [master, dev] 16 | paths: 17 | - 'lib/**' 18 | - 'pubspec.yaml' 19 | - '.github/**' 20 | - 'android/**' 21 | - 'ios/**' 22 | - 'assets/**' 23 | - 'web/**' 24 | 25 | jobs: 26 | test: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v3 30 | - uses: subosito/flutter-action@v2 31 | with: 32 | channel: "stable" 33 | cache: true 34 | - name: Fetch dependencies 35 | run: flutter packages get 36 | - name: Widgetbook generator 37 | run: flutter pub run build_runner build --delete-conflicting-outputs 38 | - name: Analyze project 39 | run: flutter analyze . 40 | - name: Run tests 41 | run: flutter test --coverage 42 | - name: Upload coverage reports to Codecov 43 | uses: codecov/codecov-action@v3 44 | with: 45 | file: ./coverage/lcov.info 46 | token: ${{ secrets.CODECOV_TOKEN }} 47 | 48 | widgetbook: 49 | name: Build widgetbook 50 | needs: test 51 | if: github.event_name == 'pull_request' 52 | env: 53 | ALFRED: ${{secrets.ALFRED}} 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v3 57 | - uses: subosito/flutter-action@v2 58 | with: 59 | channel: "stable" 60 | cache: true 61 | - name: Fetch dependencies 62 | run: flutter packages get 63 | - name: Widgetbook generator 64 | run: flutter pub run build_runner build --delete-conflicting-outputs 65 | - name: Generate release build 66 | run: flutter build web --release -t lib/widgetbook.dart --web-renderer canvaskit 67 | - name: Deploy widgetbook 68 | if: ${{ github.event_name == 'pull_request' }} 69 | uses: peaceiris/actions-gh-pages@v3 70 | with: 71 | github_token: ${{ secrets.ALFRED }} 72 | personal_token: ${{ secrets.ALFRED }} 73 | publish_dir: ./build/web 74 | force_orphan: true 75 | user_name: 'Alfred 🦇' 76 | user_email: 'alfred@batman.inc' 77 | full_commit_message: 'feat: publish widgetbook' 78 | commit_message: 'feat: publish widgetbook' 79 | - name: Comment Widgetbook status 80 | if: ${{ github.event_name == 'pull_request' }} 81 | uses: thollander/actions-comment-pull-request@v2 82 | with: 83 | GITHUB_TOKEN: ${{secrets.ALFRED}} 84 | message: | 85 | Hello Team :wave: 86 | All widgets are now available for preview at: https://devfest23.netlify.app 87 | reactions: rocket -------------------------------------------------------------------------------- /.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 | .fvm/ 24 | *.jks 25 | coverage/ 26 | 27 | # Flutter/Dart/Pub related 28 | **/doc/api/ 29 | **/ios/Flutter/.last_build_id 30 | .dart_tool/ 31 | .flutter-plugins 32 | .flutter-plugins-dependencies 33 | .packages 34 | .pub-cache/ 35 | .pub/ 36 | /build/ 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # generated files 45 | *.g.dart 46 | 47 | # Android Studio will place build artifacts here 48 | /android/app/debug 49 | /android/app/profile 50 | /android/app/release -------------------------------------------------------------------------------- /.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: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31" 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: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 17 | base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 18 | - platform: web 19 | create_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 20 | base_revision: efbf63d9c66b9f6ec30e9ad4611189aa80003d31 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /.run/Prod.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.run/Staging.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.run/widgetbook.dart.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "inputs": [ 7 | { 8 | "id": "buildMode", 9 | "type": "pickString", 10 | "default": "debug", 11 | "options": ["debug", "profile", "release"], 12 | "description": "Which build mode to use?" 13 | }, 14 | { 15 | "id": "environment", 16 | "type": "pickString", 17 | "default": "staging", 18 | "options": ["staging", "prod"], 19 | "description": "Which environment to run against?" 20 | } 21 | ], 22 | "configurations": [ 23 | { 24 | "name": "Devfest23 Widgetbook", 25 | "request": "launch", 26 | "type": "dart", 27 | "program": "lib/widgetbook.dart", 28 | "flutterMode": "debug" 29 | }, 30 | { 31 | "name": "Devfest23 App", 32 | "request": "launch", 33 | "type": "dart", 34 | "program": "lib/main_${input:environment}.dart", 35 | "flutterMode": "${input:buildMode}" 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 GDG Lagos 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 | # DevFest Lagos 2023 2 | [![Codemagic build status](https://api.codemagic.io/apps/64da71850bfae33ab37b51d5/dev-android/status_badge.svg)](https://codemagic.io/apps/64da71850bfae33ab37b51d5/dev-android/latest_build) 3 | [![CI](https://github.com/Mastersam07/devfest23/actions/workflows/ci.yaml/badge.svg?branch=dev)](https://github.com/Mastersam07/devfest23/actions/workflows/ci.yaml) 4 | [![codecov](https://codecov.io/github/Mastersam07/devfest23/graph/badge.svg?token=C9p9ZBJjLt)](https://codecov.io/github/Mastersam07/devfest23) 5 | 6 |

7 | DevFest Lagos 2023 8 |

9 |

10 | Get it on Google Play 11 | Get it on the App Store 12 |

13 |

:rocket: Get Started

14 | 15 | ### Show some ❤️ and star the repo to support the project 16 | 17 | ## Overview 18 | 19 | Some description goes here. 20 | 21 | ## 💻 Technology 22 | 23 | - Flutter 24 | - Firebase 25 | - Widgetbook 26 | - Firebase emulator 27 | - Cloud functions 28 | 29 | ## Getting Started 30 | 31 | 1. [Fork the repository](https://github.com/Mastersam07/devfest23) and clone your fork locally 32 | 2. Install [Flutter 3.16.x](https://flutter.dev/docs/get-started/install) 33 | 3. Install [Android Studio/ IntelliJ / VSCode](https://flutter.dev/docs/development/tools/android-studio) 34 | 4. Run dev mode: 35 | ```dart 36 | flutter run -t lib/main_staging.dart 37 | ``` 38 | 5. In devmode, ensure to run the firebase emulator. For more on firebase emulator, check here. 39 | 5. Run prod mode: 40 | ```dart 41 | flutter run -t lib/main_prod.dart 42 | ``` 43 | 6. [Preparing release for android](https://flutter.dev/docs/deployment/android) 44 | 7. [Preparing release for iOS](https://flutter.dev/docs/deployment/ios) 45 | 46 | ## 📸 ScreenShots 47 | 48 | | iOS | Android | 49 | | :--------------------------: | :-------------------------------: | 50 | 51 | ## 🛠️ Building the project 52 | Some description goes here 53 | 54 | ## 📄 Contributing 55 | Some description goes here 56 | 57 | ## ❗️ Note 58 | Some note goes here 59 | 60 | ## 🤓 Contributors 61 | 62 | - [Samuel Abada](https://github.com/mastersam07) 63 | - [Sebastine Odeh](https://github.com/CoderNamedHendrick) 64 | 65 | ## License 66 | 67 | Project is published under the [MIT license](/LICENSE). 68 | Feel free to clone and modify repo as you want, but don't forget to add reference to authors 🙂 -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | depend_on_referenced_packages: false 26 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 27 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 28 | 29 | # Additional information about this file can be found at 30 | # https://dart.dev/guides/language/analysis-options 31 | -------------------------------------------------------------------------------- /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/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "163954857780", 4 | "project_id": "devfestlagos-2022", 5 | "storage_bucket": "devfestlagos-2022.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:163954857780:android:4d45af7f9a23b6c1ae3761", 11 | "android_client_info": { 12 | "package_name": "com.gdglagos.devfest22" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "163954857780-9ijr6due97v02ilkcp42hncnuocfqcp1.apps.googleusercontent.com", 18 | "client_type": 1, 19 | "android_info": { 20 | "package_name": "com.gdglagos.devfest22", 21 | "certificate_hash": "29faf42239b0f9b246501b3b49b9b87f162661d0" 22 | } 23 | }, 24 | { 25 | "client_id": "163954857780-vieq7ei97tna2tc755to85l2dge5hfu3.apps.googleusercontent.com", 26 | "client_type": 3 27 | } 28 | ], 29 | "api_key": [ 30 | { 31 | "current_key": "AIzaSyAyucrDWB2TK6bLcne65YcihOBVBHLjMtI" 32 | } 33 | ], 34 | "services": { 35 | "appinvite_service": { 36 | "other_platform_oauth_client": [ 37 | { 38 | "client_id": "163954857780-vieq7ei97tna2tc755to85l2dge5hfu3.apps.googleusercontent.com", 39 | "client_type": 3 40 | }, 41 | { 42 | "client_id": "163954857780-guggkvl3euja9qm3l3op70h19prpffj1.apps.googleusercontent.com", 43 | "client_type": 2, 44 | "ios_info": { 45 | "bundle_id": "com.gdglagos.devfest22", 46 | "app_store_id": "6444653663" 47 | } 48 | } 49 | ] 50 | } 51 | } 52 | }, 53 | { 54 | "client_info": { 55 | "mobilesdk_app_id": "1:163954857780:android:8f28e8d1eac80d7cae3761", 56 | "android_client_info": { 57 | "package_name": "com.gdglagos.devfestlg" 58 | } 59 | }, 60 | "oauth_client": [ 61 | { 62 | "client_id": "163954857780-4o1eqfetdrv3j6u6lc8ke1ik6ksv1cqs.apps.googleusercontent.com", 63 | "client_type": 1, 64 | "android_info": { 65 | "package_name": "com.gdglagos.devfestlg", 66 | "certificate_hash": "b11a16a6650a4e6f793620bc9f8a630f27406d3e" 67 | } 68 | }, 69 | { 70 | "client_id": "163954857780-vieq7ei97tna2tc755to85l2dge5hfu3.apps.googleusercontent.com", 71 | "client_type": 3 72 | } 73 | ], 74 | "api_key": [ 75 | { 76 | "current_key": "AIzaSyAyucrDWB2TK6bLcne65YcihOBVBHLjMtI" 77 | } 78 | ], 79 | "services": { 80 | "appinvite_service": { 81 | "other_platform_oauth_client": [ 82 | { 83 | "client_id": "163954857780-vieq7ei97tna2tc755to85l2dge5hfu3.apps.googleusercontent.com", 84 | "client_type": 3 85 | }, 86 | { 87 | "client_id": "163954857780-guggkvl3euja9qm3l3op70h19prpffj1.apps.googleusercontent.com", 88 | "client_type": 2, 89 | "ios_info": { 90 | "bundle_id": "com.gdglagos.devfest22", 91 | "app_store_id": "6444653663" 92 | } 93 | } 94 | ] 95 | } 96 | } 97 | } 98 | ], 99 | "configuration_version": "1" 100 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 16 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/gdglagos/devfestlg/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.gdglagos.devfestlg 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /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 | // START: FlutterFire Configuration 11 | classpath 'com.google.gms:google-services:4.3.15' 12 | // END: FlutterFire Configuration 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | rootProject.buildDir = '../build' 25 | subprojects { 26 | project.buildDir = "${rootProject.buildDir}/${project.name}" 27 | } 28 | subprojects { 29 | project.evaluationDependsOn(':app') 30 | } 31 | 32 | tasks.register("clean", Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /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 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/fonts/GoogleSansDisplay-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/fonts/GoogleSansDisplay-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/GoogleSansDisplay-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/fonts/GoogleSansDisplay-Regular.ttf -------------------------------------------------------------------------------- /assets/icons/google-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/images/devfest-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/devfest-banner.png -------------------------------------------------------------------------------- /assets/images/google-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/google-logo.png -------------------------------------------------------------------------------- /assets/images/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/logo-dark.png -------------------------------------------------------------------------------- /assets/images/logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/logo-light.png -------------------------------------------------------------------------------- /assets/images/lyft-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/lyft-logo.png -------------------------------------------------------------------------------- /assets/images/splash-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/splash-dark.png -------------------------------------------------------------------------------- /assets/images/splash-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/splash-light.png -------------------------------------------------------------------------------- /assets/images/spotify-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/spotify-logo.png -------------------------------------------------------------------------------- /assets/images/uber-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/assets/images/uber-logo.png -------------------------------------------------------------------------------- /codemagic.yaml: -------------------------------------------------------------------------------- 1 | workflows: 2 | dev-android: 3 | name: build, test & publish Android version 4 | instance_type: mac_mini_m1 5 | max_build_duration: 120 6 | environment: 7 | flutter: 3.16.0 8 | xcode: latest 9 | android_signing: 10 | - devfest23-key 11 | cache: 12 | cache_paths: 13 | - $FLUTTER_ROOT/.pub-cache 14 | - $HOME/.gradle/caches 15 | - $HOME/Library/Caches/CocoaPods 16 | triggering: 17 | branch_patterns: 18 | - pattern: 'dev' 19 | include: true 20 | events: 21 | - push 22 | cancel_previous_builds: true 23 | scripts: 24 | - name: Set up local.properties 25 | script: | 26 | echo "flutter.sdk=$HOME/programs/flutter" > "$CM_BUILD_DIR/android/local.properties" 27 | - name: Get Flutter packages 28 | script: | 29 | flutter packages pub get 30 | - name: Widgetbook generator 31 | script: | 32 | flutter pub run build_runner build --delete-conflicting-outputs 33 | - name: Flutter analyze 34 | ignore_failure: true 35 | script: | 36 | flutter analyze . 37 | - name: Flutter unit tests 38 | script: | 39 | flutter test 40 | ignore_failure: true 41 | - name: Build app binary 42 | script: | 43 | flutter build apk --release -t lib/main_prod.dart --obfuscate --split-debug-info=. 44 | artifacts: 45 | - build/**/outputs/apk/**/*.apk 46 | publishing: 47 | email: 48 | recipients: 49 | - abadasamuelosp@gmail.com 50 | - sebastinesoacatp@gmail.com 51 | notify: 52 | success: true # To receive a notification when a build succeeds 53 | failure: false # To not receive a notification when a build fails 54 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "emulators": { 3 | "auth": { 4 | "port": 9099 5 | }, 6 | "ui": { 7 | "enabled": true 8 | }, 9 | "singleProjectMode": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /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? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig" 3 | #include "Generated.xcconfig" 4 | -------------------------------------------------------------------------------- /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 | 32 | pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '10.16.0' 33 | use_frameworks! 34 | use_modular_headers! 35 | 36 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 37 | target 'RunnerTests' do 38 | inherit! :search_paths 39 | end 40 | end 41 | 42 | post_install do |installer| 43 | installer.pods_project.targets.each do |target| 44 | flutter_additional_ios_build_settings(target) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /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.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" : "appstore-20@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom": "iphone", 12 | "filename" : "appstore-20@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size" : "20x20", 17 | "idiom": "ipad", 18 | "filename" : "appstore-20.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size" : "20x20", 23 | "idiom": "ipad", 24 | "filename" : "appstore-20@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "appstore-29@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "29x29", 35 | "idiom" : "iphone", 36 | "filename" : "appstore-29@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "appstore-40@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "40x40", 47 | "idiom" : "iphone", 48 | "filename" : "appstore-40@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "appstore-60@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "appstore-60@3x.png", 61 | "scale" : "3x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "appstore-29.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "appstore-29@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "appstore-40.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "appstore-40@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "appstore-76.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "appstore-76@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "appstore-83.5@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "appstore-1024.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-20.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/AppIcon.appiconset/appstore-83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /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/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 163954857780-u8p3v1ug6scdo4n91l56ooesm3q9u6in.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.163954857780-u8p3v1ug6scdo4n91l56ooesm3q9u6in 9 | ANDROID_CLIENT_ID 10 | 163954857780-4o1eqfetdrv3j6u6lc8ke1ik6ksv1cqs.apps.googleusercontent.com 11 | API_KEY 12 | AIzaSyBrjQGvrhDQTdVk5-DInHkDLMiOgvzHoxI 13 | GCM_SENDER_ID 14 | 163954857780 15 | PLIST_VERSION 16 | 1 17 | BUNDLE_ID 18 | com.gdglagos.devfestlg 19 | PROJECT_ID 20 | devfestlagos-2022 21 | STORAGE_BUCKET 22 | devfestlagos-2022.appspot.com 23 | IS_ADS_ENABLED 24 | 25 | IS_ANALYTICS_ENABLED 26 | 27 | IS_APPINVITE_ENABLED 28 | 29 | IS_GCM_ENABLED 30 | 31 | IS_SIGNIN_ENABLED 32 | 33 | GOOGLE_APP_ID 34 | 1:163954857780:ios:e9c87dee27844b2cae3761 35 | 36 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | DevFest Lagos'23 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | devfest23 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | UIStatusBarHidden 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:163954857780:ios:e9c87dee27844b2cae3761", 5 | "FIREBASE_PROJECT_ID": "devfestlagos-2022", 6 | "GCM_SENDER_ID": "163954857780" 7 | } -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'core/router/module_provider.dart'; 2 | import 'core/router/router.dart'; 3 | import 'core/size_util.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_native_splash/flutter_native_splash.dart'; 6 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 7 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 8 | 9 | import 'core/providers/providers.dart'; 10 | import 'core/router/navigator.dart'; 11 | import 'core/themes/themes.dart'; 12 | 13 | class DevfestApp extends ConsumerStatefulWidget { 14 | const DevfestApp({super.key}); 15 | 16 | @override 17 | ConsumerState createState() => _DevfestAppState(); 18 | } 19 | 20 | class _DevfestAppState extends ConsumerState { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | FlutterNativeSplash.remove(); 25 | 26 | WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((_) { 27 | ref.read(themeManagerProvider.notifier).getThemeMode(context); 28 | }); 29 | } 30 | 31 | // This widget is the root of your application. 32 | @override 33 | Widget build(BuildContext context) { 34 | return ScreenUtilInit( 35 | designSize: designSize, 36 | minTextAdapt: true, 37 | builder: (_, child) { 38 | return MaterialApp( 39 | debugShowCheckedModeBanner: false, 40 | themeMode: ref.watch(themeManagerProvider), 41 | navigatorKey: AppNavigator.getKey(Module.general), 42 | onGenerateRoute: AppRouter.generateRoutes, 43 | onUnknownRoute: (settings) => MaterialPageRoute( 44 | settings: settings, 45 | builder: (_) => Scaffold( 46 | body: Center( 47 | child: Text('No route defined for ${settings.name}'), 48 | ), 49 | ), 50 | ), 51 | routes: AppRouter.routes, 52 | theme: ThemeData( 53 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 54 | useMaterial3: true, 55 | textTheme: TextTheme( 56 | bodyMedium: TextStyle( 57 | fontFamily: 'Google Sans', 58 | color: ref.watch(textColorProvider), 59 | ), 60 | ), 61 | extensions: >[ 62 | /// Use the below format for raw theme data 63 | /// DevFestTheme(textTheme: DevfestTextTheme()), 64 | DevFestTheme.light(), 65 | ], 66 | ), 67 | darkTheme: ThemeData( 68 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 69 | useMaterial3: true, 70 | textTheme: TextTheme( 71 | bodyMedium: TextStyle( 72 | fontFamily: 'Google Sans', 73 | color: ref.watch(textColorProvider), 74 | ), 75 | ), 76 | extensions: >[ 77 | /// Use the below format for raw theme data 78 | /// DevFestTheme(textTheme: DevfestTextTheme()), 79 | DevFestTheme.dark(), 80 | ], 81 | ), 82 | ); 83 | }, 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/core/constants.dart: -------------------------------------------------------------------------------- 1 | abstract class Constants { 2 | static final day1 = DateTime(2023, 11, 24); 3 | static final day2 = DateTime(2023, 11, 25); 4 | 5 | static const kAnimationDur = Duration(milliseconds: 300); 6 | static const kShimmerDur = Duration(milliseconds: 800); 7 | 8 | static const horizontalMargin = 24.0; 9 | static const horizontalGutter = 8.0; 10 | static const largeVerticalGutter = 24.0; 11 | static const verticalGutter = 16.0; 12 | static const smallVerticalGutter = 8.0; 13 | 14 | static const iconWeight = 600.0; 15 | } 16 | -------------------------------------------------------------------------------- /lib/core/data/data.dart: -------------------------------------------------------------------------------- 1 | export 'devfest_repository.dart'; 2 | export 'dto/dto.dart'; 3 | -------------------------------------------------------------------------------- /lib/core/data/devfest_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/devfest_repository_impl.dart'; 2 | import 'package:devfest23/core/data/dto/dto.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | import '../network/network.dart'; 6 | 7 | abstract interface class DevfestRepository { 8 | const DevfestRepository(); 9 | 10 | Future> logout(); 11 | 12 | Future> rsvpLogin(LoginRequestDto dto); 13 | 14 | Future> fetchAgendas(); 15 | 16 | Future> fetchSpeakers(); 17 | 18 | Future> fetchSessions(); 19 | 20 | Future addToRSVP(RSVPSessionRequestDto dto); 21 | 22 | Future removeFromRSVP(RSVPSessionRequestDto dto); 23 | 24 | Future updateUserDeviceToken(UpdateTokenRequestDto dto); 25 | 26 | Future addMultipleRSVPs(AddMultipleRSVPRequestDto dto); 27 | 28 | Future>> fetchRSVPSessions(); 29 | 30 | Future> fetchSessionCategories(); 31 | } 32 | 33 | final devfestRepositoryProvider = 34 | Provider.autoDispose((ref) { 35 | return const DevfestRepositoryImplementation(); 36 | }); 37 | -------------------------------------------------------------------------------- /lib/core/data/dto/add_multiple_rsvp_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | final class AddMultipleRSVPRequestDto extends Equatable { 4 | final List sessionIds; 5 | 6 | const AddMultipleRSVPRequestDto({required this.sessionIds}); 7 | 8 | Map toJson() => {'sessionIds': sessionIds}; 9 | 10 | @override 11 | List get props => [sessionIds]; 12 | } 13 | -------------------------------------------------------------------------------- /lib/core/data/dto/agenda_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | final class AgendaResponseDto extends Equatable { 4 | final List agendas; 5 | 6 | const AgendaResponseDto({required this.agendas}); 7 | 8 | factory AgendaResponseDto.fromJson(dynamic json) => switch (json) { 9 | final List list? => AgendaResponseDto( 10 | agendas: list 11 | .map((e) => Agenda.fromJson(e as Map)) 12 | .toList(), 13 | ), 14 | _ => const AgendaResponseDto(agendas: []), 15 | }; 16 | 17 | @override 18 | List get props => [agendas]; 19 | } 20 | 21 | final class Agenda extends Equatable { 22 | final bool isBreakout; 23 | final String schedule; 24 | final int order; 25 | final String facilitator; 26 | final String duration; 27 | final String time; 28 | final List sessions; 29 | 30 | const Agenda({ 31 | required this.isBreakout, 32 | required this.schedule, 33 | required this.order, 34 | required this.facilitator, 35 | required this.duration, 36 | required this.time, 37 | required this.sessions, 38 | }); 39 | 40 | const Agenda.empty() 41 | : this( 42 | isBreakout: false, 43 | schedule: '', 44 | order: -1, 45 | facilitator: '', 46 | duration: '', 47 | time: '', 48 | sessions: const [], 49 | ); 50 | 51 | factory Agenda.fromJson(Map json) => Agenda( 52 | isBreakout: json['isBreakout'] ?? '', 53 | schedule: json['schedule'] ?? '', 54 | order: json['order'] ?? -1, 55 | facilitator: json['facilitator'] ?? '', 56 | duration: json['duration'] ?? '', 57 | time: json['time'] ?? '', 58 | sessions: switch (json['sessions']) { 59 | final List list? => list 60 | .map((e) => AgendaSession.fromJson(e as Map)) 61 | .toList(), 62 | _ => const [], 63 | }, 64 | ); 65 | 66 | @override 67 | List get props => [ 68 | isBreakout, 69 | schedule, 70 | order, 71 | facilitator, 72 | duration, 73 | time, 74 | ]; 75 | } 76 | 77 | final class AgendaSession extends Equatable { 78 | final String schedule; 79 | final String venue; 80 | final String time; 81 | final String facilitator; 82 | 83 | const AgendaSession({ 84 | required this.schedule, 85 | required this.venue, 86 | required this.time, 87 | required this.facilitator, 88 | }); 89 | 90 | factory AgendaSession.fromJson(Map json) => AgendaSession( 91 | schedule: json['schedule'] ?? '', 92 | venue: json['venue'] ?? '', 93 | time: json['time'] ?? '', 94 | facilitator: json['facilitator'] ?? '', 95 | ); 96 | 97 | @override 98 | List get props => [schedule, venue, time, facilitator]; 99 | } 100 | -------------------------------------------------------------------------------- /lib/core/data/dto/categories_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | final class CategoriesResponseDto extends Equatable { 4 | final List categories; 5 | 6 | const CategoriesResponseDto({required this.categories}); 7 | 8 | factory CategoriesResponseDto.fromJson(dynamic json) => switch (json) { 9 | final List list? => CategoriesResponseDto( 10 | categories: list 11 | .map((e) => Category.fromJson(e as Map)) 12 | .toList(), 13 | ), 14 | _ => const CategoriesResponseDto(categories: []), 15 | }; 16 | 17 | @override 18 | List get props => [categories]; 19 | } 20 | 21 | final class Category extends Equatable { 22 | final String imageUrl; 23 | final String name; 24 | 25 | const Category({required this.imageUrl, required this.name}); 26 | 27 | factory Category.fromJson(Map json) => Category( 28 | imageUrl: json['imageUrl'] ?? '', 29 | name: json['name'] ?? '', 30 | ); 31 | 32 | @override 33 | List get props => [imageUrl, name]; 34 | } 35 | -------------------------------------------------------------------------------- /lib/core/data/dto/dto.dart: -------------------------------------------------------------------------------- 1 | export 'agenda_response.dart'; 2 | export 'rsvp_sessions_response.dart'; 3 | export 'sessions_response.dart'; 4 | export 'speakers_response.dart'; 5 | export 'rsvp_session_request.dart'; 6 | export 'categories_response.dart'; 7 | export 'add_multiple_rsvp_request.dart'; 8 | export 'login_request.dart'; 9 | export 'update_token_request.dart'; 10 | -------------------------------------------------------------------------------- /lib/core/data/dto/login_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | final class LoginRequestDto extends Equatable { 4 | final String email; 5 | final String password; 6 | 7 | const LoginRequestDto({required this.email, required this.password}); 8 | 9 | @override 10 | List get props => [email, password]; 11 | } 12 | -------------------------------------------------------------------------------- /lib/core/data/dto/rsvp_session_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | final class RSVPSessionRequestDto extends Equatable { 4 | final String sessionId; 5 | 6 | const RSVPSessionRequestDto({required this.sessionId}); 7 | 8 | Map toJson() => {'sessionId': sessionId}; 9 | 10 | @override 11 | List get props => [sessionId]; 12 | } 13 | -------------------------------------------------------------------------------- /lib/core/data/dto/rsvp_sessions_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | final class RSVPSessionsResponseDto extends Equatable { 4 | const RSVPSessionsResponseDto(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | -------------------------------------------------------------------------------- /lib/core/data/dto/speakers_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../constants.dart'; 4 | 5 | final class SpeakersResponseDto extends Equatable { 6 | final List speakers; 7 | 8 | const SpeakersResponseDto({required this.speakers}); 9 | 10 | factory SpeakersResponseDto.fromJson(dynamic json) => switch (json) { 11 | final List list? => SpeakersResponseDto( 12 | speakers: list 13 | .map((e) => Speaker.fromJson(e as Map)) 14 | .toList(), 15 | ), 16 | _ => const SpeakersResponseDto(speakers: []), 17 | }; 18 | 19 | @override 20 | List get props => [speakers]; 21 | } 22 | 23 | final class Speaker extends Equatable { 24 | final String twitter; 25 | final String github; 26 | final String role; 27 | final String organization; 28 | final String name; 29 | final String bio; 30 | final String linkedIn; 31 | final String avatar; 32 | final String email; 33 | final int order; 34 | final String category; 35 | final String currentSession; 36 | final String currentSessionId; 37 | final DateTime sessionDate; 38 | 39 | const Speaker({ 40 | required this.twitter, 41 | required this.github, 42 | required this.role, 43 | required this.organization, 44 | required this.name, 45 | required this.bio, 46 | required this.linkedIn, 47 | required this.avatar, 48 | required this.email, 49 | required this.order, 50 | required this.category, 51 | required this.currentSession, 52 | required this.currentSessionId, 53 | required this.sessionDate, 54 | }); 55 | 56 | Speaker.empty() 57 | : this( 58 | twitter: '', 59 | github: '', 60 | role: '', 61 | organization: '', 62 | name: '', 63 | bio: '', 64 | linkedIn: '', 65 | avatar: '', 66 | email: '', 67 | order: 0, 68 | category: '', 69 | currentSession: '', 70 | currentSessionId: '', 71 | sessionDate: DateTime.now(), 72 | ); 73 | 74 | factory Speaker.fromJson(Map json) => Speaker( 75 | twitter: json['twitter'] ?? '', 76 | github: json['github'] ?? '', 77 | role: json['role'] ?? '', 78 | organization: json['organization'] ?? '', 79 | name: json['name'] ?? '', 80 | bio: json['bio'] ?? '', 81 | linkedIn: json['linkedIn'] ?? '', 82 | avatar: json['avatar'] ?? '', 83 | email: json['email'] ?? '', 84 | order: json['order'] ?? 0, 85 | category: json['category'] ?? '', 86 | currentSession: json['currentSession'] ?? '', 87 | currentSessionId: json['currentSessionId'] ?? '', 88 | sessionDate: () { 89 | final date = 90 | DateTime.tryParse(json['sessionDate'] as String? ?? '') ?? 91 | Constants.day1; 92 | 93 | return DateTime(date.year, date.month, date.day); 94 | }(), 95 | ); 96 | 97 | @override 98 | List get props => [ 99 | twitter, 100 | github, 101 | role, 102 | organization, 103 | name, 104 | bio, 105 | linkedIn, 106 | avatar, 107 | email, 108 | order, 109 | category, 110 | currentSession, 111 | currentSessionId, 112 | sessionDate, 113 | ]; 114 | } 115 | -------------------------------------------------------------------------------- /lib/core/data/dto/update_token_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | final class UpdateTokenRequestDto extends Equatable { 4 | final String deviceToken; 5 | 6 | const UpdateTokenRequestDto({required this.deviceToken}); 7 | 8 | Map toJson() => { 9 | 'deviceToken': deviceToken, 10 | }; 11 | 12 | @override 13 | List get props => [deviceToken]; 14 | } 15 | -------------------------------------------------------------------------------- /lib/core/enums/devfest_day.dart: -------------------------------------------------------------------------------- 1 | enum DevfestDay { day1, day2 } 2 | -------------------------------------------------------------------------------- /lib/core/enums/status.dart: -------------------------------------------------------------------------------- 1 | enum Status { 2 | success('success'), 3 | error('error'); 4 | 5 | const Status(this.json); 6 | 7 | final String json; 8 | } 9 | 10 | extension StatusX on String { 11 | Status get status { 12 | return Status.values.firstWhere( 13 | (element) => element.json == this, 14 | orElse: () => throw Exception('No status type defined for $this'), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/core/enums/tab_item.dart: -------------------------------------------------------------------------------- 1 | enum TabItem { home, schedule, speakers, favourites, more } 2 | -------------------------------------------------------------------------------- /lib/core/exceptions/client_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/exceptions.dart'; 2 | 3 | final class ClientException extends DevfestException { 4 | final String exceptionMessage; 5 | 6 | const ClientException({required this.exceptionMessage}); 7 | 8 | @override 9 | String toString() => exceptionMessage; 10 | } 11 | -------------------------------------------------------------------------------- /lib/core/exceptions/devfest_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/exceptions.dart'; 2 | import 'package:dio/dio.dart'; 3 | 4 | base class DevfestException implements Exception { 5 | const DevfestException(); 6 | 7 | factory DevfestException.fromErrorResponse(Response error) { 8 | try { 9 | if (error.statusCode! >= 400 && error.statusCode! < 500) { 10 | return ServerException.fromJson(error.data as Map); 11 | } 12 | 13 | if (error.statusCode! >= 500) { 14 | return const ClientException( 15 | exceptionMessage: 16 | 'We encountered a problem reaching the server. Please try again', 17 | ); 18 | } 19 | 20 | return const ClientException(exceptionMessage: 'An error occurred.'); 21 | } catch (_) { 22 | return const ClientException( 23 | exceptionMessage: 'An error occurred.. Please try again'); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/core/exceptions/empty_exception.dart: -------------------------------------------------------------------------------- 1 | import 'devfest_exception.dart'; 2 | 3 | final class EmptyException extends DevfestException { 4 | const EmptyException(); 5 | 6 | @override 7 | String toString() => ''; 8 | } 9 | -------------------------------------------------------------------------------- /lib/core/exceptions/exceptions.dart: -------------------------------------------------------------------------------- 1 | export 'devfest_exception.dart'; 2 | export 'empty_exception.dart'; 3 | export 'client_exception.dart'; 4 | export 'object_parse_exception.dart'; 5 | export 'server_exception.dart'; 6 | export 'user_not_registered_exception.dart'; 7 | export 'invalid_ticket_id_exception.dart'; 8 | -------------------------------------------------------------------------------- /lib/core/exceptions/invalid_ticket_id_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/devfest_exception.dart'; 2 | 3 | final class InvalidTicketIdException extends DevfestException { 4 | const InvalidTicketIdException(); 5 | 6 | @override 7 | String toString() { 8 | return 'Invalid ticket Id. Please ensure the ticket id(password) provided is correct'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/core/exceptions/object_parse_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/exceptions.dart'; 2 | 3 | final class ObjectParseException extends DevfestException { 4 | const ObjectParseException(this.stacktraceInfo); 5 | 6 | final StackTrace? stacktraceInfo; 7 | 8 | @override 9 | String toString() => 10 | 'We encountered a problem trying to reach the server. We are working to fix it...'; 11 | } 12 | -------------------------------------------------------------------------------- /lib/core/exceptions/server_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/exceptions.dart'; 2 | 3 | final class ServerException extends DevfestException { 4 | final String message; 5 | final String error; 6 | 7 | const ServerException({required this.message, required this.error}); 8 | 9 | factory ServerException.fromJson(Map json) => 10 | ServerException( 11 | message: json['message'] ?? '', 12 | error: json['error'] ?? '', 13 | ); 14 | 15 | @override 16 | String toString() => message; 17 | } 18 | -------------------------------------------------------------------------------- /lib/core/exceptions/user_not_registered_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/devfest_exception.dart'; 2 | 3 | final class UserNotRegisteredException extends DevfestException { 4 | const UserNotRegisteredException(); 5 | 6 | @override 7 | String toString() { 8 | return 'Your email is not in our database. Please register now.'; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/core/icons.dart: -------------------------------------------------------------------------------- 1 | abstract final class AppIcons { 2 | static const googleLogo = 'assets/icons/google-logo.svg'; 3 | static const devfestLogo = 'assets/icons/logo.svg'; 4 | } 5 | -------------------------------------------------------------------------------- /lib/core/images.dart: -------------------------------------------------------------------------------- 1 | abstract final class AppImages { 2 | static const devfestLogoLight = 'assets/images/logo-light.png'; 3 | static const devfestLogoDark = 'assets/images/logo-dark.png'; 4 | 5 | static const splashDark = 'assets/images/splash-dark.png'; 6 | static const splashLight = 'assets/images/splash-light.png'; 7 | 8 | static const googleLogo = 'assets/images/google-logo.png'; 9 | static const spotifyLogo = 'assets/images/spotify-logo.png'; 10 | static const uberLogo = 'assets/images/uber-logo.png'; 11 | static const lyftLogo = 'assets/images/lyft-logo.png'; 12 | 13 | static const devfestBanner = 'assets/images/devfest-banner.png'; 14 | } 15 | -------------------------------------------------------------------------------- /lib/core/network/data_transformer.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/enums/status.dart'; 2 | import 'package:devfest23/core/exceptions/exceptions.dart'; 3 | import 'package:dio/dio.dart'; 4 | import 'package:either_dart/either.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | 7 | typedef EitherExceptionOr = Either; 8 | 9 | Future> processData( 10 | E Function(dynamic data) transformer, 11 | EitherExceptionOr response, 12 | ) async { 13 | if (response.isLeft) return Left(response.left); 14 | 15 | return await compute>( 16 | (data) => _transformResponse(data, (p0) => transformer(p0)), 17 | response.right!.data, 18 | ); 19 | } 20 | 21 | EitherExceptionOr _transformResponse( 22 | dynamic data, E Function(dynamic) transform) { 23 | try { 24 | final json = data as Map; 25 | switch ((json['status'] as String).status) { 26 | case Status.success: 27 | return Right(transform(json['data'])); 28 | case Status.error: 29 | return Left(ServerException.fromJson(json)); 30 | } 31 | } on TypeError catch (e) { 32 | return Left(ObjectParseException(e.stackTrace)); 33 | } on Exception catch (e) { 34 | return Left(ClientException(exceptionMessage: e.toString())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/core/network/network.dart: -------------------------------------------------------------------------------- 1 | export 'network_client.dart'; 2 | export 'data_transformer.dart'; 3 | -------------------------------------------------------------------------------- /lib/core/providers/current_tab_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | 3 | final appCurrentTab = StateProvider((_) => 0); 4 | -------------------------------------------------------------------------------- /lib/core/providers/providers.dart: -------------------------------------------------------------------------------- 1 | export 'theme_providers.dart'; 2 | -------------------------------------------------------------------------------- /lib/core/providers/theme_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | class ThemeManagerNotifier extends StateNotifier { 7 | ThemeManagerNotifier() : super(ThemeMode.system); 8 | 9 | static const _themeModeKey = 'theme_mode'; 10 | 11 | Future updateThemeMode(ThemeMode theme) async { 12 | final prefs = await SharedPreferences.getInstance(); 13 | await prefs.setString(_themeModeKey, theme.name); 14 | 15 | state = theme; 16 | } 17 | 18 | Future getThemeMode(BuildContext context) async { 19 | final prefs = await SharedPreferences.getInstance(); 20 | final themeModeName = prefs.getString(_themeModeKey); 21 | 22 | if (!mounted) return; 23 | 24 | await updateThemeMode(ThemeMode.values 25 | .firstWhereOrNull((element) => element.name == themeModeName) ?? 26 | _getThemeFromBrightness(context)); 27 | } 28 | 29 | ThemeMode _getThemeFromBrightness(BuildContext context) { 30 | return MediaQuery.platformBrightnessOf(context) == Brightness.dark 31 | ? ThemeMode.dark 32 | : ThemeMode.light; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/core/providers/theme_providers.dart: -------------------------------------------------------------------------------- 1 | import 'theme_manager.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | import '../themes/themes.dart'; 6 | 7 | final themeManagerProvider = 8 | StateNotifierProvider.autoDispose((ref) { 9 | return ThemeManagerNotifier(); 10 | }); 11 | final isDarkProvider = Provider.autoDispose((ref) { 12 | return ref.watch(themeManagerProvider) == ThemeMode.dark; 13 | }); 14 | 15 | final textColorProvider = Provider.autoDispose((ref) { 16 | return ref.watch(themeManagerProvider) == ThemeMode.dark 17 | ? DevfestColors.background 18 | : DevfestColors.grey0; 19 | }); 20 | -------------------------------------------------------------------------------- /lib/core/router/module_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | enum Module { general, home, schedule, speakers, favourites, more } 4 | 5 | class ModuleProvider extends InheritedWidget { 6 | final Module module; 7 | 8 | const ModuleProvider({ 9 | super.key, 10 | required this.module, 11 | required super.child, 12 | }); 13 | 14 | @override 15 | bool updateShouldNotify(ModuleProvider oldWidget) { 16 | return module != oldWidget.module; 17 | } 18 | 19 | static Module of(BuildContext context) { 20 | return context 21 | .dependOnInheritedWidgetOfExactType() 22 | ?.module ?? 23 | Module.general; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/router/router.dart: -------------------------------------------------------------------------------- 1 | import 'routes.dart'; 2 | import '../../features/agenda/pages/agenda_base.dart'; 3 | import '../../features/favourites/pages/favourites_base.dart'; 4 | import '../../features/schedule/pages/schedule_base.dart'; 5 | import '../../features/splash/splash.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:regex_router/regex_router.dart'; 8 | import '../../features/home/pages/home.dart'; 9 | import '../../features/more/pages/more_base.dart'; 10 | import '../../features/onboarding/pages/authentication.dart'; 11 | import '../../features/onboarding/pages/onboarding.dart'; 12 | 13 | import 'package:collection/collection.dart'; 14 | 15 | import '../../features/speakers/page/speakers_base.dart'; 16 | import '../enums/devfest_day.dart'; 17 | import '../enums/tab_item.dart'; 18 | 19 | final router = RegexRouter.create({ 20 | // Access "object" arguments from `NavigatorState.pushNamed`. 21 | "${RoutePaths.onboarding}/${RoutePaths.auth}(\\?result=(?success|pending|failed))?": 22 | (context, args) { 23 | final String? stateId = args.pathArgs['result']; 24 | AuthState? authState; 25 | if (stateId != null) { 26 | authState = AuthState.values.firstWhereOrNull( 27 | (stateItem) => stateItem == AuthState.values.byName(stateId), 28 | ); 29 | } 30 | return AuthenticationPage(authState: authState); 31 | }, 32 | 'app/:tab': (context, args) { 33 | final tabId = args.pathArgs['tab']; 34 | final tabItem = TabItem.values.firstWhere( 35 | (tabItem) => tabItem == TabItem.values.byName(tabId!), 36 | orElse: (() => throw Exception('Tab not found: $tabId')), 37 | ); 38 | 39 | const dayItem = DevfestDay.day1; 40 | 41 | final page = switch (tabItem) { 42 | TabItem.home => const AgendaView(initialDay: dayItem), 43 | TabItem.schedule => const ScheduleView(initialDay: dayItem), 44 | TabItem.speakers => const SpeakersView(initialDay: dayItem), 45 | TabItem.favourites => const FavouritesView(initialDay: dayItem), 46 | TabItem.more => const MoreView(), 47 | }; 48 | 49 | return AppHome( 50 | key: page.key, 51 | tab: tabItem, 52 | ); 53 | } 54 | }); 55 | 56 | abstract class AppRouter { 57 | static Map get routes { 58 | return { 59 | RoutePaths.app: (context) => const SplashPage(), 60 | RoutePaths.onboarding: (context) => const OnboardingPage(), 61 | }; 62 | } 63 | 64 | static Route? generateRoutes(RouteSettings settings) { 65 | return router.generateRoute(settings); 66 | } 67 | 68 | static PageRoute getPageRoute({ 69 | required RouteSettings settings, 70 | required Widget view, 71 | }) { 72 | return MaterialPageRoute(settings: settings, builder: (_) => view); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/core/router/routes.dart: -------------------------------------------------------------------------------- 1 | /// All route paths 2 | class RoutePaths { 3 | static const app = '/'; 4 | static const onboarding = '/onboarding'; 5 | static const home = 'home'; 6 | static const auth = 'auth'; 7 | static const session = '/session'; 8 | static const speakers = '/speakers'; 9 | static const profile = '/profile'; 10 | } 11 | 12 | /// All route names 13 | class RouteNames { 14 | static const onboarding = 'onboarding'; 15 | static const home = 'home'; 16 | static const auth = 'auth'; 17 | static const session = 'session'; 18 | static const speakers = 'speakers'; 19 | static const profile = 'profile'; 20 | } 21 | -------------------------------------------------------------------------------- /lib/core/services/firebase_notification_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/services/local_notification_manager.dart'; 2 | import 'package:firebase_core/firebase_core.dart'; 3 | import 'package:firebase_messaging/firebase_messaging.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | 6 | class FirebaseNotificationManager { 7 | FirebaseMessaging messaging = FirebaseMessaging.instance; 8 | 9 | /// Define a top-level named handler which background/terminated messages will call. 10 | /// To verify things are working, check out the native platform logs. 11 | static Future _firebaseMessagingBackgroundHandler( 12 | RemoteMessage message) async { 13 | // If you're going to use other Firebase services in the background, such as Firestore, 14 | // make sure you call `initializeApp` before using other Firebase services. 15 | await Firebase.initializeApp(); 16 | } 17 | 18 | Future registerNotification() async { 19 | await Firebase.initializeApp(); 20 | messaging.getInitialMessage().then((RemoteMessage? message) { 21 | if (message != null) { 22 | debugPrint('Notification Messages: ${message.data.toString()}'); 23 | } else { 24 | debugPrint('Empty Messages: $message'); 25 | } 26 | }); 27 | await LocalNotificationManager.initialiseHeadsUpNotificationAndroid(); 28 | FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); 29 | // request permission from user to display notification 30 | NotificationSettings settings = await messaging.requestPermission( 31 | alert: true, 32 | announcement: false, 33 | badge: true, 34 | carPlay: false, 35 | criticalAlert: false, 36 | provisional: false, 37 | sound: true, 38 | ); 39 | 40 | /// Update the iOS foreground notification presentation options to allow 41 | /// heads up notifications. 42 | await FirebaseMessaging.instance 43 | .setForegroundNotificationPresentationOptions( 44 | alert: true, 45 | badge: true, 46 | sound: true, 47 | ); 48 | 49 | /// Check if Permission to request Notification has been granted 50 | if (settings.authorizationStatus == AuthorizationStatus.authorized) { 51 | FirebaseMessaging.onMessage.listen((RemoteMessage message) { 52 | RemoteNotification? notification = message.notification; 53 | AndroidNotification? android = message.notification?.android; 54 | 55 | if (notification != null && android != null) { 56 | // Set Notification Message Object 57 | NotificationDto notificationMessage = NotificationDto( 58 | id: notification.hashCode, 59 | title: notification.title, 60 | body: notification.body, 61 | ); 62 | 63 | // Call Notification Manager to show Notification 64 | LocalNotificationManager.showNotification(notificationMessage); 65 | } 66 | }); 67 | } 68 | } 69 | 70 | Future get deviceToken async { 71 | return await messaging.getToken(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/core/services/local_notification_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 3 | 4 | final class LocalNotificationManager { 5 | static Future initialiseHeadsUpNotificationAndroid() async { 6 | await _flutterLocalNotificationsPlugin 7 | .resolvePlatformSpecificImplementation< 8 | AndroidFlutterLocalNotificationsPlugin>() 9 | ?.createNotificationChannel(_androidChannel); 10 | } 11 | 12 | static const AndroidNotificationChannel _androidChannel = 13 | AndroidNotificationChannel( 14 | 'high_importance_channel', 15 | 'High Importance Notifications', 16 | description: 'This channel is used for important notifications.', 17 | importance: Importance.max, 18 | ); 19 | 20 | static const _initializationSettingsAndroid = 21 | AndroidInitializationSettings('@mipmap/ic_launcher'); 22 | static const _initializationSettingsDarwin = DarwinInitializationSettings( 23 | requestSoundPermission: true, 24 | requestBadgePermission: true, 25 | requestAlertPermission: true, 26 | ); 27 | 28 | static final FlutterLocalNotificationsPlugin 29 | _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); 30 | 31 | static void showNotification(NotificationDto message) { 32 | _flutterLocalNotificationsPlugin.initialize( 33 | const InitializationSettings( 34 | android: _initializationSettingsAndroid, 35 | iOS: _initializationSettingsDarwin, 36 | ), 37 | ); 38 | 39 | _flutterLocalNotificationsPlugin.show( 40 | message.id, 41 | message.title, 42 | message.body, 43 | NotificationDetails( 44 | android: AndroidNotificationDetails( 45 | _androidChannel.id, 46 | _androidChannel.name, 47 | channelDescription: _androidChannel.description, 48 | priority: Priority.high, 49 | importance: _androidChannel.importance, 50 | ), 51 | iOS: const DarwinNotificationDetails( 52 | presentAlert: true, 53 | presentBadge: true, 54 | presentSound: true, 55 | badgeNumber: 0, 56 | attachments: null, 57 | ), 58 | ), 59 | ); 60 | } 61 | } 62 | 63 | final class NotificationDto extends Equatable { 64 | final int id; 65 | final String? title; 66 | final String? body; 67 | 68 | const NotificationDto({required this.id, this.title, this.body}); 69 | 70 | @override 71 | List get props => [id, title, body]; 72 | } 73 | -------------------------------------------------------------------------------- /lib/core/size_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const designSize = Size(430, 960); 4 | -------------------------------------------------------------------------------- /lib/core/themes/bottom_nav_theme.dart: -------------------------------------------------------------------------------- 1 | import 'colors.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | 5 | @immutable 6 | class DevfestBottomNavTheme extends ThemeExtension { 7 | final TextStyle labelStyle; 8 | final Color selectedColor; 9 | final Color unselectedColor; 10 | 11 | const DevfestBottomNavTheme._({ 12 | required this.labelStyle, 13 | required this.selectedColor, 14 | required this.unselectedColor, 15 | }); 16 | 17 | DevfestBottomNavTheme.light() 18 | : this._( 19 | labelStyle: TextStyle( 20 | fontSize: 14.sp, 21 | fontWeight: FontWeight.w500, 22 | height: 1.27, 23 | fontFamily: 'Google Sans', 24 | ), 25 | selectedColor: DevfestColors.blue, 26 | unselectedColor: DevfestColors.grey70, 27 | ); 28 | 29 | DevfestBottomNavTheme.dark() 30 | : this._( 31 | labelStyle: TextStyle( 32 | fontSize: 14.sp, 33 | fontWeight: FontWeight.w500, 34 | fontFamily: 'Google Sans', 35 | height: 1.27, 36 | ), 37 | selectedColor: DevfestColors.blue, 38 | unselectedColor: DevfestColors.grey70, 39 | ); 40 | 41 | @override 42 | DevfestBottomNavTheme copyWith({ 43 | TextStyle? labelStyle, 44 | Color? selectedColor, 45 | Color? unselectedColor, 46 | }) { 47 | return DevfestBottomNavTheme._( 48 | labelStyle: labelStyle ?? this.labelStyle, 49 | selectedColor: selectedColor ?? this.selectedColor, 50 | unselectedColor: unselectedColor ?? this.unselectedColor, 51 | ); 52 | } 53 | 54 | @override 55 | DevfestBottomNavTheme lerp( 56 | covariant ThemeExtension? other, double t) { 57 | if (other is! DevfestBottomNavTheme) return this; 58 | 59 | return DevfestBottomNavTheme._( 60 | labelStyle: TextStyle.lerp(labelStyle, other.labelStyle, t)!, 61 | selectedColor: Color.lerp(selectedColor, other.selectedColor, t)!, 62 | unselectedColor: Color.lerp(unselectedColor, other.unselectedColor, t)!, 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/core/themes/button_theme.dart: -------------------------------------------------------------------------------- 1 | import 'colors.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | @immutable 5 | class DevfestButtonTheme extends ThemeExtension { 6 | final ShapeBorder shape; 7 | final Color backgroundColor; 8 | final TextStyle textStyle; 9 | final Color iconColor; 10 | 11 | const DevfestButtonTheme._({ 12 | required this.shape, 13 | required this.backgroundColor, 14 | required this.textStyle, 15 | required this.iconColor, 16 | }); 17 | 18 | const DevfestButtonTheme.light() 19 | : this._( 20 | shape: const RoundedRectangleBorder( 21 | borderRadius: BorderRadius.all(Radius.circular(48))), 22 | backgroundColor: DevfestColors.grey0, 23 | textStyle: const TextStyle( 24 | fontSize: 16, 25 | fontWeight: FontWeight.w700, 26 | color: DevfestColors.grey100, 27 | ), 28 | iconColor: DevfestColors.grey100, 29 | ); 30 | 31 | const DevfestButtonTheme.dark() 32 | : this._( 33 | shape: const RoundedRectangleBorder( 34 | borderRadius: BorderRadius.all(Radius.circular(48))), 35 | backgroundColor: DevfestColors.background, 36 | textStyle: const TextStyle( 37 | fontSize: 16, 38 | fontWeight: FontWeight.w700, 39 | color: DevfestColors.grey0, 40 | ), 41 | iconColor: DevfestColors.grey0, 42 | ); 43 | 44 | @override 45 | DevfestButtonTheme copyWith({ 46 | ShapeBorder? shape, 47 | Color? backgroundColor, 48 | TextStyle? textStyle, 49 | Color? iconColor, 50 | }) { 51 | return DevfestButtonTheme._( 52 | shape: shape ?? this.shape, 53 | backgroundColor: backgroundColor ?? this.backgroundColor, 54 | textStyle: textStyle ?? this.textStyle, 55 | iconColor: iconColor ?? this.iconColor, 56 | ); 57 | } 58 | 59 | @override 60 | DevfestButtonTheme lerp( 61 | covariant ThemeExtension? other, double t) { 62 | if (other is! DevfestButtonTheme) return this; 63 | 64 | return DevfestButtonTheme._( 65 | shape: ShapeBorder.lerp(shape, other.shape, t)!, 66 | backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t)!, 67 | textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!, 68 | iconColor: Color.lerp(iconColor, other.iconColor, t)!, 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/core/themes/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract final class DevfestColors { 4 | /// Completely invisible. 5 | static const Color background = Color(0xFFFFFAEB); 6 | static const Color darkbackground = Color(0xFF050505); 7 | static const Color grey0 = Color(0xFF000000); 8 | static const Color grey10 = Color(0xFF1C1C1C); 9 | static const Color grey20 = Color(0xFF333333); 10 | static const Color grey30 = Color(0xFF4D4D4D); 11 | static const Color grey40 = Color(0xFF666666); 12 | static const Color grey50 = Color(0xFF808080); 13 | static const Color grey60 = Color(0xFF999999); 14 | static const Color grey70 = Color(0xFFB3B3B3); 15 | static const Color grey80 = Color(0xFFCCCCCC); 16 | static const Color grey90 = Color(0xFFE6E6E6); 17 | static const Color grey100 = Color(0xFFFFFFFF); 18 | 19 | static const Color blue = Color(0xFF4285F4); 20 | static const Color green = Color(0xFF34A853); 21 | static const Color yellow = Color(0xFFF9AB00); 22 | static const Color red = Color(0xFFEA4335); 23 | 24 | static const Color blueSecondary = Color(0xFF8AB4F8); 25 | static const Color greenSecondary = Color(0xFF81C995); 26 | static const Color yellowSecondary = Color(0xFFFDE293); 27 | static const Color redSecondary = Color(0xFFF28B82); 28 | static const Color grey = Color(0xFF0D0D0D); 29 | } 30 | -------------------------------------------------------------------------------- /lib/core/themes/outlined_button_theme.dart: -------------------------------------------------------------------------------- 1 | import 'colors.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | @immutable 5 | class DevfestOutlinedButtonTheme 6 | extends ThemeExtension { 7 | final OutlinedBorder shape; 8 | final Color outlineColor; 9 | final TextStyle textStyle; 10 | final Color iconColor; 11 | 12 | const DevfestOutlinedButtonTheme._({ 13 | required this.shape, 14 | required this.outlineColor, 15 | required this.textStyle, 16 | required this.iconColor, 17 | }); 18 | 19 | const DevfestOutlinedButtonTheme.light() 20 | : this._( 21 | shape: const RoundedRectangleBorder( 22 | side: BorderSide(color: DevfestColors.grey0), 23 | borderRadius: BorderRadius.all(Radius.circular(48)), 24 | ), 25 | outlineColor: DevfestColors.grey0, 26 | textStyle: const TextStyle( 27 | fontSize: 16, 28 | fontWeight: FontWeight.w700, 29 | color: DevfestColors.grey0, 30 | ), 31 | iconColor: DevfestColors.grey0, 32 | ); 33 | 34 | const DevfestOutlinedButtonTheme.dark() 35 | : this._( 36 | shape: const RoundedRectangleBorder( 37 | side: BorderSide(color: DevfestColors.grey100), 38 | borderRadius: BorderRadius.all(Radius.circular(48)), 39 | ), 40 | outlineColor: DevfestColors.grey100, 41 | textStyle: const TextStyle( 42 | fontSize: 16, 43 | fontWeight: FontWeight.w700, 44 | color: DevfestColors.grey100, 45 | ), 46 | iconColor: DevfestColors.grey100, 47 | ); 48 | 49 | @override 50 | DevfestOutlinedButtonTheme copyWith({ 51 | OutlinedBorder? shape, 52 | Color? outlineColor, 53 | TextStyle? textStyle, 54 | Color? iconColor, 55 | }) { 56 | return DevfestOutlinedButtonTheme._( 57 | shape: shape ?? this.shape, 58 | outlineColor: outlineColor ?? this.outlineColor, 59 | textStyle: textStyle ?? this.textStyle, 60 | iconColor: iconColor ?? this.iconColor, 61 | ); 62 | } 63 | 64 | @override 65 | DevfestOutlinedButtonTheme lerp( 66 | covariant ThemeExtension? other, double t) { 67 | if (other is! DevfestOutlinedButtonTheme) return this; 68 | 69 | return DevfestOutlinedButtonTheme._( 70 | shape: OutlinedBorder.lerp(shape, other.shape, t)!, 71 | outlineColor: Color.lerp(outlineColor, other.outlineColor, t)!, 72 | textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!, 73 | iconColor: Color.lerp(iconColor, other.iconColor, t)!, 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/core/themes/text_field_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/themes/colors.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | @immutable 5 | class DevfestTextFieldTheme extends ThemeExtension { 6 | final InputBorder border; 7 | final InputBorder focusedBorder; 8 | final TextStyle hintStyle; 9 | final TextStyle style; 10 | 11 | const DevfestTextFieldTheme._( 12 | {required this.border, 13 | required this.focusedBorder, 14 | required this.hintStyle, 15 | required this.style}); 16 | 17 | const DevfestTextFieldTheme.light() 18 | : this._( 19 | border: const OutlineInputBorder( 20 | borderRadius: BorderRadius.all(Radius.circular(16)), 21 | borderSide: BorderSide(color: DevfestColors.grey90, width: 1.5), 22 | ), 23 | focusedBorder: const OutlineInputBorder( 24 | borderRadius: BorderRadius.all(Radius.circular(16)), 25 | borderSide: BorderSide(color: DevfestColors.grey40, width: 1.5), 26 | ), 27 | hintStyle: const TextStyle( 28 | fontSize: 18, 29 | fontWeight: FontWeight.w500, 30 | color: DevfestColors.grey40, 31 | ), 32 | style: const TextStyle( 33 | fontSize: 18, 34 | fontWeight: FontWeight.w500, 35 | color: DevfestColors.grey40, 36 | ), 37 | ); 38 | 39 | const DevfestTextFieldTheme.dark() 40 | : this._( 41 | border: const OutlineInputBorder( 42 | borderRadius: BorderRadius.all(Radius.circular(16)), 43 | borderSide: BorderSide(color: DevfestColors.grey90), 44 | ), 45 | focusedBorder: const OutlineInputBorder( 46 | borderRadius: BorderRadius.all(Radius.circular(16)), 47 | borderSide: BorderSide(color: DevfestColors.grey100), 48 | ), 49 | hintStyle: const TextStyle( 50 | fontSize: 18, 51 | fontWeight: FontWeight.w500, 52 | color: DevfestColors.grey100, 53 | ), 54 | style: const TextStyle( 55 | fontSize: 18, 56 | fontWeight: FontWeight.w500, 57 | color: DevfestColors.grey100, 58 | ), 59 | ); 60 | 61 | @override 62 | ThemeExtension copyWith({ 63 | InputBorder? border, 64 | InputBorder? focusedBorder, 65 | TextStyle? hintStyle, 66 | TextStyle? style, 67 | }) { 68 | return DevfestTextFieldTheme._( 69 | border: border ?? this.border, 70 | focusedBorder: focusedBorder ?? this.focusedBorder, 71 | hintStyle: hintStyle ?? this.hintStyle, 72 | style: style ?? this.style, 73 | ); 74 | } 75 | 76 | @override 77 | ThemeExtension lerp( 78 | covariant ThemeExtension? other, double t) { 79 | return this; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/core/themes/theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/themes/text_field_theme.dart'; 2 | 3 | import 'bottom_nav_theme.dart'; 4 | import 'button_theme.dart'; 5 | import 'outlined_button_theme.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'colors.dart'; 9 | import 'text_theme.dart'; 10 | 11 | class DevFestTheme extends ThemeExtension { 12 | /// Create and register new themes here 13 | final DevfestTextTheme? textTheme; 14 | final DevfestButtonTheme? buttonTheme; 15 | final DevfestOutlinedButtonTheme? outlinedButtonTheme; 16 | final DevfestBottomNavTheme? bottomNavTheme; 17 | final DevfestTextFieldTheme? textFieldTheme; 18 | final Color? backgroundColor; 19 | final Color? onBackgroundColor; 20 | final Color? inverseBackgroundColor; 21 | 22 | static DevFestTheme of(BuildContext context) => 23 | Theme.of(context).extension()!; 24 | 25 | const DevFestTheme({ 26 | this.textTheme, 27 | this.backgroundColor, 28 | this.onBackgroundColor, 29 | this.inverseBackgroundColor, 30 | this.outlinedButtonTheme, 31 | this.bottomNavTheme, 32 | this.buttonTheme, 33 | this.textFieldTheme 34 | }); 35 | 36 | DevFestTheme.light() 37 | : this( 38 | backgroundColor: DevfestColors.background, 39 | onBackgroundColor: DevfestColors.grey0, 40 | inverseBackgroundColor: DevfestColors.grey30, 41 | buttonTheme: const DevfestButtonTheme.light(), 42 | outlinedButtonTheme: const DevfestOutlinedButtonTheme.light(), 43 | bottomNavTheme: DevfestBottomNavTheme.light(), 44 | textTheme: DevfestTextTheme.responsive(), 45 | textFieldTheme:const DevfestTextFieldTheme.light() 46 | ); 47 | 48 | DevFestTheme.dark() 49 | : this( 50 | backgroundColor: DevfestColors.darkbackground, 51 | onBackgroundColor: DevfestColors.background, 52 | inverseBackgroundColor: DevfestColors.grey70, 53 | buttonTheme: const DevfestButtonTheme.dark(), 54 | outlinedButtonTheme: const DevfestOutlinedButtonTheme.dark(), 55 | bottomNavTheme: DevfestBottomNavTheme.dark(), 56 | textTheme: DevfestTextTheme.responsive(), 57 | textFieldTheme:const DevfestTextFieldTheme.dark() 58 | ); 59 | 60 | @override 61 | DevFestTheme copyWith({ 62 | DevfestTextTheme? textTheme, 63 | DevfestButtonTheme? buttonTheme, 64 | DevfestOutlinedButtonTheme? outlinedButtonTheme, 65 | DevfestBottomNavTheme? bottomNavTheme, 66 | }) { 67 | return DevFestTheme( 68 | textTheme: textTheme ?? this.textTheme, 69 | buttonTheme: buttonTheme ?? this.buttonTheme, 70 | outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme, 71 | bottomNavTheme: bottomNavTheme ?? this.bottomNavTheme, 72 | ); 73 | } 74 | 75 | @override 76 | DevFestTheme lerp(DevFestTheme? other, double t) { 77 | if (other is! DevFestTheme) return this; 78 | return DevFestTheme( 79 | backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t), 80 | textTheme: textTheme?.lerp(other.textTheme, t), 81 | buttonTheme: buttonTheme?.lerp(other.buttonTheme, t), 82 | outlinedButtonTheme: 83 | outlinedButtonTheme?.lerp(other.outlinedButtonTheme, t), 84 | bottomNavTheme: bottomNavTheme?.lerp(other.bottomNavTheme, t), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/core/themes/theme_widget.dart: -------------------------------------------------------------------------------- 1 | import 'theme_data.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class DevfestTheme extends StatelessWidget { 5 | final DevFestTheme data; 6 | final Widget child; 7 | 8 | const DevfestTheme({ 9 | super.key, 10 | required this.data, 11 | required this.child, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final theme = Theme.of(context); 17 | final themeExtensions = Map.from(theme.extensions); 18 | 19 | themeExtensions[DevFestTheme] = data; 20 | return Theme( 21 | data: theme.copyWith(extensions: themeExtensions.values), 22 | child: child, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/themes/themes.dart: -------------------------------------------------------------------------------- 1 | export 'bottom_nav_theme.dart'; 2 | export 'button_theme.dart'; 3 | export 'colors.dart'; 4 | export 'outlined_button_theme.dart'; 5 | export 'text_theme.dart'; 6 | export 'theme_data.dart'; 7 | export 'theme_widget.dart'; 8 | -------------------------------------------------------------------------------- /lib/core/ui_state_model/ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/exceptions.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../router/module_provider.dart'; 6 | import '../router/navigator.dart'; 7 | import 'view_state.dart'; 8 | 9 | typedef DevfestUiStateRef = List; 10 | 11 | @immutable 12 | abstract base class DevfestUiState extends Equatable { 13 | const DevfestUiState({ 14 | this.viewState = ViewState.idle, 15 | this.exception = const EmptyException(), 16 | }); 17 | 18 | final ViewState viewState; 19 | final DevfestException exception; 20 | 21 | @override 22 | bool? get stringify => true; 23 | 24 | @override 25 | List get props => [viewState, exception]; 26 | } 27 | 28 | Future launch( 29 | DevfestUiStateRef model, 30 | Future Function(DevfestUiStateRef model) function, { 31 | bool displayError = true, 32 | }) async { 33 | await Future.sync(() => function(model)); 34 | 35 | if (model.isEmpty || !displayError) return; 36 | model._state.displayError(); 37 | } 38 | 39 | extension DevfestUiStateX on T { 40 | // provides an instance we can update since lists are passed by reference in dart 41 | DevfestUiStateRef get ref => [this]; 42 | 43 | void displayError() async { 44 | if (viewState != ViewState.error) return; 45 | assert(exception is! EmptyException, 'Please pass appropriate exception'); 46 | 47 | final context = AppNavigator.getKey(Module.general).currentContext!; 48 | 49 | final snackBar = SnackBar( 50 | backgroundColor: Colors.red, 51 | content: Text(exception.toString()), 52 | ); 53 | 54 | ScaffoldMessenger.maybeOf(context)?.showSnackBar(snackBar); 55 | } 56 | } 57 | 58 | extension DevfestUiStateRefX on DevfestUiStateRef { 59 | DevfestUiStateRef _assign(T value) => this..insert(0, value); 60 | 61 | T get _state => elementAt(0); 62 | 63 | T setState(T? value) => _assign(value ?? this._state)._state; 64 | } 65 | -------------------------------------------------------------------------------- /lib/core/ui_state_model/ui_state_model.dart: -------------------------------------------------------------------------------- 1 | export 'ui_state.dart'; 2 | export 'view_state.dart'; 3 | -------------------------------------------------------------------------------- /lib/core/ui_state_model/view_state.dart: -------------------------------------------------------------------------------- 1 | enum ViewState { idle, loading, success, error } 2 | -------------------------------------------------------------------------------- /lib/core/utils.dart: -------------------------------------------------------------------------------- 1 | extension ListX on List { 2 | List safeSublist(int length) { 3 | try { 4 | return sublist(0, length); 5 | } on RangeError { 6 | return this; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/core/widgets/animated_indexed_stack.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AnimatedIndexedStack extends StatelessWidget { 4 | const AnimatedIndexedStack( 5 | {super.key, 6 | required this.index, 7 | required this.children, 8 | this.duration = const Duration(milliseconds: 250)}); 9 | final int index; 10 | final List children; 11 | final Duration duration; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return IndexedStack( 16 | index: index, 17 | sizing: StackFit.expand, 18 | children: [ 19 | for (var i = 0; i < children.length; i++) 20 | AnimatedScale( 21 | key: children[i].key, 22 | scale: index == i ? 1.0 : 0.98, 23 | curve: Curves.easeIn, 24 | duration: duration, 25 | child: AnimatedOpacity( 26 | opacity: index == i ? 1.0 : 0.0, 27 | duration: duration, 28 | curve: Curves.decelerate, 29 | child: children[i], 30 | ), 31 | ), 32 | ], 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/core/widgets/loading_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | import '../../features/home/widgets/schedule_tile.dart'; 5 | import '../../features/home/widgets/session_category_chip.dart'; 6 | import '../../features/home/widgets/speakers_chip.dart'; 7 | import '../themes/themes.dart'; 8 | 9 | class FetchingSessions extends StatelessWidget { 10 | const FetchingSessions({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return ListView.separated( 15 | shrinkWrap: true, 16 | padding: EdgeInsets.zero, 17 | itemBuilder: (context, index) => const ScheduleTileShimmer(), 18 | separatorBuilder: (_, __) => const SizedBox(height: 14), 19 | itemCount: 5, 20 | ); 21 | } 22 | } 23 | 24 | class FetchingSpeakers extends StatelessWidget { 25 | const FetchingSpeakers({super.key}); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return ListView.separated( 30 | shrinkWrap: true, 31 | padding: EdgeInsets.zero, 32 | itemBuilder: (context, index) { 33 | var color = [ 34 | const Color(0xfff6eeee), 35 | DevfestColors.greenSecondary, 36 | DevfestColors.blueSecondary, 37 | const Color(0xffffafff) 38 | ].elementAt(index > 3 ? 3 : index); 39 | return SpeakerShimmerChip(moodColor: color); 40 | }, 41 | separatorBuilder: (_, __) => const SizedBox(height: 14), 42 | itemCount: 5, 43 | ); 44 | } 45 | } 46 | 47 | class FetchCategories extends StatelessWidget { 48 | const FetchCategories({super.key}); 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return ListView.separated( 53 | scrollDirection: Axis.horizontal, 54 | itemBuilder: (_, __) => const SessionCategoryShimmerChip(), 55 | separatorBuilder: (context, index) => 8.horizontalSpace, 56 | itemCount: 8, 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/core/widgets/on_screen_loader.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/themes/theme_data.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class OnScreenLoader extends StatelessWidget { 5 | const OnScreenLoader({ 6 | super.key, 7 | this.isLoading = false, 8 | required this.child, 9 | }); 10 | 11 | final bool isLoading; 12 | final Widget child; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return IgnorePointer( 17 | ignoring: isLoading, 18 | child: Stack( 19 | fit: StackFit.expand, 20 | children: [ 21 | child, 22 | if (isLoading) 23 | Container( 24 | color: 25 | DevFestTheme.of(context).onBackgroundColor?.withOpacity(0.4), 26 | child: Center( 27 | child: CircularProgressIndicator( 28 | color: DevFestTheme.of(context).backgroundColor, 29 | strokeCap: StrokeCap.round, 30 | strokeWidth: 6, 31 | ), 32 | ), 33 | ), 34 | ], 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/core/widgets/switcher.dart: -------------------------------------------------------------------------------- 1 | import '../providers/providers.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | 6 | import '../themes/themes.dart'; 7 | 8 | class DevfestSwitcher extends ConsumerWidget { 9 | const DevfestSwitcher({ 10 | super.key, 11 | required this.value, 12 | required this.onChanged, 13 | }); 14 | 15 | final bool value; 16 | final ValueChanged onChanged; 17 | 18 | @override 19 | Widget build(BuildContext context, ref) { 20 | return SizedBox( 21 | height: 24.h, 22 | child: CupertinoTheme( 23 | data: CupertinoThemeData( 24 | primaryColor: ref.watch(isDarkProvider) 25 | ? DevfestColors.grey10 26 | : DevfestColors.grey90, 27 | ), 28 | child: CupertinoSwitch( 29 | value: value, 30 | applyTheme: true, 31 | thumbColor: DevfestColors.grey10, 32 | // trackColor: ref.watch(isDarkProvider) 33 | // ? DevfestColors.grey10 34 | // : DevfestColors.grey90, 35 | activeColor: DevFestTheme.of(context).onBackgroundColor, 36 | onChanged: onChanged, 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/core/widgets/text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/constants.dart'; 2 | import 'package:devfest23/core/themes/text_field_theme.dart'; 3 | import 'package:devfest23/core/themes/themes.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 6 | 7 | class DevfestTextFormField extends StatelessWidget { 8 | const DevfestTextFormField({ 9 | super.key, 10 | this.title, 11 | this.controller, 12 | this.hint, 13 | this.info, 14 | TextInputType? keyboardType, 15 | Color? iconColor, 16 | this.onChanged, 17 | this.validator, 18 | this.textInputAction, 19 | }) : keyboardType = keyboardType ?? TextInputType.text, 20 | iconColor = iconColor ?? DevfestColors.grey0; 21 | final String? title; 22 | final String? info; 23 | final String? hint; 24 | final TextEditingController? controller; 25 | final TextInputType keyboardType; 26 | final Color iconColor; 27 | final ValueChanged? onChanged; 28 | final String? Function(String?)? validator; 29 | final TextInputAction? textInputAction; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | final textFieldTheme = DevFestTheme.of(context).textFieldTheme ?? 34 | const DevfestTextFieldTheme.light(); 35 | return Column( 36 | children: [ 37 | Row( 38 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 39 | children: [ 40 | if (title != null) 41 | Text( 42 | title!, 43 | style: DevFestTheme.of(context).textTheme?.body04, 44 | ), 45 | if (info != null) 46 | Wrap( 47 | crossAxisAlignment: WrapCrossAlignment.center, 48 | children: [ 49 | Icon( 50 | Icons.info_outline, 51 | color: iconColor, 52 | size: 11, 53 | ), 54 | (Constants.smallVerticalGutter / 2).horizontalSpace, 55 | Text( 56 | info!, 57 | style: DevFestTheme.of(context).textTheme?.body05, 58 | ), 59 | ], 60 | ) 61 | ], 62 | ), 63 | Constants.smallVerticalGutter.verticalSpace, 64 | TextFormField( 65 | controller: controller, 66 | keyboardType: keyboardType, 67 | onChanged: onChanged, 68 | validator: validator, 69 | textInputAction: textInputAction, 70 | cursorColor: DevFestTheme.of(context).onBackgroundColor, 71 | style: DevFestTheme.of(context).textFieldTheme?.style, 72 | decoration: InputDecoration( 73 | hintText: hint, 74 | hintStyle: textFieldTheme.hintStyle, 75 | border: textFieldTheme.border, 76 | enabledBorder: textFieldTheme.border, 77 | focusedBorder: textFieldTheme.focusedBorder, 78 | ), 79 | ), 80 | Constants.largeVerticalGutter.verticalSpace, 81 | ], 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/core/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | export 'animated_indexed_stack.dart'; 2 | export 'bottom_nav.dart'; 3 | export 'buttons.dart'; 4 | export 'switcher.dart'; 5 | export 'loading_widgets.dart'; 6 | export 'on_screen_loader.dart'; 7 | export 'text_field.dart'; 8 | 9 | abstract final class DevfestButtons {} 10 | 11 | abstract final class DevfestTiles {} 12 | 13 | abstract final class DevfestTabs {} 14 | 15 | abstract final class DevfestChips {} 16 | 17 | abstract final class DevfestSpeakerActionCard {} 18 | -------------------------------------------------------------------------------- /lib/features/agenda/model/agenda_model.dart: -------------------------------------------------------------------------------- 1 | class Temperatures { 2 | String status; 3 | String message; 4 | List data; 5 | 6 | Temperatures({ 7 | required this.status, 8 | required this.message, 9 | required this.data, 10 | }); 11 | 12 | } 13 | 14 | class Datum { 15 | bool isbreakout; 16 | String schedule; 17 | int order; 18 | String facilitator; 19 | String duration; 20 | String time; 21 | List? sessions; 22 | 23 | Datum({ 24 | required this.isbreakout, 25 | required this.schedule, 26 | required this.order, 27 | required this.facilitator, 28 | required this.duration, 29 | required this.time, 30 | this.sessions, 31 | }); 32 | 33 | } 34 | 35 | class Session { 36 | String schedule; 37 | String venue; 38 | String time; 39 | String facilitator; 40 | 41 | Session({ 42 | required this.schedule, 43 | required this.venue, 44 | required this.time, 45 | required this.facilitator, 46 | }); 47 | 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /lib/features/agenda/pages/agenda_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | 3 | import '../../../core/enums/devfest_day.dart'; 4 | import '../../../core/router/module_provider.dart'; 5 | import '../../../core/router/navigator.dart'; 6 | import 'agenda.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:regex_router/regex_router.dart'; 9 | 10 | import '../../../core/router/routes.dart'; 11 | import '../../speakers/page/speaker_details.dart'; 12 | import '../../schedule/pages/session.dart'; 13 | 14 | agendaRouter(DevfestDay initialDay) => RegexRouter.create({ 15 | "/": (context, args) => AgendaPage(initialDay: initialDay), 16 | RoutePaths.session: (context, args) { 17 | return SessionPage(session: args.body as Session); 18 | }, 19 | RoutePaths.speakers: (context, args) { 20 | return SpeakerDetailsPage(speaker: args.body as Speaker); 21 | } 22 | }); 23 | 24 | class AgendaView extends StatefulWidget { 25 | const AgendaView({super.key, this.initialDay}); 26 | 27 | final DevfestDay? initialDay; 28 | 29 | @override 30 | State createState() => _AgendaViewState(); 31 | } 32 | 33 | class _AgendaViewState extends State 34 | with AutomaticKeepAliveClientMixin { 35 | bool canPop = false; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | super.build(context); 40 | return ModuleProvider( 41 | module: Module.home, 42 | child: PopScope( 43 | canPop: canPop, 44 | onPopInvoked: (_) { 45 | final navState = AppNavigator.getKey(Module.home).currentState; 46 | if (navState != null && navState.canPop()) { 47 | AppNavigator.pop(module: Module.home); 48 | setState(() { 49 | canPop = false; 50 | }); // We handled the popping manually 51 | return; 52 | } 53 | setState(() { 54 | canPop = true; 55 | }); // Allow default behavior 56 | }, 57 | child: Navigator( 58 | key: AppNavigator.getKey(Module.home), 59 | onUnknownRoute: (settings) => MaterialPageRoute( 60 | settings: settings, 61 | builder: (_) => Scaffold( 62 | body: Center( 63 | child: Text('No home route defined for ${settings.name}'), 64 | ), 65 | ), 66 | ), 67 | onGenerateRoute: 68 | agendaRouter(widget.initialDay ?? DevfestDay.day1).generateRoute, 69 | ), 70 | ), 71 | ); 72 | } 73 | 74 | @override 75 | bool get wantKeepAlive => true; 76 | } 77 | -------------------------------------------------------------------------------- /lib/features/favourites/pages/favourites_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | 3 | import '../../../core/router/routes.dart'; 4 | import 'favourites.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:regex_router/regex_router.dart'; 7 | 8 | import '../../../core/enums/devfest_day.dart'; 9 | import '../../../core/router/module_provider.dart'; 10 | import '../../../core/router/navigator.dart'; 11 | import '../../schedule/pages/session.dart'; 12 | 13 | favouriteRouter(DevfestDay initialDay) => RegexRouter.create({ 14 | "/": (context, args) => FavouritesPage(initialDay: initialDay), 15 | RoutePaths.session: (context, args) { 16 | return SessionPage(session: args.body as Session); 17 | } 18 | }); 19 | 20 | class FavouritesView extends StatefulWidget { 21 | const FavouritesView({super.key, this.initialDay}); 22 | 23 | final DevfestDay? initialDay; 24 | 25 | @override 26 | State createState() => _FavouritesViewState(); 27 | } 28 | 29 | class _FavouritesViewState extends State 30 | with AutomaticKeepAliveClientMixin { 31 | bool canPop = false; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | super.build(context); 36 | return ModuleProvider( 37 | module: Module.favourites, 38 | child: PopScope( 39 | canPop: canPop, 40 | onPopInvoked: (didPop) { 41 | final navState = AppNavigator.getKey(Module.favourites).currentState; 42 | if (navState != null && navState.canPop()) { 43 | AppNavigator.pop(module: Module.favourites); 44 | setState(() { 45 | canPop = false; 46 | }); // We handled the popping manually 47 | return; 48 | } 49 | setState(() { 50 | canPop = true; 51 | }); // Allow default behavior 52 | }, 53 | child: Navigator( 54 | key: AppNavigator.getKey(Module.favourites), 55 | onUnknownRoute: (settings) => MaterialPageRoute( 56 | settings: settings, 57 | builder: (_) => Scaffold( 58 | body: Center( 59 | child: Text('No favourite route defined for ${settings.name}'), 60 | ), 61 | ), 62 | ), 63 | onGenerateRoute: favouriteRouter(widget.initialDay ?? DevfestDay.day1) 64 | .generateRoute, 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | @override 71 | bool get wantKeepAlive => true; 72 | } 73 | -------------------------------------------------------------------------------- /lib/features/home/widgets/header_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HeaderDelegate extends SliverPersistentHeaderDelegate { 4 | const HeaderDelegate({required this.height, required this.child}); 5 | 6 | final double height; 7 | final Widget child; 8 | 9 | @override 10 | Widget build( 11 | BuildContext context, double shrinkOffset, bool overlapsContent) { 12 | return child; 13 | } 14 | 15 | @override 16 | double get maxExtent => height; 17 | 18 | @override 19 | double get minExtent => maxExtent; 20 | 21 | @override 22 | bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { 23 | if (oldDelegate is! HeaderDelegate) return false; 24 | if (oldDelegate == this) return false; 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/features/home/widgets/more_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/themes/themes.dart'; 2 | 3 | import '../../../core/constants.dart'; 4 | import '../../../core/widgets/widgets.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 7 | import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; 8 | 9 | @widgetbook.UseCase(name: 'More Tile', type: DevfestTiles) 10 | Widget devfestMoreTile(BuildContext context) { 11 | return Material( 12 | color: DevFestTheme.of(context).backgroundColor, 13 | child: Column( 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | MoreTile( 17 | leading: const Icon(Icons.account_circle_outlined), 18 | title: const Text('Profile'), 19 | trailing: const Icon(Icons.chevron_right), 20 | onPressed: () {}, 21 | ), 22 | ], 23 | ), 24 | ); 25 | } 26 | 27 | class MoreTile extends StatelessWidget { 28 | const MoreTile({ 29 | super.key, 30 | required this.title, 31 | this.subtitle, 32 | this.leading, 33 | this.trailing, 34 | this.onPressed, 35 | this.titleStyle, 36 | this.subtitleStyle, 37 | }); 38 | 39 | final Widget title; 40 | final Widget? subtitle; 41 | final Widget? leading; 42 | final Widget? trailing; 43 | final TextStyle? titleStyle; 44 | final TextStyle? subtitleStyle; 45 | final VoidCallback? onPressed; 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return IconTheme( 50 | data: IconThemeData( 51 | color: DevFestTheme.of(context).onBackgroundColor, 52 | weight: 600, 53 | ), 54 | child: InkWell( 55 | onTap: onPressed, 56 | child: Padding( 57 | padding: 58 | const EdgeInsets.symmetric(vertical: Constants.verticalGutter), 59 | child: Row( 60 | children: [ 61 | if (leading case final widget?) ...[ 62 | widget, 63 | Constants.horizontalGutter.horizontalSpace, 64 | ], 65 | Expanded( 66 | child: Column( 67 | crossAxisAlignment: CrossAxisAlignment.stretch, 68 | children: [ 69 | AnimatedDefaultTextStyle( 70 | duration: Constants.kAnimationDur, 71 | style: titleStyle ?? 72 | DevFestTheme.of(context).textTheme!.body02!.copyWith( 73 | color: 74 | DevFestTheme.of(context).onBackgroundColor, 75 | fontWeight: FontWeight.w500, 76 | ), 77 | child: title, 78 | ), 79 | if (subtitle != null) ...[ 80 | (Constants.smallVerticalGutter / 2).verticalSpace, 81 | AnimatedDefaultTextStyle( 82 | style: subtitleStyle ?? 83 | DevFestTheme.of(context) 84 | .textTheme! 85 | .body04! 86 | .copyWith(color: DevfestColors.grey50), 87 | duration: Constants.kAnimationDur, 88 | child: subtitle!, 89 | ), 90 | ], 91 | ], 92 | ), 93 | ), 94 | if (trailing case final widget?) ...[ 95 | Constants.horizontalGutter.horizontalSpace, 96 | widget, 97 | ], 98 | ], 99 | ), 100 | ), 101 | ), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/features/home/widgets/sponsors_chip.dart: -------------------------------------------------------------------------------- 1 | import '../../../core/widgets/widgets.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; 6 | 7 | import '../../../core/constants.dart'; 8 | import '../../../core/images.dart'; 9 | import '../../../core/providers/providers.dart'; 10 | import '../../../core/themes/themes.dart'; 11 | 12 | @widgetbook.UseCase( 13 | name: 'Sponsor chip', 14 | type: DevfestChips, 15 | designLink: 16 | 'https://www.figma.com/file/CCnX5Sh86ILqRn7ng6Shlr/DevFest-Jordan-Year---Mobile-App?node-id=1591%3A1213&mode=dev') 17 | Widget devfestSponsorTile(BuildContext context) { 18 | return Material( 19 | color: DevFestTheme.of(context).backgroundColor, 20 | child: SizedBox( 21 | width: MediaQuery.sizeOf(context).width, 22 | child: const Padding( 23 | padding: EdgeInsets.symmetric(horizontal: Constants.horizontalMargin), 24 | child: Column( 25 | mainAxisAlignment: MainAxisAlignment.center, 26 | children: [ 27 | SponsorsChip(image: AppImages.googleLogo), 28 | ], 29 | ), 30 | ), 31 | ), 32 | ); 33 | } 34 | 35 | class SponsorsChip extends ConsumerWidget { 36 | const SponsorsChip({ 37 | super.key, 38 | required this.image, 39 | }); 40 | 41 | final String image; 42 | 43 | @override 44 | Widget build(BuildContext context, WidgetRef ref) { 45 | final isDark = ref.watch(isDarkProvider); 46 | return AnimatedContainer( 47 | duration: Constants.kAnimationDur, 48 | decoration: ShapeDecoration( 49 | color: isDark ? DevfestColors.darkbackground : const Color(0xFFFFFAEB), 50 | shape: RoundedRectangleBorder( 51 | side: BorderSide( 52 | width: 1, 53 | strokeAlign: BorderSide.strokeAlignCenter, 54 | color: isDark ? DevfestColors.grey10 : const Color(0xFFE6E6E6), 55 | ), 56 | borderRadius: BorderRadius.circular(16), 57 | ), 58 | ), 59 | padding: const EdgeInsets.all(16).w, 60 | margin: const EdgeInsets.only(right: 8).w, 61 | child: Image(image: AssetImage(image)), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/features/more/pages/more_base.dart: -------------------------------------------------------------------------------- 1 | import '../../../core/router/module_provider.dart'; 2 | import '../../../core/router/navigator.dart'; 3 | import 'more.dart'; 4 | import '../../profile/pages/profile.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class MoreView extends StatefulWidget { 8 | const MoreView({super.key}); 9 | 10 | @override 11 | State createState() => _MoreViewState(); 12 | } 13 | 14 | class _MoreViewState extends State { 15 | bool canPop = false; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ModuleProvider( 20 | module: Module.more, 21 | child: PopScope( 22 | canPop: canPop, 23 | onPopInvoked: (didPop) { 24 | final navState = AppNavigator.getKey(Module.more).currentState; 25 | if (navState != null && navState.canPop()) { 26 | AppNavigator.pop(module: Module.more); 27 | setState(() { 28 | canPop = false; 29 | }); // We handled the popping manually 30 | return; 31 | } 32 | setState(() { 33 | canPop = true; 34 | }); // Allow default behavior 35 | }, 36 | child: Navigator( 37 | key: AppNavigator.getKey(Module.more), 38 | onGenerateRoute: (settings) { 39 | switch (settings.name) { 40 | case '/': 41 | return MaterialPageRoute( 42 | settings: settings, 43 | builder: (_) => const MorePage(), 44 | ); 45 | case '/profile': 46 | return MaterialPageRoute( 47 | settings: settings, 48 | builder: (_) => const ProfilePage(), 49 | ); 50 | default: 51 | return MaterialPageRoute( 52 | settings: settings, 53 | builder: (_) => Scaffold( 54 | body: Center( 55 | child: Text('No more route defined for ${settings.name}'), 56 | ), 57 | ), 58 | ); 59 | } 60 | }, 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/features/onboarding/application/application.dart: -------------------------------------------------------------------------------- 1 | export 'auth/view_model.dart'; 2 | export 'controllers.dart'; 3 | -------------------------------------------------------------------------------- /lib/features/onboarding/application/auth/auth_value_objects.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | 5 | final class LoginForm extends Equatable { 6 | final EmailAddress emailAddress; 7 | final Password password; 8 | 9 | const LoginForm({required this.emailAddress, required this.password}); 10 | 11 | LoginForm.empty() 12 | : this( 13 | emailAddress: EmailAddress(''), 14 | password: Password(''), 15 | ); 16 | 17 | LoginForm copyWith({EmailAddress? emailAddress, Password? password}) { 18 | return LoginForm( 19 | emailAddress: emailAddress ?? this.emailAddress, 20 | password: password ?? this.password, 21 | ); 22 | } 23 | 24 | LoginRequestDto toDto() { 25 | return LoginRequestDto( 26 | email: emailAddress.valueOrCrash, 27 | password: password.valueOrCrash, 28 | ); 29 | } 30 | 31 | bool get isValid => formValidationError == null; 32 | 33 | String? get formValidationError { 34 | if (!emailAddress.isValid) { 35 | return emailAddress.validationError; 36 | } 37 | 38 | if (!password.isValid) { 39 | return password.validationError; 40 | } 41 | 42 | return null; 43 | } 44 | 45 | @override 46 | List get props => [emailAddress, password]; 47 | } 48 | 49 | final class EmailAddress extends Equatable { 50 | static final regex = RegExp( 51 | r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'); 52 | 53 | final String _value; 54 | final String? _validationError; 55 | 56 | factory EmailAddress(String input) { 57 | String failure = ''; 58 | if (!regex.hasMatch(input)) { 59 | failure = 'Please enter a valid email address'; 60 | } 61 | 62 | if (input.isEmpty) { 63 | failure = 'Email cannot be empty'; 64 | } 65 | 66 | if (failure.isNotEmpty) return EmailAddress._(input, failure); 67 | return EmailAddress._(input, null); 68 | } 69 | 70 | const EmailAddress._(this._value, this._validationError); 71 | 72 | String get valueOrCrash { 73 | if (!isValid) { 74 | throw ErrorDescription('Please enter a valid email'); 75 | } 76 | 77 | return value; 78 | } 79 | 80 | String get value => _value; 81 | 82 | String? get validationError => _validationError; 83 | 84 | bool get isValid => _validationError == null; 85 | 86 | @override 87 | List get props => [_value, _validationError]; 88 | } 89 | 90 | final class Password extends Equatable { 91 | final String _value; 92 | final String? _validationError; 93 | 94 | factory Password(String input) { 95 | String failure = ''; 96 | 97 | if (input.isEmpty) { 98 | failure = 'Ticket Number cannot be empty'; 99 | } 100 | 101 | if (failure.isNotEmpty) return Password._(input, failure); 102 | return Password._(input, null); 103 | } 104 | 105 | const Password._(this._value, this._validationError); 106 | 107 | String get valueOrCrash { 108 | if (!isValid) { 109 | throw ErrorDescription('Please enter a valid password'); 110 | } 111 | 112 | return value; 113 | } 114 | 115 | String get value => _value; 116 | 117 | String? get validationError => _validationError; 118 | 119 | bool get isValid => _validationError == null; 120 | 121 | @override 122 | List get props => [_value, _validationError]; 123 | } 124 | -------------------------------------------------------------------------------- /lib/features/onboarding/application/auth/ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/exceptions.dart'; 2 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 3 | import 'package:devfest23/features/onboarding/application/auth/auth_value_objects.dart'; 4 | 5 | final class AuthUiState extends DevfestUiState { 6 | final LoginForm form; 7 | final bool showFormErrors; 8 | 9 | const AuthUiState({ 10 | required super.viewState, 11 | required super.exception, 12 | required this.form, 13 | required this.showFormErrors, 14 | }); 15 | 16 | AuthUiState.initial() 17 | : this( 18 | viewState: ViewState.idle, 19 | exception: const EmptyException(), 20 | form: LoginForm.empty(), 21 | showFormErrors: false, 22 | ); 23 | 24 | AuthUiState copyWith({ 25 | ViewState? viewState, 26 | DevfestException? exception, 27 | LoginForm? form, 28 | bool? showFormErrors, 29 | }) { 30 | return AuthUiState( 31 | viewState: viewState ?? this.viewState, 32 | exception: exception ?? this.exception, 33 | form: form ?? this.form, 34 | showFormErrors: showFormErrors ?? this.showFormErrors, 35 | ); 36 | } 37 | 38 | @override 39 | List get props => [...super.props, form, showFormErrors]; 40 | } 41 | -------------------------------------------------------------------------------- /lib/features/onboarding/application/auth/view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 2 | 3 | import '../../../../core/data/data.dart'; 4 | import 'auth_value_objects.dart'; 5 | 6 | import 'ui_state.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | 9 | class AuthViewModel extends StateNotifier { 10 | final DevfestRepository _repo; 11 | 12 | AuthViewModel(this._repo) : super(AuthUiState.initial()); 13 | 14 | void emailAddressOnChanged(String input) { 15 | state = state.copyWith( 16 | form: state.form.copyWith(emailAddress: EmailAddress(input)), 17 | ); 18 | } 19 | 20 | void passwordOnChanged(String input) { 21 | state = state.copyWith( 22 | form: state.form.copyWith(password: Password(input)), 23 | ); 24 | } 25 | 26 | Future loginOnTap() async { 27 | if (state.form.isValid) { 28 | await launch(state.ref, (model) async { 29 | state = model.setState(state.copyWith(viewState: ViewState.loading)); 30 | final result = await _repo.rsvpLogin(state.form.toDto()); 31 | 32 | state = model.setState(result.fold( 33 | (left) => state.copyWith(viewState: ViewState.error, exception: left), 34 | (right) => state.copyWith(viewState: ViewState.success), 35 | )); 36 | }, displayError: false); 37 | 38 | state = state.copyWith(viewState: ViewState.idle); 39 | return; 40 | } 41 | 42 | state = state.copyWith(showFormErrors: true); 43 | } 44 | } 45 | 46 | final authViewModelProvider = 47 | StateNotifierProvider.autoDispose( 48 | (ref) => AuthViewModel(ref.read(devfestRepositoryProvider)), 49 | ); 50 | -------------------------------------------------------------------------------- /lib/features/onboarding/application/controllers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 2 | 3 | import '../../../core/ui_state_model/ui_state_model.dart'; 4 | import 'application.dart'; 5 | 6 | final showFormErrorsProvider = Provider.autoDispose((ref) { 7 | return ref 8 | .watch(authViewModelProvider.select((value) => value.showFormErrors)); 9 | }); 10 | 11 | final authIsLoadingProvider = Provider.autoDispose((ref) { 12 | return ref.watch(authViewModelProvider.select((value) => value.viewState)) == 13 | ViewState.loading; 14 | }); 15 | -------------------------------------------------------------------------------- /lib/features/onboarding/widgets/title_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | 5 | import '../../../core/providers/providers.dart'; 6 | import '../../../core/themes/themes.dart'; 7 | 8 | class TitleTile extends ConsumerWidget { 9 | const TitleTile({ 10 | super.key, 11 | required this.emoji, 12 | required this.title, 13 | required this.backgroundColor, 14 | }); 15 | 16 | final String emoji; 17 | final String title; 18 | final Color backgroundColor; 19 | 20 | @override 21 | Widget build(BuildContext context, ref) { 22 | final color = ref.watch(isDarkProvider) 23 | ? backgroundColor 24 | : backgroundColor.withOpacity(0.2); 25 | return Container( 26 | padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4).w, 27 | margin: const EdgeInsets.only(top: 20, bottom: 16).w, 28 | decoration: ShapeDecoration( 29 | color: color, 30 | shape: const RoundedRectangleBorder( 31 | borderRadius: BorderRadius.all(Radius.circular(8.0)), 32 | ), 33 | ), 34 | child: Text.rich( 35 | TextSpan( 36 | text: emoji, 37 | style: DevFestTheme.of(context) 38 | .textTheme 39 | ?.body03 40 | ?.copyWith(color: DevfestColors.grey0), 41 | children: [ 42 | WidgetSpan(child: SizedBox(width: 4.w)), 43 | TextSpan(text: title) 44 | ], 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/features/profile/application/ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/exceptions.dart'; 2 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 3 | 4 | final class ProfileUiState extends DevfestUiState { 5 | const ProfileUiState({ 6 | required super.viewState, 7 | required super.exception, 8 | }); 9 | 10 | const ProfileUiState.initial() 11 | : this( 12 | viewState: ViewState.idle, 13 | exception: const EmptyException(), 14 | ); 15 | 16 | ProfileUiState copyWith({ 17 | ViewState? viewState, 18 | DevfestException? exception, 19 | }) { 20 | return ProfileUiState( 21 | viewState: viewState ?? this.viewState, 22 | exception: exception ?? this.exception, 23 | ); 24 | } 25 | 26 | @override 27 | List get props => [...super.props]; 28 | } 29 | -------------------------------------------------------------------------------- /lib/features/profile/application/view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 2 | import 'package:devfest23/features/profile/application/ui_state.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | import '../../../core/data/data.dart'; 6 | 7 | class ProfileViewModel extends StateNotifier { 8 | final DevfestRepository _repo; 9 | 10 | ProfileViewModel(this._repo) : super(const ProfileUiState.initial()); 11 | 12 | Future logout() async { 13 | await launch(state.ref, (model) async { 14 | state = model.setState(state.copyWith(viewState: ViewState.loading)); 15 | final result = await _repo.logout(); 16 | 17 | state = model.setState(result.fold( 18 | (left) => state.copyWith(viewState: ViewState.error, exception: left), 19 | (right) => state.copyWith(viewState: ViewState.success), 20 | )); 21 | }); 22 | 23 | state = state.copyWith(viewState: ViewState.idle); 24 | } 25 | } 26 | 27 | final profileViewModelProvider = 28 | StateNotifierProvider.autoDispose( 29 | (ref) => ProfileViewModel(ref.read(devfestRepositoryProvider)), 30 | ); 31 | -------------------------------------------------------------------------------- /lib/features/schedule/application/application.dart: -------------------------------------------------------------------------------- 1 | export 'controllers.dart'; 2 | export 'sessions/view_model.dart'; 3 | export 'session_detail/view_model.dart'; 4 | -------------------------------------------------------------------------------- /lib/features/schedule/application/controllers.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:devfest23/features/schedule/application/application.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | import '../../../core/constants.dart'; 6 | 7 | final allSessionsProviderProvider = Provider.autoDispose>((ref) { 8 | return ref.watch(sessionsViewModelProvider.select((value) => value.sessions)); 9 | }); 10 | 11 | final day1SessionsProvider = Provider.autoDispose>((ref) { 12 | return ref 13 | .watch(sessionsViewModelProvider.select((value) => value.sessions)) 14 | .where((element) => 15 | element.sessionDate.difference(Constants.day1).inDays == 0) 16 | .toList() 17 | ..sort((a, b) => a.order.compareTo(b.order)); 18 | }); 19 | 20 | final day2SessionsProvider = Provider.autoDispose>((ref) { 21 | return ref 22 | .watch(sessionsViewModelProvider.select((value) => value.sessions)) 23 | .where((element) => 24 | element.sessionDate.difference(Constants.day2).inDays == 0) 25 | .toList() 26 | ..sort((a, b) => a.order.compareTo(b.order)); 27 | }); 28 | 29 | final day1RSVPSessionProvider = Provider.autoDispose>((ref) { 30 | return ref 31 | .watch(day1SessionsProvider) 32 | .where((element) => element.hasRsvped) 33 | .toList() 34 | ..sort((a, b) => a.order.compareTo(b.order)); 35 | }); 36 | 37 | final day2RSVPSessionProvider = Provider.autoDispose>((ref) { 38 | return ref 39 | .watch(day2SessionsProvider) 40 | .where((element) => element.hasRsvped) 41 | .toList() 42 | ..sort((a, b) => a.order.compareTo(b.order)); 43 | }); 44 | 45 | final sessionProvider = Provider.autoDispose((ref) { 46 | return ref 47 | .watch(sessionDetailsViewModelProvider.select((value) => value.session)); 48 | }); 49 | -------------------------------------------------------------------------------- /lib/features/schedule/application/session_detail/ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:devfest23/core/exceptions/exceptions.dart'; 3 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 4 | 5 | final class SessionDetailUiState extends DevfestUiState { 6 | final Session session; 7 | 8 | const SessionDetailUiState({ 9 | required this.session, 10 | super.viewState, 11 | super.exception, 12 | }); 13 | 14 | SessionDetailUiState.initial() 15 | : this( 16 | session: Session.empty(), 17 | viewState: ViewState.idle, 18 | exception: const EmptyException(), 19 | ); 20 | 21 | SessionDetailUiState copyWith({ 22 | ViewState? viewState, 23 | DevfestException? exception, 24 | Session? session, 25 | }) { 26 | return SessionDetailUiState( 27 | session: session ?? this.session, 28 | viewState: viewState ?? this.viewState, 29 | exception: exception ?? this.exception, 30 | ); 31 | } 32 | 33 | @override 34 | List get props => [...super.props, session]; 35 | } 36 | -------------------------------------------------------------------------------- /lib/features/schedule/application/session_detail/view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 3 | 4 | import 'ui_state.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | 7 | class SessionDetailsViewModel extends StateNotifier { 8 | final DevfestRepository _repo; 9 | 10 | SessionDetailsViewModel(this._repo) : super(SessionDetailUiState.initial()); 11 | 12 | void initialiseSession(Session session) { 13 | state = state.copyWith(session: session); 14 | } 15 | 16 | Future reserveSessionOnTap(bool hasRsvped) async { 17 | if (!hasRsvped) { 18 | await launch(state.ref, (model) async { 19 | state = model.setState(state.copyWith(viewState: ViewState.loading)); 20 | final dto = RSVPSessionRequestDto(sessionId: state.session.sessionId); 21 | final result = await _repo.addToRSVP(dto); 22 | 23 | state = model.setState(result.fold( 24 | (left) => state.copyWith(viewState: ViewState.error, exception: left), 25 | (right) => state.copyWith( 26 | viewState: ViewState.success, 27 | session: state.session.copyWith( 28 | availableSeats: state.session.availableSeats - 1, 29 | hasRsvped: true, 30 | ), 31 | ), 32 | )); 33 | }); 34 | 35 | state = state.copyWith(viewState: ViewState.idle); 36 | return; 37 | } 38 | 39 | await launch(state.ref, (model) async { 40 | state = model.setState(state.copyWith(viewState: ViewState.loading)); 41 | final dto = RSVPSessionRequestDto(sessionId: state.session.sessionId); 42 | final result = await _repo.removeFromRSVP(dto); 43 | 44 | state = model.setState( 45 | result.fold( 46 | (left) => state.copyWith(viewState: ViewState.error, exception: left), 47 | (right) => state.copyWith( 48 | viewState: ViewState.success, 49 | session: state.session.copyWith( 50 | availableSeats: state.session.availableSeats + 1, 51 | hasRsvped: false, 52 | ), 53 | ), 54 | ), 55 | ); 56 | }); 57 | 58 | state = state.copyWith(viewState: ViewState.idle); 59 | } 60 | } 61 | 62 | final sessionDetailsViewModelProvider = StateNotifierProvider.autoDispose< 63 | SessionDetailsViewModel, SessionDetailUiState>( 64 | (ref) => SessionDetailsViewModel(ref.read(devfestRepositoryProvider)), 65 | ); 66 | -------------------------------------------------------------------------------- /lib/features/schedule/application/sessions/ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/devfest_exception.dart'; 2 | import 'package:devfest23/core/exceptions/empty_exception.dart'; 3 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 4 | 5 | import '../../../../core/data/data.dart'; 6 | 7 | final class SessionsUiState extends DevfestUiState { 8 | final List sessions; 9 | 10 | const SessionsUiState({ 11 | super.viewState, 12 | super.exception, 13 | required this.sessions, 14 | }); 15 | 16 | const SessionsUiState.initial() 17 | : this( 18 | viewState: ViewState.idle, 19 | exception: const EmptyException(), 20 | sessions: const [], 21 | ); 22 | 23 | SessionsUiState copyWith({ 24 | ViewState? viewState, 25 | DevfestException? exception, 26 | List? sessions, 27 | }) { 28 | return SessionsUiState( 29 | sessions: sessions ?? this.sessions, 30 | viewState: viewState ?? this.viewState, 31 | exception: exception ?? this.exception, 32 | ); 33 | } 34 | 35 | @override 36 | List get props => [...super.props, sessions]; 37 | } 38 | -------------------------------------------------------------------------------- /lib/features/schedule/application/sessions/view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/services/firebase_notification_manager.dart'; 2 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 3 | import 'package:devfest23/features/schedule/application/sessions/ui_state.dart'; 4 | import 'package:firebase_auth/firebase_auth.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | 7 | import '../../../../core/data/data.dart'; 8 | 9 | class SessionsViewModel extends StateNotifier { 10 | final DevfestRepository _repo; 11 | final FirebaseAuth _auth; 12 | final FirebaseNotificationManager _notificationManager; 13 | 14 | SessionsViewModel(this._repo, this._auth, this._notificationManager) 15 | : super(const SessionsUiState.initial()); 16 | 17 | Future fetchSessions() async { 18 | await launch(state.ref, (model) async { 19 | state = model.setState(state.copyWith(viewState: ViewState.loading)); 20 | if (_auth.currentUser == null) { 21 | final result = await _repo.fetchSessions(); 22 | 23 | state = model.setState( 24 | result.fold( 25 | (left) => 26 | state.copyWith(viewState: ViewState.error, exception: left), 27 | (right) => state.copyWith( 28 | viewState: ViewState.success, 29 | sessions: right.sessions, 30 | ), 31 | ), 32 | ); 33 | return; 34 | } 35 | 36 | final [sessionResult, rsvpResult, _] = await Future.wait([ 37 | _repo.fetchSessions(), 38 | _repo.fetchRSVPSessions(), 39 | _repo.updateUserDeviceToken( 40 | UpdateTokenRequestDto( 41 | deviceToken: await _notificationManager.deviceToken ?? '', 42 | ), 43 | ), 44 | ]); 45 | 46 | if (sessionResult.isLeft) { 47 | state = model.setState( 48 | state.copyWith( 49 | viewState: ViewState.error, exception: sessionResult.left), 50 | ); 51 | return; 52 | } 53 | 54 | if (rsvpResult.isLeft) { 55 | state = model.setState( 56 | state.copyWith( 57 | viewState: ViewState.error, exception: rsvpResult.left), 58 | ); 59 | return; 60 | } 61 | 62 | List sessions = 63 | (sessionResult.right as SessionsResponseDto).sessions; 64 | final rsvpSessions = (rsvpResult.right as List); 65 | 66 | for (final sessionId in rsvpSessions) { 67 | sessions = sessions.map((e) { 68 | if (e.sessionId == sessionId) { 69 | return e.copyWith(hasRsvped: true); 70 | } 71 | return e; 72 | }).toList(); 73 | } 74 | 75 | state = model.setState( 76 | state.copyWith(viewState: ViewState.success, sessions: sessions), 77 | ); 78 | }); 79 | } 80 | } 81 | 82 | final sessionsViewModelProvider = 83 | StateNotifierProvider.autoDispose( 84 | (ref) => SessionsViewModel(ref.read(devfestRepositoryProvider), 85 | FirebaseAuth.instance, FirebaseNotificationManager()), 86 | ); 87 | -------------------------------------------------------------------------------- /lib/features/schedule/pages/schedule_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | 3 | import '../../../core/router/routes.dart'; 4 | import 'schedule.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:regex_router/regex_router.dart'; 7 | 8 | import '../../../core/enums/devfest_day.dart'; 9 | import '../../../core/router/module_provider.dart'; 10 | import '../../../core/router/navigator.dart'; 11 | import 'session.dart'; 12 | 13 | scheduleRouter(DevfestDay initialDay) => RegexRouter.create({ 14 | "/": (context, args) => SchedulePage(initialDay: initialDay), 15 | RoutePaths.session: (context, args) { 16 | return SessionPage(session: args.body as Session); 17 | } 18 | }); 19 | 20 | class ScheduleView extends StatefulWidget { 21 | const ScheduleView({super.key, this.initialDay}); 22 | 23 | final DevfestDay? initialDay; 24 | 25 | @override 26 | State createState() => _ScheduleViewState(); 27 | } 28 | 29 | class _ScheduleViewState extends State 30 | with AutomaticKeepAliveClientMixin { 31 | bool canPop = false; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | super.build(context); 36 | return ModuleProvider( 37 | module: Module.schedule, 38 | child: PopScope( 39 | canPop: canPop, 40 | onPopInvoked: (didPop) { 41 | final navState = AppNavigator.getKey(Module.schedule).currentState; 42 | if (navState != null && navState.canPop()) { 43 | AppNavigator.pop(module: Module.schedule); 44 | setState(() { 45 | canPop = false; 46 | }); // We handled the popping manually 47 | return; 48 | } 49 | setState(() { 50 | canPop = true; 51 | }); // Allow default behavior 52 | }, 53 | child: Navigator( 54 | key: AppNavigator.getKey(Module.schedule), 55 | onUnknownRoute: (settings) => MaterialPageRoute( 56 | settings: settings, 57 | builder: (_) => Scaffold( 58 | body: Center( 59 | child: Text('No home route defined for ${settings.name}'), 60 | ), 61 | ), 62 | ), 63 | onGenerateRoute: scheduleRouter(widget.initialDay ?? DevfestDay.day1) 64 | .generateRoute, 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | @override 71 | bool get wantKeepAlive => true; 72 | } 73 | -------------------------------------------------------------------------------- /lib/features/speakers/application/application.dart: -------------------------------------------------------------------------------- 1 | export 'controllers.dart'; 2 | export 'speakers/view_model.dart'; 3 | export 'speaker_detail/view_model.dart'; 4 | -------------------------------------------------------------------------------- /lib/features/speakers/application/controllers.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | 4 | import '../../../core/constants.dart'; 5 | import 'application.dart'; 6 | 7 | final day1AllSpeakersProvider = Provider.autoDispose>((ref) { 8 | return ref 9 | .watch(speakersViewModelProvider.select((value) => value.speakers)) 10 | .where((element) => 11 | element.sessionDate.difference(Constants.day1).inDays == 0) 12 | .toList() 13 | ..sort((a, b) => a.order.compareTo(b.order)); 14 | }); 15 | 16 | final day2AllSpeakersProvider = Provider.autoDispose>((ref) { 17 | return ref 18 | .watch(speakersViewModelProvider.select((value) => value.speakers)) 19 | .where((element) => 20 | element.sessionDate.difference(Constants.day2).inDays == 0) 21 | .toList() 22 | ..sort((a, b) => a.order.compareTo(b.order)); 23 | }); 24 | 25 | final day1FilteredSpeakersProvider = Provider.autoDispose>((ref) { 26 | return ref 27 | .watch( 28 | speakersViewModelProvider.select((value) => value.filteredSpeakers)) 29 | .where((element) => 30 | element.sessionDate.difference(Constants.day1).inDays == 0) 31 | .toList() 32 | ..sort((a, b) => a.order.compareTo(b.order)); 33 | }); 34 | 35 | final day2FilteredSpeakersProvider = Provider.autoDispose>((ref) { 36 | return ref 37 | .watch( 38 | speakersViewModelProvider.select((value) => value.filteredSpeakers)) 39 | .where((element) => 40 | element.sessionDate.difference(Constants.day2).inDays == 0) 41 | .toList() 42 | ..sort((a, b) => a.order.compareTo(b.order)); 43 | }); 44 | 45 | final day1SpeakersProvider = Provider.autoDispose>((ref) { 46 | return ref.watch(speakersViewModelProvider 47 | .select((value) => value.selectedCategory)) == 48 | 'All Speakers' 49 | ? ref.watch(day1AllSpeakersProvider) 50 | : ref.watch(day1FilteredSpeakersProvider); 51 | }); 52 | 53 | final day2SpeakersProvider = Provider.autoDispose>((ref) { 54 | return ref.watch(speakersViewModelProvider 55 | .select((value) => value.selectedCategory)) == 56 | 'All Speakers' 57 | ? ref.watch(day2AllSpeakersProvider) 58 | : ref.watch(day2FilteredSpeakersProvider); 59 | }); 60 | 61 | final categoriesProvider = Provider.autoDispose>((ref) { 62 | return ref.watch(speakersViewModelProvider 63 | .select((value) => value.categoriesUiState.categories)); 64 | }); 65 | 66 | final speakerProvider = Provider.autoDispose((ref) { 67 | return ref 68 | .watch(speakerDetailsViewModelProvider.select((value) => value.speaker)); 69 | }); 70 | -------------------------------------------------------------------------------- /lib/features/speakers/application/speaker_detail/ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:devfest23/core/exceptions/exceptions.dart'; 3 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 4 | 5 | final class SpeakerDetailsUiState extends DevfestUiState { 6 | final Speaker speaker; 7 | final Session session; 8 | 9 | const SpeakerDetailsUiState({ 10 | super.viewState, 11 | super.exception, 12 | required this.speaker, 13 | required this.session, 14 | }); 15 | 16 | SpeakerDetailsUiState.initial() 17 | : this( 18 | viewState: ViewState.idle, 19 | exception: const EmptyException(), 20 | speaker: Speaker.empty(), 21 | session: Session.empty(), 22 | ); 23 | 24 | SpeakerDetailsUiState copyWith({ 25 | ViewState? viewState, 26 | DevfestException? exception, 27 | Speaker? speaker, 28 | Session? session, 29 | }) { 30 | return SpeakerDetailsUiState( 31 | speaker: speaker ?? this.speaker, 32 | viewState: viewState ?? this.viewState, 33 | exception: exception ?? this.exception, 34 | session: session ?? this.session, 35 | ); 36 | } 37 | 38 | @override 39 | List get props => [...super.props, speaker, session]; 40 | } 41 | -------------------------------------------------------------------------------- /lib/features/speakers/application/speaker_detail/view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | import 'ui_state.dart'; 6 | 7 | class SpeakerDetailsViewModel extends StateNotifier { 8 | final DevfestRepository _repo; 9 | 10 | SpeakerDetailsViewModel(this._repo) : super(SpeakerDetailsUiState.initial()); 11 | 12 | void initialiseSpeaker(Speaker speaker) { 13 | state = state.copyWith(speaker: speaker); 14 | } 15 | 16 | void initialiseSession(Session session) { 17 | state = state.copyWith(session: session); 18 | } 19 | 20 | Future reserveSessionOnTap(bool hasRsvped) async { 21 | assert( 22 | state.session.sessionId.isNotEmpty, 23 | 'A valid session must be initialised before calling this function', 24 | ); 25 | 26 | if (!hasRsvped) { 27 | await launch(state.ref, (model) async { 28 | state = model.setState(state.copyWith(viewState: ViewState.loading)); 29 | final dto = RSVPSessionRequestDto(sessionId: state.session.sessionId); 30 | final result = await _repo.addToRSVP(dto); 31 | 32 | state = model.setState(result.fold( 33 | (left) => state.copyWith(viewState: ViewState.error, exception: left), 34 | (right) => state.copyWith( 35 | viewState: ViewState.success, 36 | session: state.session.copyWith( 37 | availableSeats: state.session.availableSeats - 1, 38 | hasRsvped: true, 39 | ), 40 | ), 41 | )); 42 | }); 43 | state = state.copyWith(viewState: ViewState.idle); 44 | return; 45 | } 46 | 47 | await launch(state.ref, (model) async { 48 | state = model.setState(state.copyWith(viewState: ViewState.loading)); 49 | final dto = RSVPSessionRequestDto(sessionId: state.session.sessionId); 50 | final result = await _repo.removeFromRSVP(dto); 51 | 52 | state = model.setState( 53 | result.fold( 54 | (left) => state.copyWith(viewState: ViewState.error, exception: left), 55 | (right) => state.copyWith( 56 | viewState: ViewState.success, 57 | session: state.session.copyWith( 58 | availableSeats: state.session.availableSeats + 1, 59 | hasRsvped: false, 60 | ), 61 | ), 62 | ), 63 | ); 64 | }); 65 | state = state.copyWith(viewState: ViewState.idle); 66 | } 67 | } 68 | 69 | final speakerDetailsViewModelProvider = StateNotifierProvider.autoDispose< 70 | SpeakerDetailsViewModel, SpeakerDetailsUiState>( 71 | (ref) => SpeakerDetailsViewModel(ref.read(devfestRepositoryProvider)), 72 | ); 73 | -------------------------------------------------------------------------------- /lib/features/speakers/application/speakers/ui_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/exceptions/exceptions.dart'; 2 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 3 | 4 | import '../../../../core/data/data.dart'; 5 | 6 | final class SpeakersUiState extends DevfestUiState { 7 | final List speakers; 8 | final List filteredSpeakers; 9 | final SpeakerCategoriesUiState categoriesUiState; 10 | final String selectedCategory; 11 | 12 | const SpeakersUiState({ 13 | super.viewState, 14 | super.exception, 15 | required this.speakers, 16 | required this.filteredSpeakers, 17 | required this.categoriesUiState, 18 | required this.selectedCategory, 19 | }); 20 | 21 | const SpeakersUiState.initial() 22 | : this( 23 | viewState: ViewState.idle, 24 | exception: const EmptyException(), 25 | speakers: const [], 26 | filteredSpeakers: const [], 27 | categoriesUiState: const SpeakerCategoriesUiState.initial(), 28 | selectedCategory: 'All Speakers', 29 | ); 30 | 31 | SpeakersUiState copyWith({ 32 | ViewState? viewState, 33 | DevfestException? exception, 34 | List? speakers, 35 | List? filteredSpeakers, 36 | SpeakerCategoriesUiState? categoriesUiState, 37 | String? selectedCategory, 38 | }) { 39 | return SpeakersUiState( 40 | viewState: viewState ?? this.viewState, 41 | speakers: speakers ?? this.speakers, 42 | filteredSpeakers: filteredSpeakers ?? this.filteredSpeakers, 43 | exception: exception ?? this.exception, 44 | categoriesUiState: categoriesUiState ?? this.categoriesUiState, 45 | selectedCategory: selectedCategory ?? this.selectedCategory, 46 | ); 47 | } 48 | 49 | @override 50 | List get props => [ 51 | ...super.props, 52 | speakers, 53 | filteredSpeakers, 54 | categoriesUiState, 55 | selectedCategory, 56 | ]; 57 | } 58 | 59 | final class SpeakerCategoriesUiState extends DevfestUiState { 60 | final List categories; 61 | 62 | const SpeakerCategoriesUiState({ 63 | super.viewState, 64 | super.exception, 65 | required this.categories, 66 | }); 67 | 68 | const SpeakerCategoriesUiState.initial() 69 | : this( 70 | viewState: ViewState.idle, 71 | exception: const EmptyException(), 72 | categories: const [], 73 | ); 74 | 75 | SpeakerCategoriesUiState copyWith({ 76 | ViewState? viewState, 77 | DevfestException? exception, 78 | List? categories, 79 | }) { 80 | return SpeakerCategoriesUiState( 81 | categories: categories ?? this.categories, 82 | viewState: viewState ?? this.viewState, 83 | exception: exception ?? this.exception, 84 | ); 85 | } 86 | 87 | @override 88 | List get props => [...super.props, categories]; 89 | } 90 | -------------------------------------------------------------------------------- /lib/features/speakers/application/speakers/view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 2 | 3 | import '../../../../core/data/data.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | 6 | import 'ui_state.dart'; 7 | 8 | class SpeakersViewModel extends StateNotifier { 9 | final DevfestRepository _repo; 10 | 11 | SpeakersViewModel(this._repo) : super(const SpeakersUiState.initial()); 12 | 13 | void filterSpeakersByCategory(String categoryName) { 14 | state = state.copyWith( 15 | filteredSpeakers: categoryName == 'All Speakers' 16 | ? [] 17 | : state.speakers 18 | .toList() 19 | .where((element) => element.category == categoryName) 20 | .toList(), 21 | selectedCategory: categoryName, 22 | ); 23 | } 24 | 25 | Future fetchSpeakers() async { 26 | await launch(state.ref, (model) async { 27 | state = model.setState(state.copyWith(viewState: ViewState.loading)); 28 | final result = await _repo.fetchSpeakers(); 29 | 30 | state = model.setState( 31 | result.fold( 32 | (left) => state.copyWith(viewState: ViewState.error, exception: left), 33 | (right) => state.copyWith( 34 | viewState: ViewState.success, 35 | speakers: right.speakers, 36 | ), 37 | ), 38 | ); 39 | }); 40 | } 41 | 42 | Future fetchSessionCategories() async { 43 | await launch(state.categoriesUiState.ref, (model) async { 44 | state = state.copyWith( 45 | categoriesUiState: model.setState( 46 | state.categoriesUiState.copyWith(viewState: ViewState.loading), 47 | )); 48 | final result = await _repo.fetchSessionCategories(); 49 | 50 | state = state.copyWith( 51 | categoriesUiState: model.setState( 52 | result.fold( 53 | (left) => state.categoriesUiState 54 | .copyWith(viewState: ViewState.error, exception: left), 55 | (right) => state.categoriesUiState.copyWith( 56 | viewState: ViewState.success, categories: right.categories), 57 | ), 58 | )); 59 | }); 60 | } 61 | } 62 | 63 | final speakersViewModelProvider = 64 | StateNotifierProvider.autoDispose( 65 | (ref) => SpeakersViewModel(ref.read(devfestRepositoryProvider)), 66 | ); 67 | -------------------------------------------------------------------------------- /lib/features/speakers/page/speakers_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | 3 | import '../../../core/router/routes.dart'; 4 | import 'speakers.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:regex_router/regex_router.dart'; 7 | 8 | import '../../../core/enums/devfest_day.dart'; 9 | import '../../../core/router/module_provider.dart'; 10 | import '../../../core/router/navigator.dart'; 11 | import 'speaker_details.dart'; 12 | 13 | speakerRouter(DevfestDay initialDay) => RegexRouter.create({ 14 | "/": (context, args) => SpeakersPage(initialDay: initialDay), 15 | RoutePaths.speakers: (context, args) { 16 | return SpeakerDetailsPage(speaker: args.body as Speaker); 17 | } 18 | }); 19 | 20 | class SpeakersView extends StatefulWidget { 21 | const SpeakersView({super.key, this.initialDay}); 22 | 23 | final DevfestDay? initialDay; 24 | 25 | @override 26 | State createState() => _SpeakersViewState(); 27 | } 28 | 29 | class _SpeakersViewState extends State 30 | with AutomaticKeepAliveClientMixin { 31 | bool canPop = false; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | super.build(context); 36 | return ModuleProvider( 37 | module: Module.speakers, 38 | child: PopScope( 39 | canPop: canPop, 40 | onPopInvoked: (_) { 41 | final navState = AppNavigator.getKey(Module.speakers).currentState; 42 | if (navState != null && navState.canPop()) { 43 | AppNavigator.pop(module: Module.speakers); 44 | setState(() { 45 | canPop = false; 46 | }); // We handled the popping manually 47 | return; 48 | } 49 | setState(() { 50 | canPop = true; 51 | }); // Allow default behavior 52 | }, 53 | child: Navigator( 54 | key: AppNavigator.getKey(Module.speakers), 55 | onUnknownRoute: (settings) => MaterialPageRoute( 56 | settings: settings, 57 | builder: (_) => Scaffold( 58 | body: Center( 59 | child: Text('No speaker route defined for ${settings.name}'), 60 | ), 61 | ), 62 | ), 63 | onGenerateRoute: 64 | speakerRouter(widget.initialDay ?? DevfestDay.day1).generateRoute, 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | @override 71 | bool get wantKeepAlive => true; 72 | } 73 | -------------------------------------------------------------------------------- /lib/features/splash/splash.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | 3 | import '../../core/enums/tab_item.dart'; 4 | import '../../core/images.dart'; 5 | import '../../core/router/navigator.dart'; 6 | import '../../core/router/routes.dart'; 7 | import '../../core/themes/theme_data.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 10 | 11 | import '../../core/providers/providers.dart'; 12 | 13 | class SplashPage extends ConsumerStatefulWidget { 14 | const SplashPage({super.key}); 15 | 16 | @override 17 | ConsumerState createState() => _SplashPageState(); 18 | } 19 | 20 | class _SplashPageState extends ConsumerState { 21 | late AssetImage image; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | 27 | WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((_) async { 28 | Future.delayed(const Duration(seconds: 2), () { 29 | if (FirebaseAuth.instance.currentUser != null) { 30 | context.pushNamedAndClear('/app/${TabItem.home.name}'); 31 | return; 32 | } 33 | context.goReplace(RoutePaths.onboarding); 34 | }); 35 | }); 36 | } 37 | 38 | @override 39 | void didChangeDependencies() { 40 | final isDark = ref.read(isDarkProvider); 41 | image = AssetImage(isDark ? AppImages.splashDark : AppImages.splashLight); 42 | precacheImage(image, context); 43 | super.didChangeDependencies(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Scaffold( 49 | backgroundColor: DevFestTheme.of(context).backgroundColor, 50 | extendBody: true, 51 | appBar: AppBar( 52 | toolbarHeight: 0, 53 | backgroundColor: DevFestTheme.of(context).backgroundColor, 54 | ), 55 | body: SizedBox( 56 | height: double.infinity, 57 | width: double.infinity, 58 | child: DecoratedBox( 59 | decoration: BoxDecoration( 60 | image: DecorationImage(image: image, fit: BoxFit.cover), 61 | ), 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/firebase_options.dart: -------------------------------------------------------------------------------- 1 | // File generated by FlutterFire CLI. 2 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members 3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; 4 | import 'package:flutter/foundation.dart' 5 | show defaultTargetPlatform, kIsWeb, TargetPlatform; 6 | 7 | /// Default [FirebaseOptions] for use with your Firebase apps. 8 | /// 9 | /// Example: 10 | /// ```dart 11 | /// import 'firebase_options.dart'; 12 | /// // ... 13 | /// await Firebase.initializeApp( 14 | /// options: DefaultFirebaseOptions.currentPlatform, 15 | /// ); 16 | /// ``` 17 | class DefaultFirebaseOptions { 18 | static FirebaseOptions get currentPlatform { 19 | if (kIsWeb) { 20 | throw UnsupportedError( 21 | 'DefaultFirebaseOptions have not been configured for web - ' 22 | 'you can reconfigure this by running the FlutterFire CLI again.', 23 | ); 24 | } 25 | switch (defaultTargetPlatform) { 26 | case TargetPlatform.android: 27 | return android; 28 | case TargetPlatform.iOS: 29 | return ios; 30 | case TargetPlatform.macOS: 31 | throw UnsupportedError( 32 | 'DefaultFirebaseOptions have not been configured for macos - ' 33 | 'you can reconfigure this by running the FlutterFire CLI again.', 34 | ); 35 | case TargetPlatform.windows: 36 | throw UnsupportedError( 37 | 'DefaultFirebaseOptions have not been configured for windows - ' 38 | 'you can reconfigure this by running the FlutterFire CLI again.', 39 | ); 40 | case TargetPlatform.linux: 41 | throw UnsupportedError( 42 | 'DefaultFirebaseOptions have not been configured for linux - ' 43 | 'you can reconfigure this by running the FlutterFire CLI again.', 44 | ); 45 | default: 46 | throw UnsupportedError( 47 | 'DefaultFirebaseOptions are not supported for this platform.', 48 | ); 49 | } 50 | } 51 | 52 | static const FirebaseOptions android = FirebaseOptions( 53 | apiKey: 'AIzaSyAyucrDWB2TK6bLcne65YcihOBVBHLjMtI', 54 | appId: '1:163954857780:android:8f28e8d1eac80d7cae3761', 55 | messagingSenderId: '163954857780', 56 | projectId: 'devfestlagos-2022', 57 | storageBucket: 'devfestlagos-2022.appspot.com', 58 | ); 59 | 60 | static const FirebaseOptions ios = FirebaseOptions( 61 | apiKey: 'AIzaSyBrjQGvrhDQTdVk5-DInHkDLMiOgvzHoxI', 62 | appId: '1:163954857780:ios:e9c87dee27844b2cae3761', 63 | messagingSenderId: '163954857780', 64 | projectId: 'devfestlagos-2022', 65 | storageBucket: 'devfestlagos-2022.appspot.com', 66 | androidClientId: '163954857780-4o1eqfetdrv3j6u6lc8ke1ik6ksv1cqs.apps.googleusercontent.com', 67 | iosClientId: '163954857780-u8p3v1ug6scdo4n91l56ooesm3q9u6in.apps.googleusercontent.com', 68 | iosBundleId: 'com.gdglagos.devfestlg', 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /lib/main_prod.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_core/firebase_core.dart'; 2 | 3 | import 'app.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:flutter_native_splash/flutter_native_splash.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | 9 | import 'core/services/firebase_notification_manager.dart'; 10 | import 'firebase_options.dart'; 11 | 12 | void main() async { 13 | WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); 14 | FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); 15 | 16 | await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 17 | SystemChrome.setSystemUIOverlayStyle( 18 | const SystemUiOverlayStyle( 19 | systemNavigationBarColor: Colors.transparent, 20 | statusBarColor: Colors.transparent, 21 | ), 22 | ); 23 | 24 | SystemChrome.setPreferredOrientations([ 25 | DeviceOrientation.portraitUp, 26 | DeviceOrientation.portraitDown, 27 | ]); 28 | 29 | await Firebase.initializeApp( 30 | options: DefaultFirebaseOptions.currentPlatform, 31 | ); 32 | 33 | FirebaseNotificationManager firebaseNotificationManager = 34 | FirebaseNotificationManager(); 35 | firebaseNotificationManager.registerNotification(); 36 | 37 | runApp(const ProviderScope(child: DevfestApp())); 38 | } 39 | -------------------------------------------------------------------------------- /lib/main_staging.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/firebase_options.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:firebase_core/firebase_core.dart'; 4 | 5 | import 'app.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:flutter_native_splash/flutter_native_splash.dart'; 9 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 10 | 11 | import 'core/services/firebase_notification_manager.dart'; 12 | 13 | void main() async { 14 | WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); 15 | FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); 16 | 17 | await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 18 | SystemChrome.setSystemUIOverlayStyle( 19 | const SystemUiOverlayStyle( 20 | systemNavigationBarColor: Colors.transparent, 21 | statusBarColor: Colors.transparent, 22 | ), 23 | ); 24 | 25 | SystemChrome.setPreferredOrientations([ 26 | DeviceOrientation.portraitUp, 27 | DeviceOrientation.portraitDown, 28 | ]); 29 | 30 | await Firebase.initializeApp( 31 | options: DefaultFirebaseOptions.currentPlatform, 32 | ); 33 | await FirebaseAuth.instance.useAuthEmulator('localhost', 9099); 34 | 35 | FirebaseNotificationManager firebaseNotificationManager = 36 | FirebaseNotificationManager(); 37 | firebaseNotificationManager.registerNotification(); 38 | 39 | runApp(const ProviderScope(child: DevfestApp())); 40 | } 41 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: devfest23 2 | description: iOS and Android app for DevFest Lagos 2023, built with Flutter. 3 | publish_to: 'none' 4 | 5 | version: 1.0.2+9 6 | 7 | environment: 8 | sdk: '>=3.0.0 <4.0.0' 9 | flutter: ">=3.0.0 <4.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | cupertino_icons: ^1.0.6 16 | flutter_riverpod: ^2.4.9 17 | flutter_native_splash: ^2.3.6 18 | equatable: ^2.0.5 19 | cloud_firestore: ^4.12.2 20 | firebase_core: ^2.21.0 21 | firebase_auth: ^4.12.1 22 | firebase_messaging: ^14.7.3 23 | widgetbook: ^3.4.1 24 | widgetbook_annotation: ^3.1.0 25 | flutter_svg: ^2.0.9 26 | font_awesome_flutter: ^10.6.0 27 | intl: ^0.18.1 28 | shared_preferences: ^2.2.2 29 | material_symbols_icons: ^4.2711.0 30 | iconoir_flutter: ^7.0.3 31 | regex_router: ^2.0.2 32 | flutter_screenutil: ^5.9.0 33 | either_dart: ^1.0.0 34 | dio: ^5.3.4 35 | shimmer: ^3.0.0 36 | cached_network_image: ^3.3.0 37 | url_launcher: ^6.2.1 38 | package_info_plus: ^5.0.1 39 | flutter_local_notifications: ^16.2.0 40 | 41 | dev_dependencies: 42 | flutter_test: 43 | sdk: flutter 44 | flutter_lints: ^3.0.1 45 | widgetbook_generator: ^3.3.0 46 | build_runner: ^2.4.7 47 | mocktail: ^1.0.1 48 | 49 | 50 | flutter: 51 | uses-material-design: true 52 | 53 | # To add assets to your application, add an assets section, like this: 54 | assets: 55 | - assets/images/ 56 | - assets/icons/ 57 | - shorebird.yaml 58 | # - images/a_dot_ham.jpeg 59 | 60 | # To add custom fonts to your application, add a fonts section here, 61 | # in this "flutter" section. Each entry in this list should have a 62 | # "family" key with the font family name, and a "fonts" key with a 63 | # list giving the asset and other descriptors for the font. For 64 | # example: 65 | fonts: 66 | - family: Google Sans 67 | fonts: 68 | - asset: assets/fonts/GoogleSansDisplay-Bold.ttf 69 | - asset: assets/fonts/GoogleSansDisplay-Regular.ttf 70 | style: normal 71 | 72 | flutter_native_splash: 73 | # This package generates native code to customize Flutter's default white native splash screen 74 | # with background color and splash image. 75 | # Customize the parameters below, and run the following command in the terminal: 76 | # dart run flutter_native_splash:create 77 | # To restore Flutter's default white splash screen, run the following command in the terminal: 78 | # dart run flutter_native_splash:remove 79 | background_image: "assets/images/splash-light.png" 80 | background_image_dark: "assets/images/splash-dark.png" 81 | 82 | # Android 12 handles the splash screen differently than previous versions. Please visit 83 | # https://developer.android.com/guide/topics/ui/splash-screen 84 | # Following are Android 12 specific parameter. 85 | android_12: -------------------------------------------------------------------------------- /shorebird.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to configure the Shorebird updater used by your app. 2 | # Learn more at https://docs.shorebird.dev 3 | # This file should be checked into version control. 4 | 5 | # This is the unique identifier assigned to your app. 6 | # Your app_id is not a secret and is just used to identify your app 7 | # when requesting patches from Shorebird's servers. 8 | app_id: 55d74cd7-c942-477b-a248-d94d3dc1a222 9 | 10 | # auto_update controls if Shorebird should automatically update in the background on launch. 11 | # If auto_update: false, you will need to use package:shorebird_code_push to trigger updates. 12 | # https://pub.dev/packages/shorebird_code_push 13 | # Uncomment the following line to disable automatic updates. 14 | # auto_update: false 15 | -------------------------------------------------------------------------------- /shots/gdg-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/shots/gdg-logo.png -------------------------------------------------------------------------------- /test/commons.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:devfest23/core/router/module_provider.dart'; 3 | import 'package:devfest23/core/router/navigator.dart'; 4 | import 'package:devfest23/core/services/firebase_notification_manager.dart'; 5 | import 'package:firebase_auth/firebase_auth.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | import 'package:mocktail/mocktail.dart'; 9 | 10 | class RiverpodListener extends Mock { 11 | void call(T? previous, T next); 12 | } 13 | 14 | class MockDevfestRepository extends Mock implements DevfestRepository {} 15 | 16 | class MockFirebaseAuth extends Mock implements FirebaseAuth {} 17 | 18 | class MockFirebaseUser extends Mock implements User {} 19 | 20 | class MockFirebaseNotificationManager extends Mock 21 | implements FirebaseNotificationManager {} 22 | 23 | class UnitTestApp extends StatelessWidget { 24 | const UnitTestApp({super.key}); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return MaterialApp( 29 | navigatorKey: AppNavigator.getKey(Module.general), 30 | home: const ProviderScope(child: Scaffold()), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/features/onboarding/application/value_objects_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/features/onboarding/application/auth/auth_value_objects.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | group('Unit tests for Authentication feature', () { 6 | test('Email address value object test', () { 7 | EmailAddress address = EmailAddress(''); 8 | 9 | expect(address.isValid, false); // invalid email input 10 | expect(address.validationError, 'Email cannot be empty'); 11 | 12 | address = EmailAddress('john'); 13 | expect(address.isValid, false); 14 | expect(address.validationError, 'Please enter a valid email address'); 15 | // ensure our value object holds onto the value even invalid 16 | expect(address.value, 'john'); 17 | 18 | address = EmailAddress('johndoe@gmail.com'); 19 | expect(address.isValid, true); // valid email input 20 | expect(address.validationError, null); 21 | expect(address.value, 'johndoe@gmail.com'); 22 | }); 23 | 24 | test('Password value object test', () { 25 | Password password = Password(''); 26 | 27 | expect(password.isValid, false); // invalid password input 28 | expect(password.validationError, 'Ticket Number cannot be empty'); 29 | 30 | password = Password('1234'); 31 | expect(password.isValid, true); 32 | expect(password.value, '1234'); 33 | 34 | password = Password('123456'); 35 | expect(password.isValid, true); 36 | expect(password.validationError, null); 37 | expect(password.value, '123456'); 38 | }); 39 | 40 | test('LoginForm entity test', () { 41 | LoginForm form = LoginForm.empty(); 42 | 43 | expect(form.isValid, false); 44 | expect(form.formValidationError, 'Email cannot be empty'); 45 | 46 | form = form.copyWith(emailAddress: EmailAddress('johndoe@gmail.com')); 47 | 48 | expect(form.isValid, false); 49 | expect(form.formValidationError, 'Ticket Number cannot be empty'); 50 | expect(form.emailAddress.value, 'johndoe@gmail.com'); 51 | 52 | form = form.copyWith(password: Password('123456')); 53 | expect(form.isValid, true); 54 | expect(form.formValidationError, null); 55 | 56 | expect(form.emailAddress.value, 'johndoe@gmail.com'); 57 | expect(form.password.value, '123456'); 58 | }); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /test/features/profile/application/view_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:devfest23/core/data/data.dart'; 2 | import 'package:devfest23/core/exceptions/client_exception.dart'; 3 | import 'package:devfest23/core/ui_state_model/ui_state_model.dart'; 4 | import 'package:devfest23/features/profile/application/ui_state.dart'; 5 | import 'package:devfest23/features/profile/application/view_model.dart'; 6 | import 'package:either_dart/either.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | import 'package:mocktail/mocktail.dart'; 10 | 11 | import '../../../commons.dart'; 12 | 13 | void main() { 14 | final mockDevfestRepo = MockDevfestRepository(); 15 | 16 | group('Profile view model test suite', () { 17 | late ProviderContainer container; 18 | late RiverpodListener listener; 19 | 20 | setUpAll(() => registerFallbackValue(const ProfileUiState.initial())); 21 | 22 | setUp(() { 23 | container = ProviderContainer(overrides: [ 24 | devfestRepositoryProvider.overrideWithValue(mockDevfestRepo), 25 | ]); 26 | listener = RiverpodListener(); 27 | }); 28 | 29 | tearDown(() => container.dispose()); 30 | 31 | test('Logout success test', () async { 32 | when(() => mockDevfestRepo.logout()) 33 | .thenAnswer((_) => Future.value(const Right(null))); 34 | 35 | expect( 36 | container.read(profileViewModelProvider).viewState, ViewState.idle); 37 | 38 | container.listen(profileViewModelProvider, listener.call, 39 | fireImmediately: true); 40 | final currState = container.read(profileViewModelProvider); 41 | 42 | await container.read(profileViewModelProvider.notifier).logout(); 43 | 44 | verifyInOrder([ 45 | () => listener(null, currState.copyWith(viewState: ViewState.idle)), 46 | () => listener( 47 | any(that: isA()), 48 | any( 49 | that: isA().having( 50 | (s) => s.viewState, 'viewState', ViewState.loading))), 51 | () => listener( 52 | any(that: isA()), 53 | any( 54 | that: isA().having( 55 | (s) => s.viewState, 'viewState', ViewState.success))), 56 | ]); 57 | }); 58 | 59 | testWidgets('Logout failure test', (widgetTester) async { 60 | when(() => mockDevfestRepo.logout()).thenAnswer((_) => Future.value( 61 | const Left(ClientException(exceptionMessage: 'Logout failed')))); 62 | 63 | expect( 64 | container.read(profileViewModelProvider).viewState, ViewState.idle); 65 | 66 | container.listen(profileViewModelProvider, listener.call, 67 | fireImmediately: true); 68 | final currState = container.read(profileViewModelProvider); 69 | 70 | await widgetTester.pumpWidget(const UnitTestApp()); 71 | await container.read(profileViewModelProvider.notifier).logout(); 72 | await widgetTester.pumpAndSettle(); 73 | 74 | verifyInOrder([ 75 | () => listener(null, currState.copyWith(viewState: ViewState.idle)), 76 | () => listener( 77 | any(that: isA()), 78 | any( 79 | that: isA().having( 80 | (s) => s.viewState, 'viewState', ViewState.loading))), 81 | () => listener( 82 | any(that: isA()), 83 | any( 84 | that: isA() 85 | .having((s) => s.viewState, 'viewState', ViewState.error))), 86 | ]); 87 | }); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/icons/README.txt: -------------------------------------------------------------------------------- 1 | Add this to your HTML : 2 | 3 | 4 | 5 | 6 | Add this to your app's manifest.json: 7 | 8 | ... 9 | { 10 | "icons": [ 11 | { "src": "/favicon.ico", "type": "image/x-icon", "sizes": "16x16 32x32" }, 12 | { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, 13 | { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }, 14 | { "src": "/icon-192-maskable.png", "type": "image/png", "sizes": "192x192", "purpose": "maskable" }, 15 | { "src": "/icon-512-maskable.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable" } 16 | ] 17 | } 18 | ... 19 | -------------------------------------------------------------------------------- /web/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /web/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/icons/favicon.ico -------------------------------------------------------------------------------- /web/icons/icon-192-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/icons/icon-192-maskable.png -------------------------------------------------------------------------------- /web/icons/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/icons/icon-192.png -------------------------------------------------------------------------------- /web/icons/icon-512-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/icons/icon-512-maskable.png -------------------------------------------------------------------------------- /web/icons/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/icons/icon-512.png -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devfest23", 3 | "short_name": "devfest23", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "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 | -------------------------------------------------------------------------------- /web/splash/img/dark-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/splash/img/dark-background.png -------------------------------------------------------------------------------- /web/splash/img/light-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-lagos/devfest23/cd1770fd1567856988be37db57e145f2ebe3c451/web/splash/img/light-background.png --------------------------------------------------------------------------------