├── .fvm
└── fvm_config.json
├── .github
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .metadata
├── LICENSE.md
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── rohantaneja
│ │ │ │ └── fluttercon
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── 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
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ ├── production
│ │ └── res
│ │ │ ├── drawable-hdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-mdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-v21
│ │ │ ├── background.png
│ │ │ └── launch_background.xml
│ │ │ ├── drawable-xhdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-xxhdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ └── splash.png
│ │ │ ├── drawable
│ │ │ ├── background.png
│ │ │ └── launch_background.xml
│ │ │ ├── values-night-v31
│ │ │ └── styles.xml
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ ├── values-v31
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── fastlane
│ └── Fastfile
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
└── images
│ ├── fluttercon.svg
│ ├── logo.png
│ └── splash_background.png
├── 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
│ │ └── Production.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── AppIcon-20@2x.png
│ │ │ ├── AppIcon-20@2x~ipad.png
│ │ │ ├── AppIcon-20@3x.png
│ │ │ ├── AppIcon-20~ipad.png
│ │ │ ├── AppIcon-29.png
│ │ │ ├── AppIcon-29@2x.png
│ │ │ ├── AppIcon-29@2x~ipad.png
│ │ │ ├── AppIcon-29@3x.png
│ │ │ ├── AppIcon-29~ipad.png
│ │ │ ├── AppIcon-40@2x.png
│ │ │ ├── AppIcon-40@2x~ipad.png
│ │ │ ├── AppIcon-40@3x.png
│ │ │ ├── AppIcon-40~ipad.png
│ │ │ ├── AppIcon-60@2x~car.png
│ │ │ ├── AppIcon-60@3x~car.png
│ │ │ ├── AppIcon-83.5@2x~ipad.png
│ │ │ ├── AppIcon@2x.png
│ │ │ ├── AppIcon@2x~ipad.png
│ │ │ ├── AppIcon@3x.png
│ │ │ ├── AppIcon~ios-marketing.png
│ │ │ ├── AppIcon~ipad.png
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── LaunchBackgroundProduction.imageset
│ │ │ ├── Contents.json
│ │ │ └── background.png
│ │ ├── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ └── LaunchImageProduction.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ └── LaunchImage@3x.png
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ ├── LaunchScreenProduction.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── RunnerTests
│ └── RunnerTests.swift
└── fastlane
│ └── Fastfile
├── lib
├── common
│ ├── extensions
│ │ ├── session_extensions.dart
│ │ └── speaker_extensions.dart
│ └── widgets
│ │ ├── conference_app_bar.dart
│ │ ├── error_message_widget.dart
│ │ ├── session
│ │ ├── favourite_session_icon.dart
│ │ ├── session_duration.dart
│ │ ├── session_format.dart
│ │ ├── session_room.dart
│ │ ├── sessions_list_item.dart
│ │ └── sessions_tab_bar_view.dart
│ │ ├── speaker
│ │ └── speakers_list_item.dart
│ │ └── tap_to_shrink_effect_gesture_detector.dart
├── di
│ └── injector.dart
├── features
│ ├── app
│ │ └── presentation
│ │ │ └── bloc
│ │ │ ├── app_bloc.dart
│ │ │ ├── app_event.dart
│ │ │ ├── app_state.dart
│ │ │ └── bloc.dart
│ ├── favourites
│ │ ├── data
│ │ │ └── data_source
│ │ │ │ ├── favourites_local_data_source.dart
│ │ │ │ └── favourites_local_data_source_impl.dart
│ │ ├── domain
│ │ │ ├── repository
│ │ │ │ ├── favourites_repository.dart
│ │ │ │ └── favourites_repository_impl.dart
│ │ │ └── use_case
│ │ │ │ ├── get_favourite_session_ids_use_case.dart
│ │ │ │ └── save_favourite_session_ids_use_case.dart
│ │ └── presentation
│ │ │ └── pages
│ │ │ └── favourite_sessions_page.dart
│ ├── home
│ │ └── presentation
│ │ │ ├── conference_metadata.dart
│ │ │ ├── pages
│ │ │ ├── home_page.dart
│ │ │ ├── sessions_page.dart
│ │ │ └── speakers_page.dart
│ │ │ └── widgets
│ │ │ └── conference_search_app_bar.dart
│ ├── session_details
│ │ └── presentation
│ │ │ └── pages
│ │ │ └── session_details_page.dart
│ ├── speaker_details
│ │ └── presentation
│ │ │ └── pages
│ │ │ └── speaker_details_page.dart
│ └── splash
│ │ └── flutter_native_splash-production.yaml
├── fluttercon_app.dart
└── main.dart
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Production.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── app_icon_1024.png
│ │ │ ├── app_icon_128.png
│ │ │ ├── app_icon_16.png
│ │ │ ├── app_icon_256.png
│ │ │ ├── app_icon_32.png
│ │ │ ├── app_icon_512.png
│ │ │ └── app_icon_64.png
│ ├── Base.lproj
│ │ └── MainMenu.xib
│ ├── Configs
│ │ ├── AppInfo.xcconfig
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── Warnings.xcconfig
│ ├── DebugProfile.entitlements
│ ├── Info.plist
│ ├── MainFlutterWindow.swift
│ └── Release.entitlements
└── RunnerTests
│ └── RunnerTests.swift
├── packages
├── api_client
│ ├── .gitignore
│ ├── analysis_options.yaml
│ ├── lib
│ │ ├── api_client.dart
│ │ └── src
│ │ │ └── api_client.dart
│ └── pubspec.yaml
├── conference_data
│ ├── .gitignore
│ ├── analysis_options.yaml
│ ├── lib
│ │ ├── conference_data.dart
│ │ └── src
│ │ │ ├── conference_data.dart
│ │ │ ├── data
│ │ │ ├── data_source
│ │ │ │ ├── conference_data_local_data_source.dart
│ │ │ │ ├── conference_data_local_data_source_impl.dart
│ │ │ │ ├── conference_data_remote_data_source.dart
│ │ │ │ └── conference_data_remote_data_source_impl.dart
│ │ │ └── model
│ │ │ │ ├── agenda_model.dart
│ │ │ │ ├── category_model.dart
│ │ │ │ ├── category_parent_model.dart
│ │ │ │ ├── conference_data_model.dart
│ │ │ │ ├── link_model.dart
│ │ │ │ ├── room_model.dart
│ │ │ │ ├── session_model.dart
│ │ │ │ └── speaker_model.dart
│ │ │ ├── di
│ │ │ └── injector.dart
│ │ │ └── domain
│ │ │ ├── conference_data_source.dart
│ │ │ ├── entity
│ │ │ ├── agenda.dart
│ │ │ ├── category.dart
│ │ │ ├── conference_data.dart
│ │ │ ├── link.dart
│ │ │ ├── room.dart
│ │ │ ├── session.dart
│ │ │ └── speaker.dart
│ │ │ ├── repository
│ │ │ ├── conference_data_repository.dart
│ │ │ └── conference_data_repository_impl.dart
│ │ │ └── use_case
│ │ │ └── get_conference_data_use_case.dart
│ └── pubspec.yaml
└── util
│ ├── .gitignore
│ ├── analysis_options.yaml
│ ├── lib
│ ├── src
│ │ ├── failure.dart
│ │ ├── result.dart
│ │ └── use_case.dart
│ └── util.dart
│ └── pubspec.yaml
├── pubspec.lock
├── pubspec.yaml
├── screenshots
├── sessions-android.png
├── sessions-ios.png
├── sessions-macos.png
├── sessions-search-android.png
├── sessions-search-ios.png
├── sessions-search-macos.png
├── speakers-android.png
├── speakers-ios.png
├── speakers-macos.png
├── speakers-search-android.png
├── speakers-search-ios.png
└── speakers-search-macos.png
└── store-badges
├── appstore.png
└── playstore.png
/.fvm/fvm_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "flutterSdkVersion": "3.10.5",
3 | "flavors": {}
4 | }
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | ## Status
10 |
11 | **READY / IN DEVELOPMENT / HOLD**
12 |
13 | ## Description
14 |
15 |
16 |
17 | ## Type of Change
18 |
19 |
20 |
21 | - [ ] ✨ New feature (non-breaking change which adds functionality)
22 | - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
23 | - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
24 | - [ ] 🧹 Code refactor
25 | - [ ] ✅ Build configuration change
26 | - [ ] 📝 Documentation
27 | - [ ] 🗑️ Chore
28 |
29 | ## Design Changes
30 |
31 |
32 |
33 | |Before|After|
34 | |:-:|:-:|
35 | |PASTE_BEFORE_HERE|PASTE_AFTER_HERE|
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
46 | # FVM
47 | .fvm/flutter_sdk
48 |
49 | build/
50 |
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: 796c8ef79279f9c774545b3771238c3098dbefab
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: 796c8ef79279f9c774545b3771238c3098dbefab
17 | base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
18 | - platform: android
19 | create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
20 | base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
21 | - platform: ios
22 | create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
23 | base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
24 | - platform: macos
25 | create_revision: 796c8ef79279f9c774545b3771238c3098dbefab
26 | base_revision: 796c8ef79279f9c774545b3771238c3098dbefab
27 |
28 | # User provided section
29 |
30 | # List of Local paths (relative to this file) that should be
31 | # ignored by the migrate tool.
32 | #
33 | # Files that are not part of the templates will be ignored by default.
34 | unmanaged_files:
35 | - 'lib/main.dart'
36 | - 'ios/Runner.xcodeproj/project.pbxproj'
37 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Rohan Taneja
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 | # fluttercon
2 |
3 | iOS, Android and macOS app for [Fluttercon Berlin 2023](https://fluttercon.dev), built with Flutter.
4 |
5 | [](https://pub.dev/packages/very_good_analysis)
6 |
7 | ### Download Links
8 |
9 | | iOS | Android | macOS |
10 | |:--:|:--:|:--:|
11 | |[
](https://apps.apple.com/us/app/fluttercon-berlin-2023/id6450055384)|[
](https://play.google.com/store/apps/details?id=com.rohantaneja.fluttercon)|Not published|
12 |
13 | ### Screenshots
14 |
15 | | | iOS | Android | macOS |
16 | |-----------------|-------------------------------------------------------------|---------------------------------------------------------------------|-----------------------------------------------------------------|
17 | | Sessions |  |  |  |
18 | | Speakers |  |  |  |
19 | | Sessions Search |  |  |  |
20 | | Speakers Search |  |  |  |
21 |
22 | ### Dev Notes
23 |
24 | - The project uses a line length of 120 characters
25 | - The code can ideally be re-used for any conference that uses the Sessionize API
26 | - The base url for the Sessionize API for Fluttercon has been gitignored. To run the app and see conference data, switch
27 | to the [not-for-prod/mock-api-response](https://github.com/rohan20/fluttercon/tree/not-for-prod/mock-api-response)
28 | branch that uses hardcoded Fluttercon 2023 responses instead.
29 | - When running the app, make sure you set the flavor for the build to `production`
30 | - ApiClient requires a baseUrl and so if you are running the app on the `not-for-prod/mock-api-response` branch, this can be set to `http://localhost`
31 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.5.0.0.yaml
2 |
3 | linter:
4 | rules:
5 | lines_longer_than_80_chars: false
6 | public_member_api_docs: false
7 | one_member_abstracts: false
8 |
--------------------------------------------------------------------------------
/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 |
15 | fastlane/report.xml
16 | fastlane/Appfile
17 | fastlane/service_account.json
18 | fastlane/README.md
19 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | namespace "com.rohantaneja.fluttercon"
30 | compileSdkVersion flutter.compileSdkVersion
31 | ndkVersion flutter.ndkVersion
32 |
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_1_8
35 | targetCompatibility JavaVersion.VERSION_1_8
36 | }
37 |
38 | kotlinOptions {
39 | jvmTarget = '1.8'
40 | }
41 |
42 | sourceSets {
43 | main.java.srcDirs += 'src/main/kotlin'
44 | }
45 |
46 | defaultConfig {
47 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
48 | applicationId "com.rohantaneja.fluttercon"
49 | // You can update the following values to match your application needs.
50 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
51 | minSdkVersion flutter.minSdkVersion
52 | targetSdkVersion 34
53 | versionCode flutterVersionCode.toInteger()
54 | versionName flutterVersionName
55 | }
56 |
57 | signingConfigs {
58 | release {
59 | storeFile file('../fastlane/rhntnj-keystore.jks')
60 | keyAlias 'fluttercon'
61 | keyPassword project.findProperty('fluttercon-key-password') ?: ''
62 | storePassword project.findProperty('keystore-password') ?: ''
63 | }
64 | }
65 |
66 | flavorDimensions "app"
67 |
68 | productFlavors {
69 | production {
70 | dimension "app"
71 | resValue "string", "app_name", "Fluttercon"
72 | applicationId "com.rohantaneja.fluttercon"
73 | }
74 | }
75 |
76 | buildTypes {
77 | debug {
78 | productFlavors.production.signingConfig signingConfigs.debug
79 | }
80 |
81 | release {
82 | productFlavors.production.signingConfig signingConfigs.release
83 | }
84 | }
85 | }
86 |
87 | flutter {
88 | source '../..'
89 | }
90 |
91 | dependencies {
92 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
93 | }
94 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
25 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
42 |
43 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/rohantaneja/fluttercon/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rohantaneja.fluttercon
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/android/app/src/production/res/drawable-hdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/android/app/src/production/res/drawable-mdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable-v21/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/android/app/src/production/res/drawable-v21/background.png
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/android/app/src/production/res/drawable-xhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/android/app/src/production/res/drawable-xxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/android/app/src/production/res/drawable-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/android/app/src/production/res/drawable/background.png
--------------------------------------------------------------------------------
/android/app/src/production/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/production/res/values-night-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/android/app/src/production/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/android/app/src/production/res/values-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/android/app/src/production/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 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | tasks.register("clean", Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/android/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | default_platform(:android)
2 |
3 | APP_ID_PRODUCTION = "com.rohantaneja.fluttercon"
4 |
5 | platform :android do
6 |
7 | private_lane :cleanup do
8 | sh "fvm flutter clean"
9 | sh "fvm flutter packages get"
10 | end
11 |
12 | desc "Build App"
13 | private_lane :build_app do
14 | cleanup
15 | gradle(task: "clean", print_command: true)
16 | gradle(
17 | task: "bundle",
18 | build_type: "Release",
19 | print_command: true
20 | )
21 | end
22 |
23 | desc "Upload Production App to Google Play Store"
24 | lane :upload_production do
25 | flavor = "production"
26 |
27 | build_app(app_id: APP_ID_PRODUCTION, should_build_app_bundle: true)
28 | upload_to_play_store(
29 | track: "internal",
30 | package_name: APP_ID_PRODUCTION,
31 | json_key: "./fastlane/service_account.json",
32 | aab: "../build/app/outputs/bundle/#{flavor}Release/app-#{flavor}-release.aab",
33 | )
34 | end
35 |
36 | end
--------------------------------------------------------------------------------
/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/images/fluttercon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/assets/images/logo.png
--------------------------------------------------------------------------------
/assets/images/splash_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/assets/images/splash_background.png
--------------------------------------------------------------------------------
/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 |
36 | fastlane/build/
37 | fastlane/report.xml
38 | fastlane/README.md
39 | fastlane/.env.default
40 | fastlane/Appfile
41 | fastlane/Matchfile
42 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '11.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | # Flutter flavors documentation bug: https://docs.flutter.dev/deployment/flavors#plugin-configurations
8 | # As per the documentation, this should be:
9 | # project 'Runner', {
10 | # 'Debug-Production' => :debug,
11 | # 'Profile-Production' => :release,
12 | # 'Release-Production' => :release,
13 | # }
14 | #
15 | # However, that doesn't work. The following (which is the default when a new Flutter project is created) does:
16 | project 'Runner', {
17 | 'Debug' => :debug,
18 | 'Profile' => :release,
19 | 'Release' => :release,
20 | }
21 |
22 | def flutter_root
23 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
24 | unless File.exist?(generated_xcode_build_settings_path)
25 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
26 | end
27 |
28 | File.foreach(generated_xcode_build_settings_path) do |line|
29 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
30 | return matches[1].strip if matches
31 | end
32 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
33 | end
34 |
35 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
36 |
37 | flutter_ios_podfile_setup
38 |
39 | target 'Runner' do
40 | use_frameworks!
41 | use_modular_headers!
42 |
43 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
44 | target 'RunnerTests' do
45 | inherit! :search_paths
46 | end
47 | end
48 |
49 | post_install do |installer|
50 | installer.pods_project.targets.each do |target|
51 | flutter_additional_ios_build_settings(target)
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - flutter_native_splash (0.0.1):
4 | - Flutter
5 | - FMDB (2.7.12):
6 | - FMDB/standard (= 2.7.12)
7 | - FMDB/Core (2.7.12)
8 | - FMDB/standard (2.7.12):
9 | - FMDB/Core
10 | - path_provider_foundation (0.0.1):
11 | - Flutter
12 | - FlutterMacOS
13 | - shared_preferences_foundation (0.0.1):
14 | - Flutter
15 | - FlutterMacOS
16 | - sqflite (0.0.3):
17 | - Flutter
18 | - FMDB (>= 2.7.5)
19 | - url_launcher_ios (0.0.1):
20 | - Flutter
21 |
22 | DEPENDENCIES:
23 | - Flutter (from `Flutter`)
24 | - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
25 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
26 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
27 | - sqflite (from `.symlinks/plugins/sqflite/ios`)
28 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
29 |
30 | SPEC REPOS:
31 | trunk:
32 | - FMDB
33 |
34 | EXTERNAL SOURCES:
35 | Flutter:
36 | :path: Flutter
37 | flutter_native_splash:
38 | :path: ".symlinks/plugins/flutter_native_splash/ios"
39 | path_provider_foundation:
40 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
41 | shared_preferences_foundation:
42 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
43 | sqflite:
44 | :path: ".symlinks/plugins/sqflite/ios"
45 | url_launcher_ios:
46 | :path: ".symlinks/plugins/url_launcher_ios/ios"
47 |
48 | SPEC CHECKSUMS:
49 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
50 | flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
51 | FMDB: 728731dd336af3936ce00f91d9d8495f5718a0e6
52 | path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
53 | shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
54 | sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
55 | url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
56 |
57 | PODFILE CHECKSUM: 125a6238d4c2be6899f67c499e66e295a37618c7
58 |
59 | COCOAPODS: 1.15.2
60 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Production.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "filename": "AppIcon@2x.png",
5 | "idiom": "iphone",
6 | "scale": "2x",
7 | "size": "60x60"
8 | },
9 | {
10 | "filename": "AppIcon@3x.png",
11 | "idiom": "iphone",
12 | "scale": "3x",
13 | "size": "60x60"
14 | },
15 | {
16 | "filename": "AppIcon~ipad.png",
17 | "idiom": "ipad",
18 | "scale": "1x",
19 | "size": "76x76"
20 | },
21 | {
22 | "filename": "AppIcon@2x~ipad.png",
23 | "idiom": "ipad",
24 | "scale": "2x",
25 | "size": "76x76"
26 | },
27 | {
28 | "filename": "AppIcon-83.5@2x~ipad.png",
29 | "idiom": "ipad",
30 | "scale": "2x",
31 | "size": "83.5x83.5"
32 | },
33 | {
34 | "filename": "AppIcon-40@2x.png",
35 | "idiom": "iphone",
36 | "scale": "2x",
37 | "size": "40x40"
38 | },
39 | {
40 | "filename": "AppIcon-40@3x.png",
41 | "idiom": "iphone",
42 | "scale": "3x",
43 | "size": "40x40"
44 | },
45 | {
46 | "filename": "AppIcon-40~ipad.png",
47 | "idiom": "ipad",
48 | "scale": "1x",
49 | "size": "40x40"
50 | },
51 | {
52 | "filename": "AppIcon-40@2x~ipad.png",
53 | "idiom": "ipad",
54 | "scale": "2x",
55 | "size": "40x40"
56 | },
57 | {
58 | "filename": "AppIcon-20@2x.png",
59 | "idiom": "iphone",
60 | "scale": "2x",
61 | "size": "20x20"
62 | },
63 | {
64 | "filename": "AppIcon-20@3x.png",
65 | "idiom": "iphone",
66 | "scale": "3x",
67 | "size": "20x20"
68 | },
69 | {
70 | "filename": "AppIcon-20~ipad.png",
71 | "idiom": "ipad",
72 | "scale": "1x",
73 | "size": "20x20"
74 | },
75 | {
76 | "filename": "AppIcon-20@2x~ipad.png",
77 | "idiom": "ipad",
78 | "scale": "2x",
79 | "size": "20x20"
80 | },
81 | {
82 | "filename": "AppIcon-29.png",
83 | "idiom": "iphone",
84 | "scale": "1x",
85 | "size": "29x29"
86 | },
87 | {
88 | "filename": "AppIcon-29@2x.png",
89 | "idiom": "iphone",
90 | "scale": "2x",
91 | "size": "29x29"
92 | },
93 | {
94 | "filename": "AppIcon-29@3x.png",
95 | "idiom": "iphone",
96 | "scale": "3x",
97 | "size": "29x29"
98 | },
99 | {
100 | "filename": "AppIcon-29~ipad.png",
101 | "idiom": "ipad",
102 | "scale": "1x",
103 | "size": "29x29"
104 | },
105 | {
106 | "filename": "AppIcon-29@2x~ipad.png",
107 | "idiom": "ipad",
108 | "scale": "2x",
109 | "size": "29x29"
110 | },
111 | {
112 | "filename": "AppIcon-60@2x~car.png",
113 | "idiom": "car",
114 | "scale": "2x",
115 | "size": "60x60"
116 | },
117 | {
118 | "filename": "AppIcon-60@3x~car.png",
119 | "idiom": "car",
120 | "scale": "3x",
121 | "size": "60x60"
122 | },
123 | {
124 | "filename": "AppIcon~ios-marketing.png",
125 | "idiom": "ios-marketing",
126 | "scale": "1x",
127 | "size": "1024x1024"
128 | }
129 | ],
130 | "info": {
131 | "author": "iconkitchen",
132 | "version": 1
133 | }
134 | }
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchBackgroundProduction.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "background.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchBackgroundProduction.imageset/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/LaunchBackgroundProduction.imageset/background.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/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/Assets.xcassets/LaunchImageProduction.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/LaunchImageProduction.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/LaunchImageProduction.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImageProduction.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/LaunchImageProduction.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImageProduction.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/ios/Runner/Assets.xcassets/LaunchImageProduction.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreenProduction.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 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Fluttercon
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | fluttercon
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 | $(LAUNCH_SCREEN_STORYBOARD)
29 | UIMainStoryboardFile
30 | Main
31 | UIRequiresFullScreen
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 |
37 | UIViewControllerBasedStatusBarAppearance
38 |
39 | CADisableMinimumFrameDurationOnPhone
40 |
41 | UIApplicationSupportsIndirectInputEvents
42 |
43 | UIStatusBarHidden
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ios/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | default_platform(:ios)
2 |
3 | TEAM_ID = "YV55MHHS8A"
4 | GIT_BRANCH = TEAM_ID
5 |
6 | BUNDLE_ID_PRODUCTION = "com.rohantaneja.fluttercon"
7 |
8 | def generate_identifiers(app_bundle_id, extensions)
9 | bundle_ids = [app_bundle_id]
10 |
11 | extensions.each { |appExtension|
12 | bundle_ids.push(app_bundle_id + "." + appExtension)
13 | }
14 |
15 | bundle_ids
16 | end
17 |
18 | platform :ios do
19 |
20 | desc "Delete all certificates and provisioning profiles for development, enterprise and appstore"
21 | lane :delete_all_certificates_and_provisioning_profiles do
22 | delete_certificates_and_provisioning_profiles(bundle_id: BUNDLE_ID_PRODUCTION, readonly: false)
23 | delete_certificates_and_provisioning_profiles(bundle_id: BUNDLE_ID_PRODUCTION, type: "appstore", readonly: false)
24 | end
25 |
26 | desc "Delete certificates and provisioning profiles for the provided type [development/adhoc/enterprise/appstore]"
27 | lane :delete_certificates_and_provisioning_profiles do |options|
28 | bundle_id = options[:bundle_id]
29 | type = options[:type] == nil ? "development" : options[:type]
30 |
31 | match_nuke(
32 | type: type,
33 | team_id: TEAM_ID,
34 | app_identifier: bundle_id,
35 | git_branch: GIT_BRANCH,
36 | force: options[:force] == nil ? false : options[:force],
37 | readonly: options[:readonly] == nil ? true : options[:readonly],
38 | verbose: options[:verbose],
39 | profile_name: type == "development" ? get_profile_name_development(bundle_id) : get_profile_name_appstore(bundle_id)
40 | )
41 | end
42 |
43 | # This will NOT re-create certificates and provisioning profiles if they exist on the dev portal and are valid.
44 | #
45 | # Use this lane for situations like adding a new entitlement to an app identifier on the dev portal. Fastlane will
46 | # automatically detect that the certificates on dev portal are valid so it will not re-create them. But since
47 | # provisioning profiles on dev portal won't have this new capability from the app identifier, it will re-create them.
48 | desc "Regenerate all provisioning profiles for development, enterprise and appstore"
49 | lane :regenerate_all_provisioning_profiles do |options|
50 | readonly = false
51 | force = options[:force]
52 |
53 | sync_certificates_and_provisioning_profiles(bundle_id: BUNDLE_ID_PRODUCTION, readonly: readonly, force: force)
54 | sync_certificates_and_provisioning_profiles(bundle_id: BUNDLE_ID_PRODUCTION, readonly: readonly, force: force, type: "appstore")
55 | end
56 |
57 | desc "Sync all certificates and provisioning profiles for development, enterprise and appstore"
58 | lane :sync_all_certificates_and_provisioning_profiles do |options|
59 | readonly = options[:readonly]
60 |
61 | sync_certificates_and_provisioning_profiles(bundle_id: BUNDLE_ID_PRODUCTION, readonly: readonly)
62 | sync_certificates_and_provisioning_profiles(bundle_id: BUNDLE_ID_PRODUCTION, type: "appstore", readonly: readonly)
63 | end
64 |
65 | desc "Sync certificates and provisioning profiles for the provided type [development/adhoc/enterprise/appstore]"
66 | lane :sync_certificates_and_provisioning_profiles do |options|
67 | bundle_id = options[:bundle_id]
68 | type = options[:type] == nil ? "development" : options[:type]
69 |
70 | match(
71 | type: type,
72 | team_id: TEAM_ID,
73 | app_identifier: bundle_id,
74 | git_branch: GIT_BRANCH,
75 | force: options[:force] == nil ? false : options[:force],
76 | readonly: options[:readonly] == nil ? true : options[:readonly],
77 | verbose: options[:verbose],
78 | profile_name: type == "development" ? get_profile_name_development(bundle_id) : get_profile_name_appstore(bundle_id)
79 | )
80 | end
81 |
82 | desc "Upload Production App to TestFlight"
83 | lane :upload_production do
84 | bundle_id = BUNDLE_ID_PRODUCTION
85 |
86 | build_production
87 | # TODO: Add back after Firebase integration: upload_debug_symbols_to_crashlytics(bundle_id: bundle_id)
88 | upload_to_testflight(
89 | app_identifier: bundle_id,
90 | skip_submission: true,
91 | skip_waiting_for_build_processing: true,
92 | team_id: TEAM_ID
93 | )
94 | end
95 |
96 | desc "Build - Production"
97 | lane :build_production do
98 | bundle_id = BUNDLE_ID_PRODUCTION
99 |
100 | sync_certificates_and_provisioning_profiles(bundle_id: bundle_id, type: "appstore")
101 | cleanup
102 | gym(
103 | scheme: "Production",
104 | export_method: "app-store",
105 | clean: true,
106 | include_bitcode: false,
107 | include_symbols: true,
108 | export_team_id: TEAM_ID,
109 | export_options: {
110 | signingStyle: "manual",
111 | provisioningProfiles: {
112 | bundle_id => "match AppStore #{bundle_id}",
113 | }
114 | }
115 | )
116 | end
117 |
118 | def get_profile_name_development(bundle_id)
119 | return "match Development #{bundle_id}"
120 | end
121 |
122 | def get_profile_name_appstore(bundle_id)
123 | return "match AppStore #{bundle_id}"
124 | end
125 |
126 | private_lane :cleanup do
127 | sh "fvm flutter clean"
128 | sh "fvm flutter packages get"
129 | cocoapods(clean_install: true)
130 | end
131 |
132 | def get_scheme(bundle_id)
133 | case (bundle_id)
134 | when BUNDLE_ID_PRODUCTION
135 | return "production"
136 | else
137 | UI.crash!("Scheme " + bundle_id + " seems to be invalid. Please check again.")
138 | end
139 | end
140 |
141 | end
142 |
--------------------------------------------------------------------------------
/lib/common/extensions/session_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection/collection.dart';
2 | import 'package:conference_data/conference_data.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:fluttercon/features/home/presentation/conference_metadata.dart';
5 |
6 | extension SessionExt on Session {
7 | List getSessionSpeakers({required List speakers}) {
8 | return speakers.where((speaker) => speakerIds.contains(speaker.id)).toList();
9 | }
10 |
11 | List getSessionCategories({required List categories}) {
12 | return categories.where((category) => categoryIds.contains(category.id)).toList();
13 | }
14 |
15 | Room getSessionRoom({required List rooms}) {
16 | return rooms.firstWhere((room) => room.id == roomId);
17 | }
18 |
19 | Category? getSessionFormatCategory({required List categories}) {
20 | return categories.firstWhereOrNull((category) {
21 | final isCategoryTypeSessionFormat = category.typeId == ConferenceMetadata.categoryTypeSessionFormatId;
22 |
23 | return isCategoryTypeSessionFormat && categoryIds.contains(category.id);
24 | });
25 | }
26 | }
27 |
28 | extension SessionFormatExt on String {
29 | Color get sessionFormatBorderColor {
30 | switch (this) {
31 | case ConferenceMetadata.lightningTalkId:
32 | return Colors.blue.shade500;
33 | case ConferenceMetadata.sessionId:
34 | return Colors.orange.shade500;
35 | case ConferenceMetadata.workshopId || ConferenceMetadata.keynoteId || ConferenceMetadata.panelDiscussionId:
36 | return Colors.green.shade500;
37 | default:
38 | return Colors.grey.shade500;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/common/extensions/speaker_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/conference_data.dart';
2 |
3 | extension SpeakerExt on Speaker {
4 | List getSpeakerSessions({required List sessions}) {
5 | return sessions.where((session) => session.speakerIds.contains(id)).toList();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/common/widgets/conference_app_bar.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_svg/flutter_svg.dart';
5 |
6 | class ConferenceAppBar extends StatelessWidget implements PreferredSizeWidget {
7 | const ConferenceAppBar({super.key});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return AppBar(
12 | centerTitle: true,
13 | title: SvgPicture.asset(
14 | 'assets/images/fluttercon.svg',
15 | width: min(MediaQuery.of(context).size.width * 0.4, 150),
16 | ),
17 | );
18 | }
19 |
20 | @override
21 | Size get preferredSize => const Size.fromHeight(kToolbarHeight);
22 | }
23 |
--------------------------------------------------------------------------------
/lib/common/widgets/error_message_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ErrorMessageWidget extends StatelessWidget {
4 | const ErrorMessageWidget({this.text = 'Error', this.onRefresh, super.key});
5 |
6 | final String text;
7 | final RefreshCallback? onRefresh;
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | final child = Center(child: Text(text, textAlign: TextAlign.center));
12 |
13 | if (onRefresh == null) {
14 | return child;
15 | }
16 |
17 | return RefreshIndicator(
18 | onRefresh: onRefresh!,
19 | child: CustomScrollView(
20 | slivers: [
21 | SliverFillRemaining(child: child),
22 | ],
23 | ),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/common/widgets/session/favourite_session_icon.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:fluttercon/features/app/presentation/bloc/bloc.dart';
4 |
5 | class FavouriteSessionIcon extends StatelessWidget {
6 | const FavouriteSessionIcon({
7 | required this.sessionId,
8 | this.allowTap = true,
9 | this.inactiveColor,
10 | super.key,
11 | });
12 |
13 | final String sessionId;
14 | final bool allowTap;
15 | final Color? inactiveColor;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return GestureDetector(
20 | behavior: HitTestBehavior.opaque,
21 | onTap: allowTap ? () => context.read().add(SessionFavouriteIconTappedEvent(sessionId: sessionId)) : null,
22 | child: BlocBuilder(
23 | builder: (context, state) {
24 | return Icon(
25 | state.isFavouriteSession(id: sessionId) ? Icons.favorite_rounded : Icons.favorite_outline_rounded,
26 | color: state.isFavouriteSession(id: sessionId) ? Colors.red.shade400 : inactiveColor ?? Colors.grey.shade300,
27 | size: 24,
28 | );
29 | },
30 | ),
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/common/widgets/session/session_duration.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class SessionDuration extends StatelessWidget {
4 | const SessionDuration({
5 | required this.durationInMinutes,
6 | this.isNotATalk = false,
7 | super.key,
8 | });
9 |
10 | final int durationInMinutes;
11 | final bool isNotATalk;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Container(
16 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
17 | decoration: BoxDecoration(
18 | color: isNotATalk ? Colors.grey.shade50 : durationInMinutes.backgroundColor,
19 | borderRadius: BorderRadius.circular(8),
20 | border: Border.all(color: isNotATalk ? Colors.grey.shade500 : durationInMinutes.borderColor, width: 0.5),
21 | ),
22 | child: Text(
23 | '${durationInMinutes}m',
24 | style: Theme.of(context).textTheme.labelSmall,
25 | ),
26 | );
27 | }
28 | }
29 |
30 | extension DurationExt on int {
31 | Color get backgroundColor {
32 | switch (this) {
33 | case 20:
34 | return Colors.blue.shade50;
35 | case 40:
36 | return Colors.orange.shade50;
37 | default:
38 | return Colors.green.shade50;
39 | }
40 | }
41 |
42 | Color get borderColor {
43 | switch (this) {
44 | case 20:
45 | return Colors.blue.shade500;
46 | case 40:
47 | return Colors.orange.shade500;
48 | default:
49 | return Colors.green.shade500;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/common/widgets/session/session_format.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/conference_data.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:fluttercon/common/extensions/session_extensions.dart';
4 | import 'package:fluttercon/features/home/presentation/conference_metadata.dart';
5 |
6 | class SessionFormat extends StatelessWidget {
7 | const SessionFormat({
8 | required this.sessionFormat,
9 | this.hideSessionFormatIfItIsSession = false,
10 | super.key,
11 | });
12 |
13 | final Category? sessionFormat;
14 | final bool hideSessionFormatIfItIsSession;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | if (sessionFormat == null || sessionFormat!.id == ConferenceMetadata.sessionId && hideSessionFormatIfItIsSession) {
19 | return const SizedBox();
20 | }
21 |
22 | return Container(
23 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
24 | decoration: BoxDecoration(
25 | color: sessionFormat!.id.backgroundColor,
26 | borderRadius: BorderRadius.circular(8),
27 | border: Border.all(color: sessionFormat!.id.sessionFormatBorderColor, width: 0.5),
28 | ),
29 | child: Text(
30 | sessionFormat!.name,
31 | style: Theme.of(context).textTheme.labelSmall,
32 | ),
33 | );
34 | }
35 | }
36 |
37 | extension CategoryExt on String {
38 | Color get backgroundColor {
39 | switch (this) {
40 | case ConferenceMetadata.lightningTalkId:
41 | return Colors.blue.shade50;
42 | case ConferenceMetadata.sessionId:
43 | return Colors.orange.shade50;
44 | case ConferenceMetadata.workshopId || ConferenceMetadata.keynoteId || ConferenceMetadata.panelDiscussionId:
45 | return Colors.green.shade50;
46 | default:
47 | return Colors.grey.shade200;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/common/widgets/session/session_room.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class SessionRoom extends StatelessWidget {
4 | const SessionRoom({
5 | required this.roomName,
6 | super.key,
7 | });
8 |
9 | final String roomName;
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Flexible(
14 | child: Container(
15 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
16 | decoration: BoxDecoration(
17 | color: Colors.transparent,
18 | borderRadius: BorderRadius.circular(8),
19 | border: Border.all(color: Colors.grey.shade800, width: 0.2),
20 | ),
21 | child: Row(
22 | mainAxisSize: MainAxisSize.min,
23 | children: [
24 | Icon(Icons.location_on, size: 16, color: Colors.grey.shade400),
25 | const SizedBox(width: 4),
26 | Flexible(
27 | child: Text(
28 | roomName,
29 | maxLines: 1,
30 | overflow: TextOverflow.ellipsis,
31 | style: Theme.of(context).textTheme.bodySmall,
32 | ),
33 | ),
34 | ],
35 | ),
36 | ),
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/common/widgets/speaker/speakers_list_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:conference_data/conference_data.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:fluttercon/common/widgets/tap_to_shrink_effect_gesture_detector.dart';
5 | import 'package:fluttercon/features/speaker_details/presentation/pages/speaker_details_page.dart';
6 |
7 | const _speakerImageRadius = 24.0;
8 |
9 | class SpeakersListItem extends StatelessWidget {
10 | const SpeakersListItem({
11 | required this.speaker,
12 | this.backgroundColor = Colors.transparent,
13 | this.allowTap = true,
14 | this.padding = const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
15 | super.key,
16 | });
17 |
18 | final Speaker speaker;
19 | final Color backgroundColor;
20 | final bool allowTap;
21 | final EdgeInsets padding;
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return TapToShrinkEffectGestureDetector(
26 | isEffectEnabled: allowTap,
27 | onTap: () {
28 | if (allowTap) {
29 | Navigator.of(context).push(
30 | MaterialPageRoute(
31 | builder: (_) => SpeakerDetailsPage(speaker),
32 | ),
33 | );
34 | }
35 | },
36 | child: Container(
37 | padding: padding,
38 | color: backgroundColor,
39 | child: Row(
40 | children: [
41 | _SpeakerImage(name: speaker.fullName, imageUrl: speaker.profilePictureUrl),
42 | const SizedBox(width: 16),
43 | Expanded(
44 | child: Column(
45 | crossAxisAlignment: CrossAxisAlignment.start,
46 | children: [
47 | Text(
48 | speaker.fullName,
49 | style: Theme.of(context).textTheme.titleSmall,
50 | ),
51 | if (speaker.tagLine.isNotEmpty) ...{
52 | const SizedBox(height: 4),
53 | Text(
54 | speaker.tagLine,
55 | style: Theme.of(context).textTheme.bodySmall,
56 | ),
57 | },
58 | ],
59 | ),
60 | ),
61 | ],
62 | ),
63 | ),
64 | );
65 | }
66 | }
67 |
68 | class _SpeakerImage extends StatelessWidget {
69 | const _SpeakerImage({
70 | required this.name,
71 | required this.imageUrl,
72 | });
73 |
74 | final String name;
75 | final String imageUrl;
76 |
77 | @override
78 | Widget build(BuildContext context) {
79 | return Container(
80 | decoration: BoxDecoration(
81 | shape: BoxShape.circle,
82 | border: Border.all(color: Theme.of(context).colorScheme.primary),
83 | ),
84 | child: CachedNetworkImage(
85 | imageUrl: imageUrl,
86 | imageBuilder: (_, imageProvider) {
87 | return CircleAvatar(
88 | radius: _speakerImageRadius,
89 | backgroundImage: imageProvider,
90 | );
91 | },
92 | placeholder: (_, __) => _SpeakerImageFallback(name: name),
93 | errorWidget: (_, __, ___) => _SpeakerImageFallback(name: name),
94 | ),
95 | );
96 | }
97 | }
98 |
99 | class _SpeakerImageFallback extends StatelessWidget {
100 | const _SpeakerImageFallback({
101 | required this.name,
102 | });
103 |
104 | final String name;
105 |
106 | @override
107 | Widget build(BuildContext context) {
108 | return CircleAvatar(
109 | radius: _speakerImageRadius,
110 | child: Text(
111 | name[0],
112 | style: Theme.of(context).textTheme.headlineSmall!.copyWith(color: Theme.of(context).colorScheme.primary),
113 | ),
114 | );
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/lib/common/widgets/tap_to_shrink_effect_gesture_detector.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// A different tap effect for tappable widgets. Feels like pushing a real-life button.
4 | ///
5 | /// Source:
6 | /// https://gist.github.com/rohan20/b1a15c09bbb8d18c4caa9e8c41a108c0?permalink_comment_id=3770115#gistcomment-3770115
7 | class TapToShrinkEffectGestureDetector extends StatefulWidget {
8 | const TapToShrinkEffectGestureDetector({
9 | required this.onTap,
10 | required this.child,
11 | this.isEffectEnabled = true,
12 | super.key,
13 | });
14 |
15 | final VoidCallback? onTap;
16 | final Widget child;
17 | final bool isEffectEnabled;
18 |
19 | @override
20 | State createState() => _TapToShrinkEffectGestureDetectorState();
21 | }
22 |
23 | class _TapToShrinkEffectGestureDetectorState extends State
24 | with SingleTickerProviderStateMixin {
25 | static const clickAnimationDurationMillis = 50;
26 |
27 | double _scaleTransformValue = 1;
28 |
29 | // needed for the "click" tap effect
30 | late final AnimationController animationController;
31 |
32 | @override
33 | void initState() {
34 | super.initState();
35 | animationController = AnimationController(
36 | vsync: this,
37 | duration: const Duration(milliseconds: clickAnimationDurationMillis),
38 | upperBound: 0.05,
39 | )..addListener(() {
40 | setState(() => _scaleTransformValue = 1 - animationController.value);
41 | });
42 | }
43 |
44 | @override
45 | void dispose() {
46 | animationController.dispose();
47 | super.dispose();
48 | }
49 |
50 | void _shrinkButtonSize() {
51 | animationController.forward();
52 | }
53 |
54 | void _restoreButtonSize() {
55 | Future.delayed(
56 | const Duration(milliseconds: clickAnimationDurationMillis),
57 | () => animationController.reverse(),
58 | );
59 | }
60 |
61 | @override
62 | Widget build(BuildContext context) {
63 | return GestureDetector(
64 | onTap: () {
65 | Future.delayed(
66 | Duration(milliseconds: widget.isEffectEnabled ? clickAnimationDurationMillis : 0),
67 | () => widget.onTap?.call(),
68 | );
69 |
70 | if (widget.isEffectEnabled) {
71 | _shrinkButtonSize();
72 | _restoreButtonSize();
73 | }
74 | },
75 | onTapDown: widget.isEffectEnabled ? (_) => _shrinkButtonSize() : null,
76 | onTapCancel: widget.isEffectEnabled ? _restoreButtonSize : null,
77 | child: Transform.scale(
78 | scale: _scaleTransformValue,
79 | child: widget.child,
80 | ),
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/di/injector.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/conference_data.dart';
2 | import 'package:fluttercon/features/favourites/data/data_source/favourites_local_data_source.dart';
3 | import 'package:fluttercon/features/favourites/data/data_source/favourites_local_data_source_impl.dart';
4 | import 'package:fluttercon/features/favourites/domain/repository/favourites_repository.dart';
5 | import 'package:fluttercon/features/favourites/domain/repository/favourites_repository_impl.dart';
6 | import 'package:fluttercon/features/favourites/domain/use_case/get_favourite_session_ids_use_case.dart';
7 | import 'package:fluttercon/features/favourites/domain/use_case/save_favourite_session_ids_use_case.dart';
8 | import 'package:get_it/get_it.dart';
9 | import 'package:shared_preferences/shared_preferences.dart';
10 |
11 | final injector = Injector();
12 |
13 | class Injector {
14 | Injector() : _injector = GetIt.instance;
15 |
16 | final GetIt _injector;
17 |
18 | T get() => _injector.get();
19 |
20 | void registerLazySingleton(T Function() factoryFunc) {
21 | _injector.registerLazySingleton(factoryFunc);
22 | }
23 |
24 | void registerFactory(T Function() factoryFunc) => _injector.registerFactory(factoryFunc);
25 | }
26 |
27 | Future initDependencies() async {
28 | await ConferenceData.init();
29 |
30 | injector.registerFactory(GetConferenceDataUseCase.new);
31 |
32 | // Favourites
33 | final sharedPreferences = await SharedPreferences.getInstance();
34 |
35 | injector
36 | ..registerLazySingleton(() => FavouritesLocalDataSourceImpl(sharedPreferences))
37 | ..registerLazySingleton(
38 | () => FavouritesRepositoryImpl(injector.get()),
39 | )
40 | ..registerFactory(
41 | () => SaveFavouriteSessionIdsUseCase(injector.get()),
42 | )
43 | ..registerFactory(
44 | () => GetFavouriteSessionIdsUseCase(injector.get()),
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/app_bloc.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:conference_data/conference_data.dart';
4 | import 'package:flutter_bloc/flutter_bloc.dart';
5 | import 'package:fluttercon/di/injector.dart';
6 | import 'package:fluttercon/features/app/presentation/bloc/app_event.dart';
7 | import 'package:fluttercon/features/app/presentation/bloc/app_state.dart';
8 | import 'package:fluttercon/features/favourites/domain/use_case/get_favourite_session_ids_use_case.dart';
9 | import 'package:fluttercon/features/favourites/domain/use_case/save_favourite_session_ids_use_case.dart';
10 |
11 | class AppBloc extends Bloc {
12 | AppBloc() : super(const AppState()) {
13 | on(_onAppLaunchedEvent);
14 | on(_onSessionFavouriteIconTappedEvent);
15 | on(_onSearchButtonPressedEvent);
16 | on(_onSearchCancelledEvent);
17 | on(_onSearchTextChangedEvent);
18 | on(_onPullToRefreshSessionsListEvent);
19 | on(_onPullToRefreshSpeakersListEvent);
20 | }
21 |
22 | FutureOr _onAppLaunchedEvent(event, Emitter emit) async {
23 | await _getConferenceDataAndEmitState(emit: emit);
24 | }
25 |
26 | Future _getConferenceDataAndEmitState({
27 | required Emitter emit,
28 | bool? forceLatestData,
29 | }) async {
30 | emit(state.copyWith(isLoading: true));
31 |
32 | final conferenceDataResult = await injector.get().call(forceLatestData);
33 |
34 | if (conferenceDataResult.isError()) {
35 | emit(state.copyWith(isLoading: false, isError: true));
36 | } else {
37 | final conferenceData = conferenceDataResult.getSuccess()!;
38 |
39 | final favouriteSessionIds = await _getFavouriteSessionIds();
40 |
41 | emit(
42 | state.copyWith(
43 | isLoading: false,
44 | isError: false,
45 | sessions: conferenceData.sessions,
46 | speakers: conferenceData.speakers,
47 | categories: conferenceData.categories,
48 | rooms: conferenceData.rooms,
49 | favouriteSessionIds: favouriteSessionIds,
50 | ),
51 | );
52 | }
53 | }
54 |
55 | FutureOr _onSessionFavouriteIconTappedEvent(
56 | SessionFavouriteIconTappedEvent event,
57 | Emitter emit,
58 | ) async {
59 | // Emit new state using in-memory logic so that the UI-state of the 'favourite' icon updates instantly
60 | if (state.isFavouriteSession(id: event.sessionId)) {
61 | emit(state.copyWith(favouriteSessionIds: Set.of(state.favouriteSessionIds)..remove(event.sessionId)));
62 | } else {
63 | emit(state.copyWith(favouriteSessionIds: Set.of(state.favouriteSessionIds)..add(event.sessionId)));
64 | }
65 |
66 | // Then persist the new state and re-emit from the persisted state so that it acts as the ultimate source of truth
67 | await injector.get().call(List.from(state.favouriteSessionIds));
68 |
69 | final favouriteSessionIds = await _getFavouriteSessionIds();
70 | emit(state.copyWith(favouriteSessionIds: favouriteSessionIds));
71 | }
72 |
73 | Future> _getFavouriteSessionIds() async {
74 | final favouriteSessionIdsResult = await injector.get().call();
75 | final favouriteSessionIds = favouriteSessionIdsResult.when(
76 | (_) => Set.from(state.favouriteSessionIds), // keep the old favourite session ids
77 | (favouriteSessionIds) => favouriteSessionIds.toSet(),
78 | );
79 |
80 | return favouriteSessionIds;
81 | }
82 |
83 | FutureOr _onSearchButtonPressedEvent(_, Emitter emit) {
84 | if (state.isInSearchMode) {
85 | _exitSearchMode(emit: emit);
86 | } else {
87 | emit(state.copyWith(isInSearchMode: true));
88 | }
89 | }
90 |
91 | FutureOr _onSearchCancelledEvent(_, Emitter emit) {
92 | _exitSearchMode(emit: emit);
93 | }
94 |
95 | void _exitSearchMode({required Emitter emit}) {
96 | emit(state.copyWith(isInSearchMode: false, searchTerm: ''));
97 | }
98 |
99 | FutureOr _onSearchTextChangedEvent(SearchTextChangedEvent event, Emitter emit) {
100 | emit(state.copyWith(searchTerm: event.searchTerm));
101 | }
102 |
103 | FutureOr _onPullToRefreshSessionsListEvent(_, Emitter emit) async {
104 | await _getConferenceDataAndEmitState(emit: emit, forceLatestData: true);
105 | }
106 |
107 | FutureOr _onPullToRefreshSpeakersListEvent(_, Emitter emit) async {
108 | await _getConferenceDataAndEmitState(emit: emit, forceLatestData: true);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/app_event.dart:
--------------------------------------------------------------------------------
1 | abstract class AppEvent {}
2 |
3 | class AppLaunchedEvent extends AppEvent {}
4 |
5 | class SessionFavouriteIconTappedEvent extends AppEvent {
6 | SessionFavouriteIconTappedEvent({required this.sessionId});
7 |
8 | final String sessionId;
9 | }
10 |
11 | class SearchButtonPressedEvent extends AppEvent {}
12 |
13 | class SearchCancelledEvent extends AppEvent {}
14 |
15 | class SearchTextChangedEvent extends AppEvent {
16 | SearchTextChangedEvent(this.searchTerm);
17 |
18 | final String searchTerm;
19 | }
20 |
21 | class PullToRefreshSessionsListEvent extends AppEvent {}
22 |
23 | class PullToRefreshSpeakersListEvent extends AppEvent {}
24 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/app_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/conference_data.dart';
2 | import 'package:fluttercon/common/extensions/session_extensions.dart';
3 | import 'package:fluttercon/features/home/presentation/conference_metadata.dart';
4 |
5 | class AppState {
6 | const AppState({
7 | this.isLoading = false,
8 | this.isError = false,
9 | this.sessions = const [],
10 | this.speakers = const [],
11 | this.categories = const [],
12 | this.rooms = const [],
13 | this.favouriteSessionIds = const {},
14 | this.isInSearchMode = false,
15 | this.searchTerm = '',
16 | });
17 |
18 | final bool isLoading;
19 | final bool isError;
20 | final List sessions;
21 | final List speakers;
22 | final List categories;
23 | final List rooms;
24 | final Set favouriteSessionIds;
25 | final bool isInSearchMode;
26 | final String searchTerm;
27 |
28 | AppState copyWith({
29 | bool? isLoading,
30 | bool? isError,
31 | List? sessions,
32 | List? speakers,
33 | List? categories,
34 | List? rooms,
35 | Set? favouriteSessionIds,
36 | bool? isInSearchMode,
37 | String? searchTerm,
38 | }) {
39 | return AppState(
40 | isLoading: isLoading ?? this.isLoading,
41 | isError: isError ?? this.isError,
42 | sessions: sessions ?? this.sessions,
43 | speakers: speakers ?? this.speakers,
44 | categories: categories ?? this.categories,
45 | rooms: rooms ?? this.rooms,
46 | favouriteSessionIds: favouriteSessionIds ?? this.favouriteSessionIds,
47 | isInSearchMode: isInSearchMode ?? this.isInSearchMode,
48 | searchTerm: searchTerm ?? this.searchTerm,
49 | );
50 | }
51 |
52 | List get _sessionsFilteredBySearchTerm {
53 | if (!isInSearchMode || searchTerm.isEmpty) {
54 | return List.of(sessions);
55 | }
56 |
57 | return List.of(sessions).where((session) {
58 | final sessionTitleMatchesSearchTerm = session.title.containsIgnoreCase(searchTerm);
59 |
60 | final sessionSpeakers = session.getSessionSpeakers(speakers: speakers);
61 |
62 | final speakerNameOrTagLineMatchSearchTerm = sessionSpeakers.any((speaker) {
63 | return speaker.fullName.containsIgnoreCase(searchTerm) || speaker.tagLine.containsIgnoreCase(searchTerm);
64 | });
65 |
66 | return sessionTitleMatchesSearchTerm || speakerNameOrTagLineMatchSearchTerm;
67 | }).toList();
68 | }
69 |
70 | List get _sessionsSortedByStartTime {
71 | return _sessionsFilteredBySearchTerm.toList()..sort((a, b) => a.startsAt.compareTo(b.startsAt));
72 | }
73 |
74 | List _getSessionsForDay(DateTime day) {
75 | return _sessionsSortedByStartTime.where((session) => session.startsAt.isSameDayAs(day)).toList();
76 | }
77 |
78 | List get day1SessionsSortedByStartTime => _getSessionsForDay(ConferenceMetadata.day1);
79 |
80 | List get day1FavouriteSessionsSortedByStartTime {
81 | return day1SessionsSortedByStartTime.where(_isFavouriteSession).toList();
82 | }
83 |
84 | List get day2SessionsSortedByStartTime => _getSessionsForDay(ConferenceMetadata.day2);
85 |
86 | List get day2FavouriteSessionsSortedByStartTime {
87 | return day2SessionsSortedByStartTime.where(_isFavouriteSession).toList();
88 | }
89 |
90 | bool _isFavouriteSession(Session session) => favouriteSessionIds.contains(session.id);
91 |
92 | bool isFavouriteSession({required String id}) => favouriteSessionIds.contains(id);
93 |
94 | int get filteredSessionsCount => _sessionsFilteredBySearchTerm.length;
95 |
96 | int get filteredFavouriteSessionsCount {
97 | return _sessionsFilteredBySearchTerm.where(_isFavouriteSession).length;
98 | }
99 |
100 | List get _speakersFilteredBySearchTerm {
101 | if (!isInSearchMode || searchTerm.isEmpty) {
102 | return List.of(speakers);
103 | }
104 |
105 | return List.of(speakers).where((speaker) {
106 | return speaker.fullName.containsIgnoreCase(searchTerm) || speaker.tagLine.containsIgnoreCase(searchTerm);
107 | }).toList();
108 | }
109 |
110 | List get filteredSpeakers => _speakersFilteredBySearchTerm;
111 |
112 | int get filteredSpeakersCount => filteredSpeakers.length;
113 | }
114 |
115 | extension _DateTimeExt on DateTime {
116 | bool isSameDayAs(DateTime other) {
117 | return year == other.year && month == other.month && day == other.day;
118 | }
119 | }
120 |
121 | extension _StringExt on String {
122 | bool containsIgnoreCase(String other) => toLowerCase().contains(other.toLowerCase());
123 | }
124 |
--------------------------------------------------------------------------------
/lib/features/app/presentation/bloc/bloc.dart:
--------------------------------------------------------------------------------
1 | export 'package:fluttercon/features/app/presentation/bloc/app_bloc.dart';
2 | export 'package:fluttercon/features/app/presentation/bloc/app_event.dart';
3 | export 'package:fluttercon/features/app/presentation/bloc/app_state.dart';
4 |
--------------------------------------------------------------------------------
/lib/features/favourites/data/data_source/favourites_local_data_source.dart:
--------------------------------------------------------------------------------
1 | abstract class FavouritesLocalDataSource {
2 | Future saveFavouriteSessionIds(List sessionIds);
3 |
4 | Future> getFavouriteSessionIds();
5 | }
6 |
--------------------------------------------------------------------------------
/lib/features/favourites/data/data_source/favourites_local_data_source_impl.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluttercon/features/favourites/data/data_source/favourites_local_data_source.dart';
2 | import 'package:shared_preferences/shared_preferences.dart';
3 |
4 | class FavouritesLocalDataSourceImpl implements FavouritesLocalDataSource {
5 | FavouritesLocalDataSourceImpl(this.sharedPreferences);
6 |
7 | static const _favouriteSessionIdsKey = 'favouriteSessionIds-2024-usa';
8 |
9 | final SharedPreferences sharedPreferences;
10 |
11 | @override
12 | Future saveFavouriteSessionIds(List sessionIds) async {
13 | await sharedPreferences.setStringList(_favouriteSessionIdsKey, sessionIds);
14 | // Delete legacy data from older conferences (applicable in case the user didn't uninstall the app between conferences)
15 | await sharedPreferences.remove('favouriteSessionIds');
16 | }
17 |
18 | @override
19 | Future> getFavouriteSessionIds() async {
20 | return sharedPreferences.getStringList(_favouriteSessionIdsKey) ?? [];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/features/favourites/domain/repository/favourites_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:util/util.dart';
2 |
3 | abstract class FavouritesRepository {
4 | Future> saveFavouriteSessionIds(List sessionIds);
5 |
6 | Future>> getFavouriteSessionIds();
7 | }
8 |
--------------------------------------------------------------------------------
/lib/features/favourites/domain/repository/favourites_repository_impl.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluttercon/features/favourites/data/data_source/favourites_local_data_source.dart';
2 | import 'package:fluttercon/features/favourites/domain/repository/favourites_repository.dart';
3 | import 'package:util/util.dart';
4 |
5 | class FavouritesRepositoryImpl implements FavouritesRepository {
6 | FavouritesRepositoryImpl(this.favouritesLocalDataSource);
7 |
8 | final FavouritesLocalDataSource favouritesLocalDataSource;
9 |
10 | @override
11 | Future> saveFavouriteSessionIds(List sessionIds) async {
12 | try {
13 | return Success(await favouritesLocalDataSource.saveFavouriteSessionIds(sessionIds));
14 | } catch (_) {
15 | return const Error(LocalFailure());
16 | }
17 | }
18 |
19 | @override
20 | Future>> getFavouriteSessionIds() async {
21 | try {
22 | return Success(await favouritesLocalDataSource.getFavouriteSessionIds());
23 | } catch (_) {
24 | return const Error(LocalFailure());
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/features/favourites/domain/use_case/get_favourite_session_ids_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluttercon/features/favourites/domain/repository/favourites_repository.dart';
2 | import 'package:util/util.dart';
3 |
4 | class GetFavouriteSessionIdsUseCase extends UseCase, NoParams> {
5 | GetFavouriteSessionIdsUseCase(this.favouritesRepository);
6 |
7 | final FavouritesRepository favouritesRepository;
8 |
9 | @override
10 | Future>> call([NoParams? params]) async {
11 | return favouritesRepository.getFavouriteSessionIds();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/features/favourites/domain/use_case/save_favourite_session_ids_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluttercon/features/favourites/domain/repository/favourites_repository.dart';
2 | import 'package:util/util.dart';
3 |
4 | class SaveFavouriteSessionIdsUseCase extends UseCase> {
5 | SaveFavouriteSessionIdsUseCase(this.favouritesRepository);
6 |
7 | final FavouritesRepository favouritesRepository;
8 |
9 | @override
10 | Future> call([List? params]) async {
11 | return favouritesRepository.saveFavouriteSessionIds(params ?? []);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/features/favourites/presentation/pages/favourite_sessions_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:fluttercon/common/widgets/error_message_widget.dart';
4 | import 'package:fluttercon/common/widgets/session/sessions_tab_bar_view.dart';
5 | import 'package:fluttercon/features/app/presentation/bloc/bloc.dart';
6 |
7 | class FavouriteSessionsPage extends StatelessWidget {
8 | const FavouriteSessionsPage({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return BlocBuilder(
13 | builder: (context, state) {
14 | if (state.isLoading) {
15 | return const Center(
16 | child: CircularProgressIndicator(),
17 | );
18 | } else if (state.isError) {
19 | return ErrorMessageWidget(
20 | onRefresh: () async => context.read().add(PullToRefreshSessionsListEvent()),
21 | );
22 | } else {
23 | return SessionsTabBarView(
24 | day1SessionsSortedByStartTime: state.day1FavouriteSessionsSortedByStartTime,
25 | day2SessionsSortedByStartTime: state.day2FavouriteSessionsSortedByStartTime,
26 | speakers: state.speakers,
27 | categories: state.categories,
28 | rooms: state.rooms,
29 | emptySessionsMessage: state.isInSearchMode && state.searchTerm.isNotEmpty
30 | ? "No favourite sessions found for this day for the search term '${state.searchTerm}'"
31 | : 'No favourite sessions found for this day',
32 | );
33 | }
34 | },
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/features/home/presentation/conference_metadata.dart:
--------------------------------------------------------------------------------
1 | class ConferenceMetadata {
2 | const ConferenceMetadata._();
3 |
4 | static final DateTime day1 = DateTime(2024, 09, 19, 20);
5 | static final DateTime day2 = DateTime(2024, 09, 20, 20);
6 |
7 | // Get these from 'https://sessionize.com/api/v2//view/all'
8 | static const String lightningTalkId = '254923';
9 | static const String sessionId = '254924';
10 | static const String workshopId = '254925';
11 | static const String keynoteId = '254927';
12 | static const String panelDiscussionId = '254926';
13 |
14 | static const String categoryTypeSessionFormatId = '72066';
15 | }
16 |
--------------------------------------------------------------------------------
/lib/features/home/presentation/pages/sessions_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:fluttercon/common/widgets/error_message_widget.dart';
4 | import 'package:fluttercon/common/widgets/session/sessions_tab_bar_view.dart';
5 | import 'package:fluttercon/features/app/presentation/bloc/bloc.dart';
6 |
7 | class SessionsPage extends StatelessWidget {
8 | const SessionsPage({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return BlocBuilder(
13 | builder: (context, state) {
14 | if (state.isLoading) {
15 | return const Center(
16 | child: CircularProgressIndicator(),
17 | );
18 | } else if (state.isError) {
19 | return ErrorMessageWidget(
20 | onRefresh: () async => context.read().add(PullToRefreshSessionsListEvent()),
21 | );
22 | } else {
23 | return SessionsTabBarView(
24 | day1SessionsSortedByStartTime: state.day1SessionsSortedByStartTime,
25 | day2SessionsSortedByStartTime: state.day2SessionsSortedByStartTime,
26 | speakers: state.speakers,
27 | categories: state.categories,
28 | rooms: state.rooms,
29 | emptySessionsMessage: state.isInSearchMode && state.searchTerm.isNotEmpty
30 | ? "No sessions found for this day for the search term '${state.searchTerm}'"
31 | : 'No sessions found for this day',
32 | );
33 | }
34 | },
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/features/home/presentation/pages/speakers_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/conference_data.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:fluttercon/common/widgets/error_message_widget.dart';
5 | import 'package:fluttercon/common/widgets/speaker/speakers_list_item.dart';
6 | import 'package:fluttercon/features/app/presentation/bloc/bloc.dart';
7 |
8 | class SpeakersPage extends StatelessWidget {
9 | const SpeakersPage({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return BlocBuilder(
14 | builder: (context, state) {
15 | if (state.isLoading) {
16 | return const Center(
17 | child: CircularProgressIndicator(),
18 | );
19 | } else if (state.isError) {
20 | return ErrorMessageWidget(
21 | onRefresh: () async => context.read().add(PullToRefreshSpeakersListEvent()),
22 | );
23 | } else {
24 | return _SpeakersList(
25 | speakers: state.filteredSpeakers,
26 | emptySpeakersMessage: state.isInSearchMode && state.searchTerm.isNotEmpty
27 | ? "No speakers found for the search term '${state.searchTerm}'"
28 | : 'No speakers found',
29 | );
30 | }
31 | },
32 | );
33 | }
34 | }
35 |
36 | class _SpeakersList extends StatelessWidget {
37 | const _SpeakersList({
38 | required this.speakers,
39 | required this.emptySpeakersMessage,
40 | });
41 |
42 | final List speakers;
43 | final String emptySpeakersMessage;
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | return RefreshIndicator(
48 | onRefresh: () async => context.read().add(PullToRefreshSpeakersListEvent()),
49 | child: speakers.isEmpty
50 | ? CustomScrollView(
51 | slivers: [
52 | SliverFillRemaining(
53 | child: Center(child: Text(emptySpeakersMessage, textAlign: TextAlign.center)),
54 | ),
55 | ],
56 | )
57 | : ListView.builder(
58 | padding: const EdgeInsets.only(top: 12, bottom: 90),
59 | itemCount: speakers.length,
60 | itemBuilder: (BuildContext context, int index) {
61 | final speaker = speakers[index];
62 |
63 | return SpeakersListItem(
64 | speaker: speaker,
65 | backgroundColor: index.isEven ? Colors.transparent : Colors.grey.shade50,
66 | );
67 | },
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/features/home/presentation/widgets/conference_search_app_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:fluttercon/features/app/presentation/bloc/bloc.dart';
4 |
5 | class ConferenceSearchAppBar extends StatelessWidget implements PreferredSizeWidget {
6 | const ConferenceSearchAppBar({this.focusNode, super.key});
7 |
8 | final FocusNode? focusNode;
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return AppBar(
13 | title: TextField(
14 | autofocus: true,
15 | focusNode: focusNode,
16 | decoration: InputDecoration(
17 | hintText: 'Talk title / Speaker name or tagline',
18 | hintStyle: Theme.of(context).textTheme.labelSmall!.copyWith(
19 | fontSize: 14,
20 | color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5),
21 | ),
22 | border: InputBorder.none,
23 | prefixIcon: const Icon(Icons.search),
24 | suffixIcon: IconButton(
25 | icon: const Icon(Icons.close),
26 | onPressed: () => context.read().add(SearchCancelledEvent()),
27 | ),
28 | ),
29 | onChanged: (query) => context.read().add(SearchTextChangedEvent(query)),
30 | ),
31 | );
32 | }
33 |
34 | @override
35 | Size get preferredSize => const Size.fromHeight(kToolbarHeight);
36 | }
37 |
--------------------------------------------------------------------------------
/lib/features/session_details/presentation/pages/session_details_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/conference_data.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_bloc/flutter_bloc.dart';
4 | import 'package:fluttercon/common/extensions/session_extensions.dart';
5 | import 'package:fluttercon/common/widgets/conference_app_bar.dart';
6 | import 'package:fluttercon/common/widgets/session/favourite_session_icon.dart';
7 | import 'package:fluttercon/common/widgets/session/session_duration.dart';
8 | import 'package:fluttercon/common/widgets/session/session_format.dart';
9 | import 'package:fluttercon/common/widgets/session/session_room.dart';
10 | import 'package:fluttercon/common/widgets/speaker/speakers_list_item.dart';
11 | import 'package:fluttercon/features/app/presentation/bloc/bloc.dart';
12 | import 'package:intl/intl.dart';
13 |
14 | class SessionDetailsPage extends StatelessWidget {
15 | const SessionDetailsPage(this.session, {super.key});
16 |
17 | final Session session;
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return Scaffold(
22 | appBar: const ConferenceAppBar(),
23 | body: Padding(
24 | padding: const EdgeInsets.symmetric(horizontal: 16),
25 | child: SingleChildScrollView(
26 | child: BlocBuilder(
27 | builder: (context, state) {
28 | final sessionSpeakers = session.getSessionSpeakers(speakers: state.speakers);
29 | final sessionCategories = session.getSessionCategories(categories: state.categories);
30 | final sessionFormatCategory = session.getSessionFormatCategory(categories: sessionCategories);
31 | final roomName = session.getSessionRoom(rooms: state.rooms).name;
32 |
33 | return Column(
34 | crossAxisAlignment: CrossAxisAlignment.start,
35 | children: [
36 | const SizedBox(height: 16),
37 | Align(child: _SessionTitle(session.title)),
38 | const SizedBox(height: 24),
39 | Align(child: _SessionDateTime(session.startsAt)),
40 | const SizedBox(height: 12),
41 | Row(
42 | mainAxisAlignment: MainAxisAlignment.center,
43 | children: [
44 | SessionFormat(sessionFormat: sessionFormatCategory),
45 | const SizedBox(width: 8),
46 | SessionRoom(roomName: roomName),
47 | const SizedBox(width: 8),
48 | SessionDuration(durationInMinutes: session.duration),
49 | ],
50 | ),
51 | const SizedBox(height: 24),
52 | for (final speaker in sessionSpeakers) ...{
53 | SpeakersListItem(speaker: speaker),
54 | },
55 | const SizedBox(height: 16),
56 | Text('Description:', style: Theme.of(context).textTheme.bodyMedium),
57 | const SizedBox(height: 12),
58 | Text(
59 | session.description,
60 | style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14),
61 | ),
62 | const SizedBox(height: 16),
63 | Text('Categories:', style: Theme.of(context).textTheme.bodyMedium),
64 | const SizedBox(height: 12),
65 | _SessionCategories(
66 | categories: List.from(sessionCategories)
67 | ..removeWhere(
68 | (category) => category.id == sessionFormatCategory?.id,
69 | ),
70 | ),
71 | const SizedBox(height: 120), // big enough to ensure that the FAB doesn't hide any content below it
72 | ],
73 | );
74 | },
75 | ),
76 | ),
77 | ),
78 | floatingActionButton: FloatingActionButton(
79 | onPressed: () => context.read().add(SessionFavouriteIconTappedEvent(sessionId: session.id)),
80 | child: FavouriteSessionIcon(
81 | sessionId: session.id,
82 | allowTap: false, // to prevent the icon from being clicked directly which would mean no FAB splash effect
83 | inactiveColor: Colors.black45,
84 | ),
85 | ),
86 | );
87 | }
88 | }
89 |
90 | class _SessionCategories extends StatelessWidget {
91 | const _SessionCategories({
92 | required this.categories,
93 | });
94 |
95 | final List categories;
96 |
97 | @override
98 | Widget build(BuildContext context) {
99 | return Wrap(
100 | spacing: 4,
101 | runSpacing: 8,
102 | children: categories.map(
103 | (category) {
104 | return Container(
105 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
106 | decoration: BoxDecoration(
107 | color: Colors.deepPurple.shade50,
108 | borderRadius: BorderRadius.circular(8),
109 | ),
110 | child: Text(
111 | category.name,
112 | style: Theme.of(context).textTheme.labelSmall,
113 | ),
114 | );
115 | },
116 | ).toList(),
117 | );
118 | }
119 | }
120 |
121 | class _SessionTitle extends StatelessWidget {
122 | const _SessionTitle(this.title);
123 |
124 | final String title;
125 |
126 | @override
127 | Widget build(BuildContext context) {
128 | return Text(
129 | title,
130 | style: Theme.of(context).textTheme.titleLarge,
131 | textAlign: TextAlign.center,
132 | );
133 | }
134 | }
135 |
136 | class _SessionDateTime extends StatelessWidget {
137 | const _SessionDateTime(this.startsAt);
138 |
139 | final DateTime startsAt;
140 |
141 | @override
142 | Widget build(BuildContext context) {
143 | return Text.rich(
144 | TextSpan(
145 | children: [
146 | WidgetSpan(
147 | child: Icon(Icons.access_time, size: 16, color: Colors.black87.withOpacity(0.8)),
148 | ),
149 | const WidgetSpan(child: SizedBox(width: 4)),
150 | TextSpan(
151 | text: DateFormat('EEE, dd MMM').format(startsAt),
152 | style: Theme.of(context).textTheme.labelLarge,
153 | ),
154 | TextSpan(
155 | text: ' at ',
156 | style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14),
157 | ),
158 | TextSpan(
159 | text: DateFormat('HH:mm').format(startsAt),
160 | style: Theme.of(context).textTheme.labelLarge,
161 | ),
162 | ],
163 | ),
164 | );
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/lib/features/speaker_details/presentation/pages/speaker_details_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/conference_data.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_bloc/flutter_bloc.dart';
5 | import 'package:fluttercon/common/widgets/conference_app_bar.dart';
6 | import 'package:fluttercon/common/widgets/session/sessions_list_item.dart';
7 | import 'package:fluttercon/common/widgets/speaker/speakers_list_item.dart';
8 | import 'package:fluttercon/features/app/presentation/bloc/bloc.dart';
9 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
10 | import 'package:url_launcher/url_launcher.dart';
11 |
12 | class SpeakerDetailsPage extends StatelessWidget {
13 | const SpeakerDetailsPage(this.speaker, {super.key});
14 |
15 | final Speaker speaker;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Scaffold(
20 | appBar: const ConferenceAppBar(),
21 | body: Padding(
22 | padding: const EdgeInsets.symmetric(horizontal: 16),
23 | child: SingleChildScrollView(
24 | child: Column(
25 | crossAxisAlignment: CrossAxisAlignment.start,
26 | children: [
27 | const SizedBox(height: 16),
28 | SpeakersListItem(
29 | speaker: speaker,
30 | allowTap: false,
31 | padding: EdgeInsets.zero,
32 | ),
33 | if (speaker.bio.trim().isNotEmpty) ...{
34 | const SizedBox(height: 16),
35 | Text('Bio:', style: Theme.of(context).textTheme.bodyMedium),
36 | const SizedBox(height: 12),
37 | Text(
38 | speaker.bio.trim(),
39 | style: Theme.of(context).textTheme.bodySmall!.copyWith(fontSize: 14),
40 | ),
41 | },
42 | const SizedBox(height: 16),
43 | if (speaker.links.isNotEmpty) ...{
44 | Text('Links:', style: Theme.of(context).textTheme.bodyMedium),
45 | const SizedBox(height: 12),
46 | _SpeakerLinks(links: speaker.links),
47 | },
48 | Builder(
49 | builder: (context) {
50 | final speakerSessions = context.watch().state.sessions.where(
51 | (session) => session.speakerIds.any((speakerId) => speakerId == speaker.id),
52 | );
53 |
54 | if (speakerSessions.isEmpty) {
55 | return const SizedBox.shrink();
56 | }
57 |
58 | return Column(
59 | crossAxisAlignment: CrossAxisAlignment.start,
60 | children: [
61 | const SizedBox(height: 16),
62 | Text('Sessions:', style: Theme.of(context).textTheme.bodyMedium),
63 | const SizedBox(height: 12),
64 | for (final session in speakerSessions) ...{
65 | SessionsListItem(
66 | session: session,
67 | sessionTimeVisibility: SessionTimeVisibility.gone,
68 | padding: const EdgeInsets.only(bottom: 12),
69 | ),
70 | },
71 | ],
72 | );
73 | },
74 | ),
75 | const SizedBox(height: 32),
76 | ],
77 | ),
78 | ),
79 | ),
80 | );
81 | }
82 | }
83 |
84 | class _SpeakerLinks extends StatelessWidget {
85 | const _SpeakerLinks({
86 | required this.links,
87 | });
88 |
89 | final List links;
90 |
91 | @override
92 | Widget build(BuildContext context) {
93 | return Wrap(
94 | spacing: 4,
95 | runSpacing: 8,
96 | children: links.map(
97 | (link) {
98 | return GestureDetector(
99 | onTap: () async {
100 | final linkUri = Uri.parse(link.url);
101 |
102 | if (await canLaunchUrl(linkUri)) {
103 | try {
104 | await launchUrl(linkUri);
105 | } catch (e) {
106 | // ignore: use_build_context_synchronously
107 | _showFailedToLaunchUrlSnackbar(context: context, url: link.url);
108 | }
109 | } else {
110 | // ignore: use_build_context_synchronously
111 | _showFailedToLaunchUrlSnackbar(context: context, url: link.url);
112 | }
113 | },
114 | child: Container(
115 | padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
116 | decoration: BoxDecoration(
117 | color: Theme.of(context).primaryColor.withOpacity(0.1),
118 | borderRadius: BorderRadius.circular(8),
119 | ),
120 | child: Row(
121 | mainAxisSize: MainAxisSize.min,
122 | children: [
123 | Icon(link.iconData, size: 14, color: Colors.grey.shade700),
124 | const SizedBox(width: 6),
125 | Text(
126 | link.title,
127 | style: Theme.of(context).textTheme.labelSmall,
128 | ),
129 | ],
130 | ),
131 | ),
132 | );
133 | },
134 | ).toList(),
135 | );
136 | }
137 |
138 | void _showFailedToLaunchUrlSnackbar({required BuildContext context, required String url}) {
139 | if (!context.mounted) {
140 | return;
141 | }
142 |
143 | ScaffoldMessenger.of(context).showSnackBar(
144 | SnackBar(
145 | content: Text('Could not open url: $url'),
146 | action: SnackBarAction(
147 | label: 'Copy',
148 | onPressed: () => Clipboard.setData(ClipboardData(text: url)),
149 | ),
150 | ),
151 | );
152 | }
153 | }
154 |
155 | extension _LinkExt on Link {
156 | IconData get iconData {
157 | switch (type.toLowerCase()) {
158 | case 'twitter':
159 | return FontAwesomeIcons.twitter;
160 | case 'instagram':
161 | return FontAwesomeIcons.instagram;
162 | case 'linkedin':
163 | return FontAwesomeIcons.linkedin;
164 | default:
165 | return FontAwesomeIcons.globe;
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/lib/features/splash/flutter_native_splash-production.yaml:
--------------------------------------------------------------------------------
1 | flutter_native_splash:
2 | # background_image generated via: https://angrytools.com/gradient/image/
3 | background_image: assets/images/splash_background.png
4 | image: assets/images/logo.png
5 |
6 | android_12:
7 | # background_image doesn't work with Android 12, hence a background color is being used:
8 | # https://pub.dev/packages/flutter_native_splash#android-12-support
9 | color: "#8ad7fc"
10 |
11 | web: false
12 |
13 | android_gravity: center
14 | ios_content_mode: center
15 |
--------------------------------------------------------------------------------
/lib/fluttercon_app.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_bloc/flutter_bloc.dart';
3 | import 'package:fluttercon/features/app/presentation/bloc/bloc.dart';
4 | import 'package:fluttercon/features/home/presentation/pages/home_page.dart';
5 | import 'package:google_fonts/google_fonts.dart';
6 |
7 | class FlutterconApp extends StatelessWidget {
8 | const FlutterconApp({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return MultiBlocProvider(
13 | providers: [
14 | BlocProvider(
15 | create: (_) => AppBloc()..add(AppLaunchedEvent()),
16 | ),
17 | ],
18 | child: MaterialApp(
19 | title: 'Fluttercon',
20 | theme: ThemeData(
21 | colorScheme: ColorScheme.fromSeed(
22 | // Color picked from the 'con' part of 'Fluttercon' logo on https://fluttercon.dev
23 | seedColor: const Color.fromRGBO(1, 53, 255, 1),
24 | ),
25 | useMaterial3: true,
26 | textTheme: GoogleFonts.capriolaTextTheme(),
27 | ),
28 | home: const HomePage(),
29 | ),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:fluttercon/di/injector.dart';
3 | import 'package:fluttercon/fluttercon_app.dart';
4 |
5 | Future main() async {
6 | WidgetsFlutterBinding.ensureInitialized();
7 |
8 | await initDependencies();
9 |
10 | runApp(const FlutterconApp());
11 | }
12 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import path_provider_foundation
9 | import shared_preferences_foundation
10 | import sqflite
11 | import url_launcher_macos
12 |
13 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
14 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
15 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
16 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
17 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
18 | }
19 |
--------------------------------------------------------------------------------
/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.14'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | # Issue: https://github.com/flutter/flutter/issues/122684
7 | # Temporary fix (used currently below): https://github.com/flutter/flutter/issues/122684#issuecomment-1568139743
8 | # Permanent fix (not yet released): https://github.com/flutter/flutter/pull/127997
9 | project 'Runner', {
10 | 'Debug Production' => :debug,
11 | 'Profile Production' => :release,
12 | 'Release Production' => :release,
13 | }
14 |
15 | def flutter_root
16 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
17 | unless File.exist?(generated_xcode_build_settings_path)
18 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
19 | end
20 |
21 | File.foreach(generated_xcode_build_settings_path) do |line|
22 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
23 | return matches[1].strip if matches
24 | end
25 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
26 | end
27 |
28 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
29 |
30 | flutter_macos_podfile_setup
31 |
32 | target 'Runner' do
33 | use_frameworks!
34 | use_modular_headers!
35 |
36 | flutter_install_all_macos_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_macos_build_settings(target)
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/macos/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - FlutterMacOS (1.0.0)
3 | - FMDB (2.7.5):
4 | - FMDB/standard (= 2.7.5)
5 | - FMDB/standard (2.7.5)
6 | - path_provider_foundation (0.0.1):
7 | - Flutter
8 | - FlutterMacOS
9 | - shared_preferences_foundation (0.0.1):
10 | - Flutter
11 | - FlutterMacOS
12 | - sqflite (0.0.2):
13 | - FlutterMacOS
14 | - FMDB (>= 2.7.5)
15 | - url_launcher_macos (0.0.1):
16 | - FlutterMacOS
17 |
18 | DEPENDENCIES:
19 | - FlutterMacOS (from `Flutter/ephemeral`)
20 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
21 | - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
22 | - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
23 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
24 |
25 | SPEC REPOS:
26 | trunk:
27 | - FMDB
28 |
29 | EXTERNAL SOURCES:
30 | FlutterMacOS:
31 | :path: Flutter/ephemeral
32 | path_provider_foundation:
33 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
34 | shared_preferences_foundation:
35 | :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
36 | sqflite:
37 | :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
38 | url_launcher_macos:
39 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
40 |
41 | SPEC CHECKSUMS:
42 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
43 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
44 | path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
45 | shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
46 | sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
47 | url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451
48 |
49 | PODFILE CHECKSUM: e335a526d353f8faa28f74c9b08e24040ae645bf
50 |
51 | COCOAPODS: 1.11.3
52 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/xcshareddata/xcschemes/Production.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = fluttercon
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.rohantaneja.fluttercon
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.rohantaneja. All rights reserved.
15 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 | com.apple.security.network.client
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macos/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import FlutterMacOS
2 | import Cocoa
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 |
--------------------------------------------------------------------------------
/packages/api_client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | build/
7 | pubspec.lock
--------------------------------------------------------------------------------
/packages/api_client/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.5.0.0.yaml
2 |
3 | linter:
4 | rules:
5 | lines_longer_than_80_chars: false
6 | public_member_api_docs: false
7 | one_member_abstracts: false
8 |
--------------------------------------------------------------------------------
/packages/api_client/lib/api_client.dart:
--------------------------------------------------------------------------------
1 | /// Client to make network calls
2 | library api_client;
3 |
4 | export 'src/api_client.dart';
5 |
--------------------------------------------------------------------------------
/packages/api_client/lib/src/api_client.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:dio/dio.dart';
4 |
5 | /// {@template api_client_error}
6 | /// Error throw when accessing api failed.
7 | ///
8 | /// Check [cause] and [stackTrace] for specific details.
9 | /// {@endtemplate}
10 | class ApiClientError implements Exception {
11 | /// {@macro api_client_error}
12 | ApiClientError(this.cause, this.stackTrace);
13 |
14 | /// Error cause.
15 | final dynamic cause;
16 |
17 | /// The stack trace of the error.
18 | final StackTrace stackTrace;
19 |
20 | @override
21 | String toString() {
22 | return cause.toString();
23 | }
24 | }
25 |
26 | /// {@template api_client}
27 | /// Client to access the api
28 | /// {@endtemplate}
29 | class ApiClient {
30 | /// {@macro api_client}
31 | ApiClient({
32 | required String baseUrl,
33 | Duration connectTimeout = const Duration(seconds: 5),
34 | Duration receiveTimeout = const Duration(seconds: 10),
35 | Duration sendTimeout = const Duration(seconds: 10),
36 | }) : _dio = Dio(
37 | BaseOptions(
38 | baseUrl: baseUrl,
39 | connectTimeout: connectTimeout,
40 | receiveTimeout: receiveTimeout,
41 | sendTimeout: sendTimeout,
42 | ),
43 | );
44 |
45 | final Dio _dio;
46 |
47 | /// Sends a GET request to the specified [path].
48 | Future> get(
49 | String path,
50 | ) async {
51 | final response = await _dio.get(
52 | path,
53 | options: Options(
54 | headers: {
55 | HttpHeaders.contentTypeHeader: ContentType.json.value,
56 | },
57 | ),
58 | );
59 |
60 | return response;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/api_client/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: api_client
2 | description: Client to make network calls
3 | version: 0.1.0+1
4 | publish_to: none
5 |
6 | environment:
7 | sdk: '>=3.0.5 <4.0.0'
8 |
9 | dependencies:
10 | dio: ^5.2.1+1
11 |
12 | dev_dependencies:
13 | mocktail: ^0.3.0
14 | test: ^1.19.2
15 | very_good_analysis: ^5.0.0+1
16 |
--------------------------------------------------------------------------------
/packages/conference_data/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | build/
7 | pubspec.lock
8 |
9 | lib/src/conference_secrets.dart
10 |
--------------------------------------------------------------------------------
/packages/conference_data/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.5.0.0.yaml
2 |
3 | linter:
4 | rules:
5 | lines_longer_than_80_chars: false
6 | public_member_api_docs: false
7 | one_member_abstracts: false
8 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/conference_data.dart:
--------------------------------------------------------------------------------
1 | /// All things related to the conference data
2 | library conference_data;
3 |
4 | export 'src/conference_data.dart';
5 | export 'src/domain/conference_data_source.dart';
6 | export 'src/domain/entity/category.dart';
7 | export 'src/domain/entity/link.dart';
8 | export 'src/domain/entity/room.dart';
9 | export 'src/domain/entity/session.dart';
10 | export 'src/domain/entity/speaker.dart';
11 | export 'src/domain/use_case/get_conference_data_use_case.dart';
12 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/conference_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:api_client/api_client.dart';
2 | import 'package:conference_data/src/conference_secrets.dart';
3 | import 'package:conference_data/src/data/data_source/conference_data_local_data_source.dart';
4 | import 'package:conference_data/src/data/data_source/conference_data_local_data_source_impl.dart';
5 | import 'package:conference_data/src/data/data_source/conference_data_remote_data_source.dart';
6 | import 'package:conference_data/src/data/data_source/conference_data_remote_data_source_impl.dart';
7 | import 'package:conference_data/src/di/injector.dart';
8 | import 'package:conference_data/src/domain/repository/conference_data_repository.dart';
9 | import 'package:conference_data/src/domain/repository/conference_data_repository_impl.dart';
10 | import 'package:shared_preferences/shared_preferences.dart';
11 |
12 | class ConferenceData {
13 | static Future init() async {
14 | await _initDependencies();
15 | }
16 |
17 | static Future _initDependencies() async {
18 | final apiClient = ApiClient(baseUrl: conferenceBaseUrl);
19 |
20 | final sharedPreferences = await SharedPreferences.getInstance();
21 |
22 | injector
23 | ..registerLazySingleton(
24 | () => ConferenceDataRemoteDataSourceImpl(apiClient: apiClient),
25 | )
26 | ..registerLazySingleton(
27 | () => ConferenceDataLocalDataSourceImpl(sharedPreferences),
28 | )
29 | ..registerLazySingleton(
30 | () => ConferenceDataRepositoryImpl(
31 | conferenceDataRemoteDataSource: injector(),
32 | conferenceDataLocalDataSource: injector(),
33 | ),
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/data_source/conference_data_local_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/agenda_model.dart';
2 | import 'package:conference_data/src/data/model/conference_data_model.dart';
3 |
4 | abstract class ConferenceDataLocalDataSource {
5 | Future saveConferenceData(ConferenceDataModel conferenceDataModel);
6 |
7 | Future getConferenceData();
8 |
9 | Future saveAgenda(AgendaModel agendaModel);
10 |
11 | Future getAgenda();
12 | }
13 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/data_source/conference_data_local_data_source_impl.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:conference_data/src/data/data_source/conference_data_local_data_source.dart';
4 | import 'package:conference_data/src/data/model/agenda_model.dart';
5 | import 'package:conference_data/src/data/model/conference_data_model.dart';
6 | import 'package:shared_preferences/shared_preferences.dart';
7 |
8 | const _conferenceDataKey = 'conferenceData-2024-usa';
9 | const _agendaKey = 'agenda-2024-usa';
10 |
11 | class ConferenceDataLocalDataSourceImpl implements ConferenceDataLocalDataSource {
12 | ConferenceDataLocalDataSourceImpl(this.sharedPreferences);
13 |
14 | final SharedPreferences sharedPreferences;
15 |
16 | @override
17 | Future saveConferenceData(ConferenceDataModel conferenceDataModel) async {
18 | try {
19 | await sharedPreferences.setString(_conferenceDataKey, jsonEncode(conferenceDataModel.toJson()));
20 | // Delete legacy data from older conferences (applicable in case the user didn't uninstall the app between conferences)
21 | await sharedPreferences.remove('conferenceData');
22 | } catch (e) {
23 | throw Exception(e);
24 | }
25 | }
26 |
27 | @override
28 | Future getConferenceData() async {
29 | try {
30 | final conferenceDataJson = sharedPreferences.getString(_conferenceDataKey);
31 |
32 | if (conferenceDataJson == null) {
33 | return ConferenceDataModel();
34 | }
35 |
36 | return ConferenceDataModel.fromJson(jsonDecode(conferenceDataJson) as Map);
37 | } catch (e) {
38 | throw Exception(e);
39 | }
40 | }
41 |
42 | @override
43 | Future saveAgenda(AgendaModel agendaModel) async {
44 | try {
45 | await sharedPreferences.setString(_agendaKey, jsonEncode(agendaModel.toJson()));
46 | // Delete legacy data from older conferences (applicable in case the user didn't uninstall the app between conferences)
47 | await sharedPreferences.remove('agenda');
48 | } catch (e) {
49 | throw Exception(e);
50 | }
51 | }
52 |
53 | @override
54 | Future getAgenda() async {
55 | try {
56 | final agendaJson = sharedPreferences.getString(_agendaKey);
57 |
58 | if (agendaJson == null) {
59 | return AgendaModel();
60 | }
61 |
62 | return AgendaModel.fromLocalJson(jsonDecode(agendaJson) as Map);
63 | } catch (e) {
64 | throw Exception(e);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/data_source/conference_data_remote_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/agenda_model.dart';
2 | import 'package:conference_data/src/data/model/conference_data_model.dart';
3 |
4 | abstract class ConferenceDataRemoteDataSource {
5 | Future getConferenceData();
6 |
7 | Future getAgenda();
8 | }
9 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/data_source/conference_data_remote_data_source_impl.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:api_client/api_client.dart';
4 | import 'package:conference_data/src/data/data_source/conference_data_remote_data_source.dart';
5 | import 'package:conference_data/src/data/model/agenda_model.dart';
6 | import 'package:conference_data/src/data/model/conference_data_model.dart';
7 |
8 | class ConferenceDataRemoteDataSourceImpl implements ConferenceDataRemoteDataSource {
9 | const ConferenceDataRemoteDataSourceImpl({required this.apiClient});
10 |
11 | final ApiClient apiClient;
12 |
13 | @override
14 | Future getConferenceData() async {
15 | try {
16 | final response = await apiClient.get('view/All');
17 |
18 | if (response.statusCode != HttpStatus.ok) {
19 | throw ApiClientError(
20 | 'GET view/All returned status ${response.statusCode} with the following response: "${response.data}',
21 | StackTrace.current,
22 | );
23 | }
24 |
25 | return ConferenceDataModel.fromJson(response.data as Map);
26 | } on ApiClientError {
27 | rethrow;
28 | } catch (e) {
29 | throw Exception(e);
30 | }
31 | }
32 |
33 | @override
34 | Future getAgenda() async {
35 | try {
36 | final response = await apiClient.get('view/GridSmart');
37 |
38 | if (response.statusCode != HttpStatus.ok) {
39 | throw ApiClientError(
40 | 'GET view/GridSmart returned status ${response.statusCode} with the following response: "${response.data}',
41 | StackTrace.current,
42 | );
43 | }
44 |
45 | return AgendaModel.fromJson(response.data as List);
46 | } on ApiClientError {
47 | rethrow;
48 | } catch (e) {
49 | throw Exception(e);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/model/agenda_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/session_model.dart';
2 |
3 | class AgendaModel {
4 | AgendaModel({
5 | this.sessions = const [],
6 | });
7 |
8 | factory AgendaModel.fromJson(List jsonList) {
9 | final sessionModelsList = jsonList
10 | .map>((agendaDay) => (agendaDay as Map)['rooms'] as List)
11 | .expand((List roomJson) => roomJson)
12 | .expand((roomJson) => (roomJson as Map)['sessions'] as List)
13 | .map(
14 | (sessionJson) {
15 | final speakerIdsJsonList = ((sessionJson as Map)['speakers'] as List)
16 | .map((speakerJson) => (speakerJson as Map)['id'])
17 | .toList();
18 |
19 | final categoryItemsJsonList = (sessionJson['categories'] as List)
20 | .map((categoryJson) => categoryJson as Map)
21 | .expand((categoryJson) => categoryJson['categoryItems'] as List)
22 | .map((categoryItemJson) => (categoryItemJson as Map)['id'])
23 | .toList();
24 |
25 | sessionJson
26 | ..remove('speakers')
27 | ..putIfAbsent('speakers', () => speakerIdsJsonList)
28 | ..putIfAbsent('categoryItems', () => categoryItemsJsonList)
29 | ..remove('categories');
30 |
31 | return SessionModel.fromJson(sessionJson);
32 | },
33 | ).toList();
34 |
35 | return AgendaModel(
36 | sessions: sessionModelsList,
37 | );
38 | }
39 |
40 | factory AgendaModel.fromLocalJson(Map json) {
41 | return AgendaModel(
42 | sessions: json['sessions'] != null
43 | ? (json['sessions'] as List)
44 | .map((sessionJson) => SessionModel.fromJson(sessionJson as Map))
45 | .toList()
46 | : [],
47 | );
48 | }
49 |
50 | Map toJson() {
51 | return {
52 | 'sessions': sessions.map((session) => session.toJson()).toList(),
53 | };
54 | }
55 |
56 | final List sessions;
57 |
58 | @override
59 | String toString() {
60 | return 'AgendaModel(sessions: $sessions)';
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/model/category_model.dart:
--------------------------------------------------------------------------------
1 | class CategoryModel {
2 | CategoryModel.fromJson(Map json)
3 | : _id = json['id'] as int,
4 | _name = json['name'] as String;
5 |
6 | final int _id;
7 | final String _name;
8 |
9 | String get id => _id.toString();
10 |
11 | String get name => _name;
12 |
13 | @override
14 | String toString() => 'CategoryModel(id: $id, name: $name)';
15 | }
16 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/model/category_parent_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/category_model.dart';
2 |
3 | class CategoryParentModel {
4 | CategoryParentModel.fromJson(Map json)
5 | : _id = json['id'] as int,
6 | _title = json['title'] as String,
7 | _categories = json['items'] as List;
8 |
9 | Map toJson() {
10 | return {
11 | 'id': _id,
12 | 'title': _title,
13 | 'items': _categories,
14 | };
15 | }
16 |
17 | final int _id;
18 | final String _title;
19 | final List _categories;
20 |
21 | String get id => _id.toString();
22 |
23 | String get title => _title;
24 |
25 | List get categories => _categories
26 | .map((dynamic category) => CategoryModel.fromJson(category as Map))
27 | .toList();
28 |
29 | @override
30 | String toString() => 'CategoryParentModel(id: $id, title: $title, categories: $categories)';
31 | }
32 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/model/conference_data_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/category_parent_model.dart';
2 | import 'package:conference_data/src/data/model/room_model.dart';
3 | import 'package:conference_data/src/data/model/session_model.dart';
4 | import 'package:conference_data/src/data/model/speaker_model.dart';
5 |
6 | class ConferenceDataModel {
7 | ConferenceDataModel({
8 | this.sessions = const [],
9 | this.speakers = const [],
10 | this.categoryParents = const [],
11 | this.rooms = const [],
12 | });
13 |
14 | factory ConferenceDataModel.fromJson(Map json) {
15 | return ConferenceDataModel(
16 | sessions: json['sessions'] != null
17 | ? (json['sessions'] as List)
18 | .map((sessionJson) => SessionModel.fromJson(sessionJson as Map))
19 | .toList()
20 | : [],
21 | speakers: json['speakers'] != null
22 | ? (json['speakers'] as List)
23 | .map((speakerJson) => SpeakerModel.fromJson(speakerJson as Map))
24 | .toList()
25 | : [],
26 | categoryParents: json['categories'] != null
27 | ? (json['categories'] as List)
28 | .map(
29 | (categoryParentJson) => CategoryParentModel.fromJson(categoryParentJson as Map),
30 | )
31 | .toList()
32 | : [],
33 | rooms: json['rooms'] != null
34 | ? (json['rooms'] as List)
35 | .map((roomJson) => RoomModel.fromJson(roomJson as Map))
36 | .toList()
37 | : [],
38 | );
39 | }
40 |
41 | Map toJson() {
42 | return {
43 | 'sessions': sessions.map((session) => session.toJson()).toList(),
44 | 'speakers': speakers.map((speaker) => speaker.toJson()).toList(),
45 | 'categories': categoryParents.map((categoryParent) => categoryParent.toJson()).toList(),
46 | 'rooms': rooms.map((room) => room.toJson()).toList(),
47 | };
48 | }
49 |
50 | final List sessions;
51 | final List speakers;
52 | final List categoryParents;
53 | final List rooms;
54 |
55 | @override
56 | String toString() {
57 | return 'ConferenceDataModel(sessions: $sessions, speakers: $speakers, categoryParents: $categoryParents)';
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/model/link_model.dart:
--------------------------------------------------------------------------------
1 | class LinkModel {
2 | LinkModel.fromJson(Map json)
3 | : _title = json['title'] as String,
4 | _url = json['url'] as String,
5 | _type = json['linkType'] as String;
6 |
7 | final String _title;
8 | final String _url;
9 | final String _type;
10 |
11 | String get title => _title;
12 |
13 | String get url => _url;
14 |
15 | String get type => _type;
16 |
17 | @override
18 | String toString() => 'LinkModel{_title: $_title, _url: $_url, _type: $_type}';
19 | }
20 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/model/room_model.dart:
--------------------------------------------------------------------------------
1 | class RoomModel {
2 | RoomModel.fromJson(Map json)
3 | : _id = json['id'] as int,
4 | _name = json['name'] as String;
5 |
6 | Map toJson() {
7 | return {
8 | 'id': _id,
9 | 'name': _name,
10 | };
11 | }
12 |
13 | final int _id;
14 | final String _name;
15 |
16 | String get id => _id.toString();
17 |
18 | String get name => _name;
19 |
20 | @override
21 | String toString() => 'RoomModel(id: $id, name: $name)';
22 | }
23 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/model/session_model.dart:
--------------------------------------------------------------------------------
1 | class SessionModel {
2 | SessionModel({
3 | required String id,
4 | required String title,
5 | required String startsAt,
6 | required String endsAt,
7 | String? description,
8 | bool? isServiceSession,
9 | // 'Plenum' definition: an assembly of all the members of a group or committee.
10 | // If true, this usually implies a session that requires all attendees to be present which automatically implies that
11 | // no other session is happening at the same time. For example, the 'Keynote' session or the 'Lunch' session or
12 | // the 'Registration & Check-in' session.
13 | bool? isPlenumSession,
14 | List? speakerIds,
15 | List? categoryIds,
16 | int? roomId,
17 | String? room,
18 | }) : _id = id,
19 | _title = title,
20 | _description = description,
21 | _startsAt = startsAt,
22 | _endsAt = endsAt,
23 | _isServiceSession = isServiceSession,
24 | _isPlenumSession = isPlenumSession,
25 | _speakerIds = speakerIds,
26 | _categoryIds = categoryIds,
27 | _roomId = roomId,
28 | _room = room;
29 |
30 | factory SessionModel.fromJson(Map json) {
31 | return SessionModel(
32 | id: json['id'] as String,
33 | title: json['title'] as String,
34 | startsAt: json['startsAt'] as String,
35 | endsAt: json['endsAt'] as String,
36 | description: json['description'] as String?,
37 | isServiceSession: json['isServiceSession'] as bool?,
38 | isPlenumSession: json['isPlenumSession'] as bool?,
39 | speakerIds: json['speakers'] as List?,
40 | categoryIds: json['categoryItems'] as List?,
41 | roomId: json['roomId'] as int?,
42 | room: json['room'] as String?,
43 | );
44 | }
45 |
46 | Map toJson() {
47 | return {
48 | 'id': _id,
49 | 'title': _title,
50 | 'startsAt': _startsAt,
51 | 'endsAt': _endsAt,
52 | 'description': _description,
53 | 'isServiceSession': _isServiceSession,
54 | 'isPlenumSession': _isPlenumSession,
55 | 'speakers': _speakerIds,
56 | 'categoryItems': _categoryIds,
57 | 'roomId': _roomId,
58 | 'room': _room,
59 | };
60 | }
61 |
62 | String _id;
63 | String _title;
64 | String _startsAt;
65 | String _endsAt;
66 | String? _description;
67 | bool? _isServiceSession;
68 | bool? _isPlenumSession;
69 | List? _speakerIds;
70 | List? _categoryIds;
71 | int? _roomId;
72 | String? _room;
73 |
74 | @override
75 | String toString() {
76 | return 'SessionModel(id: $_id, title: $_title, startsAt: $_startsAt, endsAt: $_endsAt, description: $_description, isServiceSession: $_isServiceSession, isPlenumSession: $_isPlenumSession, speakerIds: $_speakerIds, categoryIds: $_categoryIds, roomId: $_roomId, room: $_room)';
77 | }
78 |
79 | String get id => _id;
80 |
81 | String get title => _title;
82 |
83 | DateTime get startsAt => DateTime.parse(_startsAt);
84 |
85 | DateTime get endsAt => DateTime.parse(_endsAt);
86 |
87 | String get description => _description ?? '';
88 |
89 | bool get isServiceSession => _isServiceSession ?? false;
90 |
91 | bool get isPlenumSession => _isPlenumSession ?? false;
92 |
93 | List get speakerIds => (_speakerIds ?? const []).map((speakerId) => speakerId.toString()).toList();
94 |
95 | List get categoryIds => (_categoryIds ?? const []).map((categoryId) => categoryId?.toString() ?? '').toList();
96 |
97 | String get roomId => (_roomId ?? -1).toString();
98 |
99 | String get room => _room ?? '';
100 | }
101 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/data/model/speaker_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/link_model.dart';
2 |
3 | class SpeakerModel {
4 | SpeakerModel({
5 | required String id,
6 | required String fullName,
7 | String? bio,
8 | String? tagLine,
9 | String? profilePictureUrl,
10 | List? sessionIds,
11 | List? links,
12 | }) : _id = id,
13 | _fullName = fullName,
14 | _bio = bio,
15 | _tagLine = tagLine,
16 | _profilePictureUrl = profilePictureUrl,
17 | _sessionIds = sessionIds,
18 | _links = links;
19 |
20 | factory SpeakerModel.fromJson(Map json) {
21 | return SpeakerModel(
22 | id: json['id'] as String,
23 | fullName: json['fullName'] as String,
24 | bio: json['bio'] as String?,
25 | tagLine: json['tagLine'] as String?,
26 | profilePictureUrl: json['profilePicture'] as String?,
27 | sessionIds: json['sessions'] as List?,
28 | links: json['links'] as List?,
29 | );
30 | }
31 |
32 | Map toJson() {
33 | return {
34 | 'id': _id,
35 | 'fullName': _fullName,
36 | 'bio': _bio,
37 | 'tagLine': _tagLine,
38 | 'profilePicture': _profilePictureUrl,
39 | 'sessions': _sessionIds,
40 | 'links': _links,
41 | };
42 | }
43 |
44 | String _id;
45 | String _fullName;
46 | String? _bio;
47 | String? _tagLine;
48 | String? _profilePictureUrl;
49 | List? _sessionIds;
50 | List? _links;
51 |
52 | @override
53 | String toString() {
54 | return 'SpeakerModel{_id: $_id, _fullName: $_fullName, _bio: $_bio, _tagLine: $_tagLine, _profilePictureUrl: $_profilePictureUrl, _sessionIds: $_sessionIds, _links: $_links}';
55 | }
56 |
57 | String get id => _id;
58 |
59 | String get fullName => _fullName;
60 |
61 | String get bio => _bio ?? '';
62 |
63 | String get tagLine => _tagLine ?? '';
64 |
65 | String get profilePictureUrl => _profilePictureUrl ?? '';
66 |
67 | List get sessionIds => _sessionIds?.map((dynamic id) => id.toString()).toList() ?? [];
68 |
69 | List get links {
70 | return _links?.map((dynamic link) => LinkModel.fromJson(link as Map)).toList() ?? [];
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/di/injector.dart:
--------------------------------------------------------------------------------
1 | import 'package:get_it/get_it.dart';
2 |
3 | final injector = GetIt.asNewInstance();
4 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/conference_data_source.dart:
--------------------------------------------------------------------------------
1 | enum ConferenceDataSource {
2 | cached,
3 | latest,
4 | }
5 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/entity/agenda.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/agenda_model.dart';
2 | import 'package:conference_data/src/domain/entity/session.dart';
3 |
4 | class Agenda {
5 | Agenda({
6 | this.sessions = const [],
7 | });
8 |
9 | factory Agenda.fromAgendaModel(AgendaModel agendaModel) {
10 | return Agenda(
11 | sessions: agendaModel.sessions.map(Session.fromSessionModel).toList(),
12 | );
13 | }
14 |
15 | final List sessions;
16 |
17 | @override
18 | String toString() {
19 | return 'Agenda(sessions: $sessions)';
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/entity/category.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/category_model.dart';
2 |
3 | class Category {
4 | const Category({
5 | required this.id,
6 | required this.name,
7 | required this.typeId,
8 | required this.type,
9 | });
10 |
11 | Category.fromCategoryModel(CategoryModel categoryModel)
12 | : id = categoryModel.id,
13 | name = categoryModel.name,
14 | typeId = '',
15 | type = '';
16 |
17 | final String id;
18 | final String name;
19 | final String typeId;
20 | final String type;
21 |
22 | Category copyWith({
23 | String? typeId,
24 | String? type,
25 | }) {
26 | return Category(
27 | id: id,
28 | name: name,
29 | typeId: typeId ?? this.typeId,
30 | type: type ?? this.type,
31 | );
32 | }
33 |
34 | @override
35 | String toString() => 'Category(id: $id, name: $name, typeId: $typeId, type: $type)';
36 | }
37 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/entity/conference_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/conference_data_model.dart';
2 | import 'package:conference_data/src/domain/entity/category.dart';
3 | import 'package:conference_data/src/domain/entity/room.dart';
4 | import 'package:conference_data/src/domain/entity/session.dart';
5 | import 'package:conference_data/src/domain/entity/speaker.dart';
6 |
7 | class ConferenceData {
8 | ConferenceData({
9 | this.sessions = const [],
10 | this.speakers = const [],
11 | this.categories = const [],
12 | this.rooms = const [],
13 | });
14 |
15 | factory ConferenceData.fromConferenceDataModel(ConferenceDataModel conferenceDataModel) {
16 | final categories = [];
17 |
18 | for (final categoryParent in conferenceDataModel.categoryParents) {
19 | for (final category in categoryParent.categories) {
20 | categories.add(
21 | Category.fromCategoryModel(category).copyWith(
22 | typeId: categoryParent.id,
23 | type: categoryParent.title,
24 | ),
25 | );
26 | }
27 | }
28 |
29 | return ConferenceData(
30 | sessions: conferenceDataModel.sessions.map(Session.fromSessionModel).toList(),
31 | speakers: conferenceDataModel.speakers.map(Speaker.fromSpeakerModel).toList(),
32 | categories: categories,
33 | rooms: conferenceDataModel.rooms.map(Room.fromRoomModel).toList(),
34 | );
35 | }
36 |
37 | final List sessions;
38 | final List speakers;
39 | final List categories;
40 | final List rooms;
41 |
42 | ConferenceData copyWith({List? sessions}) {
43 | return ConferenceData(
44 | sessions: sessions ?? this.sessions,
45 | speakers: speakers,
46 | categories: categories,
47 | rooms: rooms,
48 | );
49 | }
50 |
51 | @override
52 | String toString() {
53 | return 'ConferenceData(sessions: $sessions, speakers: $speakers, categories: $categories, rooms: $rooms)';
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/entity/link.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/link_model.dart';
2 |
3 | class Link {
4 | Link({
5 | required this.url,
6 | required this.title,
7 | required this.type,
8 | });
9 |
10 | factory Link.fromLinkModel(LinkModel linkModel) {
11 | return Link(
12 | title: linkModel.title,
13 | url: linkModel.url,
14 | type: linkModel.type,
15 | );
16 | }
17 |
18 | final String url;
19 | final String title;
20 | final String type;
21 |
22 | @override
23 | String toString() {
24 | return 'Link{url: $url, title: $title, type: $type}';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/entity/room.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/room_model.dart';
2 |
3 | class Room {
4 | const Room({
5 | required this.id,
6 | required this.name,
7 | });
8 |
9 | Room.fromRoomModel(RoomModel roomModel)
10 | : id = roomModel.id,
11 | name = roomModel.name;
12 |
13 | final String id;
14 | final String name;
15 |
16 | @override
17 | String toString() => 'Room(id: $id, name: $name)';
18 | }
19 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/entity/session.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/data/model/session_model.dart';
2 |
3 | class Session {
4 | Session({
5 | required this.id,
6 | required this.title,
7 | required this.startsAt,
8 | required this.endsAt,
9 | required this.description,
10 | required this.isServiceSession,
11 | required this.isPlenumSession,
12 | required this.roomId,
13 | required this.room,
14 | required this.speakerIds,
15 | required this.categoryIds,
16 | });
17 |
18 | factory Session.fromSessionModel(SessionModel sessionModel) {
19 | return Session(
20 | id: sessionModel.id,
21 | title: sessionModel.title,
22 | startsAt: sessionModel.startsAt,
23 | endsAt: sessionModel.endsAt,
24 | description: sessionModel.description,
25 | isServiceSession: sessionModel.isServiceSession,
26 | isPlenumSession: sessionModel.isPlenumSession,
27 | roomId: sessionModel.roomId,
28 | room: sessionModel.room,
29 | speakerIds: sessionModel.speakerIds,
30 | categoryIds: sessionModel.categoryIds,
31 | );
32 | }
33 |
34 | final String id;
35 | final String title;
36 | final DateTime startsAt;
37 | final DateTime endsAt;
38 | final String description;
39 | final bool isServiceSession;
40 | final bool isPlenumSession;
41 | final String roomId;
42 | final String room;
43 | final List speakerIds;
44 | final List categoryIds;
45 |
46 | int get duration => endsAt.difference(startsAt).inMinutes;
47 |
48 | bool get isNotATalk => isServiceSession;
49 |
50 | @override
51 | String toString() {
52 | return 'Session(id: $id, title: $title, startsAt: $startsAt, endsAt: $endsAt, description: $description, isServiceSession: $isServiceSession, isPlenumSession: $isPlenumSession, roomId: $roomId, room: $room, speakerIds: $speakerIds, categoryIds: $categoryIds)';
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/entity/speaker.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/conference_data.dart';
2 | import 'package:conference_data/src/data/model/speaker_model.dart';
3 |
4 | class Speaker {
5 | Speaker({
6 | required this.id,
7 | required this.fullName,
8 | required this.bio,
9 | required this.tagLine,
10 | required this.profilePictureUrl,
11 | required this.sessionIds,
12 | this.links = const [],
13 | });
14 |
15 | factory Speaker.fromSpeakerModel(SpeakerModel speakerModel) {
16 | return Speaker(
17 | id: speakerModel.id,
18 | fullName: speakerModel.fullName,
19 | bio: speakerModel.bio,
20 | tagLine: speakerModel.tagLine,
21 | profilePictureUrl: speakerModel.profilePictureUrl,
22 | links: speakerModel.links.map(Link.fromLinkModel).toList(),
23 | sessionIds: speakerModel.sessionIds,
24 | );
25 | }
26 |
27 | final String id;
28 | final String fullName;
29 | final String bio;
30 | final String tagLine;
31 | final String profilePictureUrl;
32 | final List sessionIds;
33 | final List links;
34 |
35 | @override
36 | String toString() {
37 | return 'Speaker{id: $id, fullName: $fullName, bio: $bio, tagLine: $tagLine, profilePictureUrl: $profilePictureUrl, sessionIds: $sessionIds, links: $links}';
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/repository/conference_data_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/domain/conference_data_source.dart';
2 | import 'package:conference_data/src/domain/entity/agenda.dart';
3 | import 'package:conference_data/src/domain/entity/conference_data.dart';
4 | import 'package:util/util.dart';
5 |
6 | abstract class ConferenceDataRepository {
7 | Future> getConferenceData({ConferenceDataSource? source});
8 |
9 | Future> getAgenda({ConferenceDataSource? source});
10 | }
11 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/repository/conference_data_repository_impl.dart:
--------------------------------------------------------------------------------
1 | import 'package:api_client/api_client.dart';
2 | import 'package:conference_data/src/data/data_source/conference_data_local_data_source.dart';
3 | import 'package:conference_data/src/data/data_source/conference_data_remote_data_source.dart';
4 | import 'package:conference_data/src/domain/conference_data_source.dart';
5 | import 'package:conference_data/src/domain/entity/agenda.dart';
6 | import 'package:conference_data/src/domain/entity/conference_data.dart';
7 | import 'package:conference_data/src/domain/repository/conference_data_repository.dart';
8 | import 'package:util/util.dart';
9 |
10 | class ConferenceDataRepositoryImpl implements ConferenceDataRepository {
11 | ConferenceDataRepositoryImpl({
12 | required this.conferenceDataRemoteDataSource,
13 | required this.conferenceDataLocalDataSource,
14 | });
15 |
16 | final ConferenceDataRemoteDataSource conferenceDataRemoteDataSource;
17 | final ConferenceDataLocalDataSource conferenceDataLocalDataSource;
18 |
19 | @override
20 | Future> getConferenceData({
21 | ConferenceDataSource? source = ConferenceDataSource.cached,
22 | }) async {
23 | try {
24 | final sourceOrFallbackSource = source ?? ConferenceDataSource.cached;
25 |
26 | try {
27 | if (sourceOrFallbackSource == ConferenceDataSource.cached) {
28 | final cachedConferenceDataModel = await conferenceDataLocalDataSource.getConferenceData();
29 |
30 | if (cachedConferenceDataModel.sessions.isNotEmpty) {
31 | return Success(ConferenceData.fromConferenceDataModel(cachedConferenceDataModel));
32 | } else {
33 | return Success(await _getLatestConferenceDataAndCacheIt());
34 | }
35 | } else {
36 | return Success(await _getLatestConferenceDataAndCacheIt());
37 | }
38 | } on ApiClientError {
39 | rethrow;
40 | } catch (e) {
41 | return Success(await _getLatestConferenceDataAndCacheIt());
42 | }
43 | } on ApiClientError {
44 | return const Error(ServerFailure());
45 | } catch (e) {
46 | return const Error(LocalFailure());
47 | }
48 | }
49 |
50 | Future _getLatestConferenceDataAndCacheIt() async {
51 | final conferenceDataModel = await conferenceDataRemoteDataSource.getConferenceData();
52 |
53 | try {
54 | await conferenceDataLocalDataSource.saveConferenceData(conferenceDataModel);
55 | } catch (_) {
56 | return ConferenceData.fromConferenceDataModel(conferenceDataModel);
57 | }
58 |
59 | return ConferenceData.fromConferenceDataModel(conferenceDataModel);
60 | }
61 |
62 | @override
63 | Future> getAgenda({
64 | ConferenceDataSource? source = ConferenceDataSource.cached,
65 | }) async {
66 | try {
67 | final sourceOrFallbackSource = source ?? ConferenceDataSource.cached;
68 |
69 | try {
70 | if (sourceOrFallbackSource == ConferenceDataSource.cached) {
71 | final cachedAgendaModel = await conferenceDataLocalDataSource.getAgenda();
72 |
73 | if (cachedAgendaModel.sessions.isNotEmpty) {
74 | return Success(Agenda.fromAgendaModel(cachedAgendaModel));
75 | } else {
76 | return Success(await _getLatestAgendaAndCacheIt());
77 | }
78 | } else {
79 | return Success(await _getLatestAgendaAndCacheIt());
80 | }
81 | } on ApiClientError {
82 | rethrow;
83 | } catch (e) {
84 | return Success(await _getLatestAgendaAndCacheIt());
85 | }
86 | } on ApiClientError {
87 | return const Error(ServerFailure());
88 | } catch (e) {
89 | return const Error(LocalFailure());
90 | }
91 | }
92 |
93 | Future _getLatestAgendaAndCacheIt() async {
94 | final agendaModel = await conferenceDataRemoteDataSource.getAgenda();
95 |
96 | try {
97 | await conferenceDataLocalDataSource.saveAgenda(agendaModel);
98 | } catch (_) {
99 | return Agenda.fromAgendaModel(agendaModel);
100 | }
101 |
102 | return Agenda.fromAgendaModel(agendaModel);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/packages/conference_data/lib/src/domain/use_case/get_conference_data_use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:conference_data/src/di/injector.dart';
2 | import 'package:conference_data/src/domain/conference_data_source.dart';
3 | import 'package:conference_data/src/domain/entity/agenda.dart';
4 | import 'package:conference_data/src/domain/entity/conference_data.dart';
5 | import 'package:conference_data/src/domain/entity/session.dart';
6 | import 'package:conference_data/src/domain/repository/conference_data_repository.dart';
7 | import 'package:util/util.dart';
8 |
9 | class GetConferenceDataUseCase extends UseCase {
10 | GetConferenceDataUseCase();
11 |
12 | @override
13 | // ignore: avoid_renaming_method_parameters
14 | Future> call([bool? forceLatest]) async {
15 | final conferenceDataRepository = injector.get();
16 |
17 | final dataSource = (forceLatest ?? false) ? ConferenceDataSource.latest : null;
18 |
19 | final conferenceDataAndAgendaResults = await Future.wait(
20 | [
21 | conferenceDataRepository.getConferenceData(source: dataSource),
22 | conferenceDataRepository.getAgenda(source: dataSource),
23 | ],
24 | );
25 |
26 | final conferenceDataResult = conferenceDataAndAgendaResults[0] as Result;
27 | final agendaResult = conferenceDataAndAgendaResults[1] as Result;
28 |
29 | if (conferenceDataResult.isError()) {
30 | return Error(conferenceDataResult.getError()!);
31 | } else {
32 | final conferenceData = conferenceDataResult.getSuccess()!;
33 |
34 | // List from [ConferenceData] is just a list of talks.
35 | // List from [Agenda] is the same list of talks plus breaks, registration, lunch, etc. events.
36 | final agendaSessions = agendaResult.when(
37 | (_) => [],
38 | (agenda) => agenda.sessions,
39 | );
40 |
41 | if (agendaSessions.isEmpty) {
42 | return Success(conferenceData);
43 | }
44 |
45 | return Success(conferenceData.copyWith(sessions: agendaSessions));
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/conference_data/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: conference_data
2 | description: All things related to the conference data
3 | version: 0.1.0+1
4 | publish_to: none
5 |
6 | environment:
7 | sdk: '>=3.0.5 <4.0.0'
8 |
9 | dependencies:
10 | api_client:
11 | path: ../api_client
12 | get_it: ^7.6.0
13 | shared_preferences: ^2.1.2
14 | util:
15 | path: ../util
16 |
17 | dev_dependencies:
18 | mocktail: ^0.3.0
19 | test: ^1.19.2
20 | very_good_analysis: ^5.0.0+1
21 |
--------------------------------------------------------------------------------
/packages/util/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | build/
7 | pubspec.lock
--------------------------------------------------------------------------------
/packages/util/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.5.0.0.yaml
2 |
3 | linter:
4 | rules:
5 | lines_longer_than_80_chars: false
6 | public_member_api_docs: false
7 | one_member_abstracts: false
8 |
--------------------------------------------------------------------------------
/packages/util/lib/src/failure.dart:
--------------------------------------------------------------------------------
1 | abstract class Failure {
2 | const Failure();
3 | }
4 |
5 | class ServerFailure extends Failure {
6 | const ServerFailure();
7 | }
8 |
9 | class LocalFailure extends Failure {
10 | const LocalFailure();
11 | }
12 |
--------------------------------------------------------------------------------
/packages/util/lib/src/result.dart:
--------------------------------------------------------------------------------
1 | import 'package:meta/meta.dart';
2 |
3 | /// Base Result class
4 | ///
5 | /// Receives two values [E] and [S]
6 | /// as [E] is an error and [S] is a success.
7 | ///
8 | /// Source: https://github.com/higorlapa/result/blob/main/lib/multiple_result.dart
9 | @sealed
10 | abstract class Result {
11 | /// Default constructor.
12 | const Result();
13 |
14 | /// Returns the current result.
15 | ///
16 | /// It may be a [Success] or an [Error].
17 | /// Check with
18 | /// ```dart
19 | /// result.isSuccess();
20 | /// ```
21 | /// or
22 | /// ```dart
23 | /// result.isError();
24 | /// ```
25 | ///
26 | /// before casting the value;
27 | dynamic get();
28 |
29 | /// Returns the value of [S].
30 | S? getSuccess();
31 |
32 | /// Returns the value of [E].
33 | E? getError();
34 |
35 | /// Returns true if the current result is an [Error].
36 | bool isError();
37 |
38 | /// Returns true if the current result is a [success].
39 | bool isSuccess();
40 |
41 | /// Return the result in one of these functions.
42 | ///
43 | /// if the result is an error, it will be returned in
44 | /// [whenError],
45 | /// if it is a success it will be returned in [whenSuccess].
46 | W when(
47 | W Function(E error) whenError,
48 | W Function(S success) whenSuccess,
49 | );
50 | }
51 |
52 | /// Success Result.
53 | ///
54 | /// return it when the result of a [Result] is
55 | /// the expected value.
56 | @immutable
57 | class Success implements Result {
58 | /// Receives the [S] param as
59 | /// the successful result.
60 | const Success(
61 | this._success,
62 | );
63 |
64 | final S _success;
65 |
66 | @override
67 | S get() {
68 | return _success;
69 | }
70 |
71 | @override
72 | bool isError() => false;
73 |
74 | @override
75 | bool isSuccess() => true;
76 |
77 | @override
78 | int get hashCode => _success.hashCode;
79 |
80 | @override
81 | bool operator ==(Object other) =>
82 | other is Success && other._success == _success;
83 |
84 | @override
85 | W when(
86 | W Function(E error) whenError,
87 | W Function(S success) whenSuccess,
88 | ) {
89 | return whenSuccess(_success);
90 | }
91 |
92 | @override
93 | E? getError() => null;
94 |
95 | @override
96 | S? getSuccess() => _success;
97 | }
98 |
99 | /// Error Result.
100 | ///
101 | /// return it when the result of a [Result] is
102 | /// not the expected value.
103 | @immutable
104 | class Error implements Result {
105 | /// Receives the [E] param as
106 | /// the error result.
107 | const Error(this._error);
108 |
109 | final E _error;
110 |
111 | @override
112 | E get() {
113 | return _error;
114 | }
115 |
116 | @override
117 | bool isError() => true;
118 |
119 | @override
120 | bool isSuccess() => false;
121 |
122 | @override
123 | int get hashCode => _error.hashCode;
124 |
125 | @override
126 | bool operator ==(Object other) => other is Error && other._error == _error;
127 |
128 | @override
129 | W when(
130 | W Function(E error) whenError,
131 | W Function(S succcess) whenSuccess,
132 | ) {
133 | return whenError(_error);
134 | }
135 |
136 | @override
137 | E? getError() => _error;
138 |
139 | @override
140 | S? getSuccess() => null;
141 | }
142 |
143 | /// Default success class.
144 | ///
145 | /// Instead of returning void, as
146 | /// ```dart
147 | /// Result
148 | /// ```
149 | /// return
150 | /// ```dart
151 | /// Result
152 | /// ```
153 | class SuccessResult {
154 | const SuccessResult._internal();
155 | }
156 |
157 | /// Default success case.
158 | const success = SuccessResult._internal();
159 |
--------------------------------------------------------------------------------
/packages/util/lib/src/use_case.dart:
--------------------------------------------------------------------------------
1 | import 'package:util/src/failure.dart';
2 | import 'package:util/src/result.dart';
3 |
4 | abstract class UseCase {
5 | Future> call([Params params]);
6 | }
7 |
8 | class NoParams {
9 | const NoParams();
10 | }
11 |
--------------------------------------------------------------------------------
/packages/util/lib/util.dart:
--------------------------------------------------------------------------------
1 | /// Contains some re-usable classes such as Result, UseCase, Failure, etc.
2 | library util;
3 |
4 | export 'src/failure.dart';
5 | export 'src/result.dart';
6 | export 'src/use_case.dart';
7 |
--------------------------------------------------------------------------------
/packages/util/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: util
2 | description: Contains some re-usable classes such as Result, UseCase, Failure, etc.
3 | version: 0.1.0+1
4 | publish_to: none
5 |
6 | environment:
7 | sdk: '>=3.0.5 <4.0.0'
8 |
9 | dev_dependencies:
10 | mocktail: ^0.3.0
11 | test: ^1.19.2
12 | very_good_analysis: ^5.0.0+1
13 | dependencies:
14 | meta: ^1.9.1
15 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: fluttercon
2 | description: Fluttercon Berlin 2023
3 | publish_to: 'none'
4 |
5 | version: 1.1.0+10
6 |
7 | environment:
8 | sdk: '>=3.0.5 <4.0.0'
9 | flutter: ^3.10.5
10 |
11 | dependencies:
12 | api_client:
13 | path: packages/api_client
14 | cached_network_image: ^3.2.3
15 | collection: ^1.17.1
16 | conference_data:
17 | path: packages/conference_data
18 | cupertino_icons: ^1.0.2
19 | dotted_border: ^2.0.0+3
20 | flutter:
21 | sdk: flutter
22 | flutter_bloc: ^8.1.3
23 | flutter_native_splash: ^2.3.1
24 | flutter_svg: ^2.0.7
25 | font_awesome_flutter: ^10.4.0
26 | get_it: ^7.6.0
27 | google_fonts: ^4.0.4
28 | intl: ^0.18.1
29 | shared_preferences: ^2.1.2
30 | url_launcher: ^6.1.11
31 | util:
32 | path: packages/util
33 |
34 | dev_dependencies:
35 | flutter_test:
36 | sdk: flutter
37 | very_good_analysis: ^5.0.0+1
38 |
39 | flutter:
40 | uses-material-design: true
41 |
42 | assets:
43 | - assets/images/
--------------------------------------------------------------------------------
/screenshots/sessions-android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/sessions-android.png
--------------------------------------------------------------------------------
/screenshots/sessions-ios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/sessions-ios.png
--------------------------------------------------------------------------------
/screenshots/sessions-macos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/sessions-macos.png
--------------------------------------------------------------------------------
/screenshots/sessions-search-android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/sessions-search-android.png
--------------------------------------------------------------------------------
/screenshots/sessions-search-ios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/sessions-search-ios.png
--------------------------------------------------------------------------------
/screenshots/sessions-search-macos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/sessions-search-macos.png
--------------------------------------------------------------------------------
/screenshots/speakers-android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/speakers-android.png
--------------------------------------------------------------------------------
/screenshots/speakers-ios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/speakers-ios.png
--------------------------------------------------------------------------------
/screenshots/speakers-macos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/speakers-macos.png
--------------------------------------------------------------------------------
/screenshots/speakers-search-android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/speakers-search-android.png
--------------------------------------------------------------------------------
/screenshots/speakers-search-ios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/speakers-search-ios.png
--------------------------------------------------------------------------------
/screenshots/speakers-search-macos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/screenshots/speakers-search-macos.png
--------------------------------------------------------------------------------
/store-badges/appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/store-badges/appstore.png
--------------------------------------------------------------------------------
/store-badges/playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rohan20/fluttercon/d7691898ddd62939293996a972b710c6acdf0111/store-badges/playstore.png
--------------------------------------------------------------------------------