├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature-request.md
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── build.yaml
│ └── verify.yaml
├── .gitignore
├── .metadata
├── .prettierignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── java
│ │ │ └── io
│ │ │ │ └── flutter
│ │ │ │ └── app
│ │ │ │ └── FlutterMultiDexApplication.java
│ │ ├── kotlin
│ │ │ └── is
│ │ │ │ └── giorgio
│ │ │ │ └── app
│ │ │ │ └── parousia
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-hdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ ├── ic_launcher_monochrome.png
│ │ │ └── splash.png
│ │ │ ├── drawable-mdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ ├── ic_launcher_monochrome.png
│ │ │ └── splash.png
│ │ │ ├── drawable-night-hdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-night-mdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-night-v21
│ │ │ ├── background.png
│ │ │ └── launch_background.xml
│ │ │ ├── drawable-night-xhdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-night-xxhdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-night-xxxhdpi
│ │ │ └── splash.png
│ │ │ ├── drawable-night
│ │ │ ├── background.png
│ │ │ └── launch_background.xml
│ │ │ ├── drawable-v21
│ │ │ ├── background.png
│ │ │ └── launch_background.xml
│ │ │ ├── drawable-xhdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ ├── ic_launcher_monochrome.png
│ │ │ └── splash.png
│ │ │ ├── drawable-xxhdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ ├── ic_launcher_monochrome.png
│ │ │ └── splash.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ ├── ic_launcher_monochrome.png
│ │ │ └── splash.png
│ │ │ ├── drawable
│ │ │ ├── background.png
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ └── ic_launcher.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-night-v31
│ │ │ └── styles.xml
│ │ │ ├── values-night
│ │ │ ├── ic_launcher_background.xml
│ │ │ └── styles.xml
│ │ │ ├── values-v31
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── env
│ ├── local.json
│ └── supabase.json
├── google_fonts
│ ├── Cabin-Bold.ttf
│ ├── Cabin-BoldItalic.ttf
│ ├── Cabin-Italic.ttf
│ ├── Cabin-Medium.ttf
│ ├── Cabin-MediumItalic.ttf
│ ├── Cabin-Regular.ttf
│ ├── Cabin-SemiBold.ttf
│ ├── Cabin-SemiBoldItalic.ttf
│ ├── Cabin_Condensed-Bold.ttf
│ ├── Cabin_Condensed-BoldItalic.ttf
│ ├── Cabin_Condensed-Italic.ttf
│ ├── Cabin_Condensed-Medium.ttf
│ ├── Cabin_Condensed-MediumItalic.ttf
│ ├── Cabin_Condensed-Regular.ttf
│ ├── Cabin_Condensed-SemiBold.ttf
│ ├── Cabin_Condensed-SemiBoldItalic.ttf
│ ├── Cabin_SemiCondensed-Bold.ttf
│ ├── Cabin_SemiCondensed-BoldItalic.ttf
│ ├── Cabin_SemiCondensed-Italic.ttf
│ ├── Cabin_SemiCondensed-Medium.ttf
│ ├── Cabin_SemiCondensed-MediumItalic.ttf
│ ├── Cabin_SemiCondensed-Regular.ttf
│ ├── Cabin_SemiCondensed-SemiBold.ttf
│ ├── Cabin_SemiCondensed-SemiBoldItalic.ttf
│ ├── OFL.txt
│ ├── Sniglet-ExtraBold.ttf
│ └── Sniglet-Regular.ttf
├── icon
│ ├── icon-dark.png
│ ├── icon-grayscale.png
│ ├── icon-monochrome.png
│ └── icon.png
└── images
│ ├── add-events.webp
│ ├── add-members.webp
│ ├── events.webp
│ └── home.webp
├── docs
└── README.md
├── flutter_launcher_icons.yaml
├── flutter_native_splash.yaml
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-38x38@2x.png
│ │ │ ├── Icon-App-38x38@3x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-64x64@2x.png
│ │ │ ├── Icon-App-64x64@3x.png
│ │ │ ├── Icon-App-68x68@2x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ ├── Icon-App-Dark-1024x1024@1x.png
│ │ │ ├── Icon-App-Dark-20x20@2x.png
│ │ │ ├── Icon-App-Dark-20x20@3x.png
│ │ │ ├── Icon-App-Dark-29x29@2x.png
│ │ │ ├── Icon-App-Dark-29x29@3x.png
│ │ │ ├── Icon-App-Dark-38x38@2x.png
│ │ │ ├── Icon-App-Dark-38x38@3x.png
│ │ │ ├── Icon-App-Dark-40x40@2x.png
│ │ │ ├── Icon-App-Dark-40x40@3x.png
│ │ │ ├── Icon-App-Dark-60x60@2x.png
│ │ │ ├── Icon-App-Dark-60x60@3x.png
│ │ │ ├── Icon-App-Dark-64x64@2x.png
│ │ │ ├── Icon-App-Dark-64x64@3x.png
│ │ │ ├── Icon-App-Dark-68x68@2x.png
│ │ │ ├── Icon-App-Dark-76x76@2x.png
│ │ │ ├── Icon-App-Dark-83.5x83.5@2x.png
│ │ │ ├── Icon-App-Tinted-1024x1024@1x.png
│ │ │ ├── Icon-App-Tinted-20x20@2x.png
│ │ │ ├── Icon-App-Tinted-20x20@3x.png
│ │ │ ├── Icon-App-Tinted-29x29@2x.png
│ │ │ ├── Icon-App-Tinted-29x29@3x.png
│ │ │ ├── Icon-App-Tinted-38x38@2x.png
│ │ │ ├── Icon-App-Tinted-38x38@3x.png
│ │ │ ├── Icon-App-Tinted-40x40@2x.png
│ │ │ ├── Icon-App-Tinted-40x40@3x.png
│ │ │ ├── Icon-App-Tinted-60x60@2x.png
│ │ │ ├── Icon-App-Tinted-60x60@3x.png
│ │ │ ├── Icon-App-Tinted-64x64@2x.png
│ │ │ ├── Icon-App-Tinted-64x64@3x.png
│ │ │ ├── Icon-App-Tinted-68x68@2x.png
│ │ │ ├── Icon-App-Tinted-76x76@2x.png
│ │ │ └── Icon-App-Tinted-83.5x83.5@2x.png
│ │ ├── LaunchBackground.imageset
│ │ │ ├── Contents.json
│ │ │ ├── background.png
│ │ │ └── darkbackground.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── LaunchImageDark.png
│ │ │ ├── LaunchImageDark@2x.png
│ │ │ ├── LaunchImageDark@3x.png
│ │ │ └── README.md
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── Runner-Bridging-Header.h
│ └── Runner.entitlements
├── RunnerTests
│ └── RunnerTests.swift
└── ci_scripts
│ ├── ci_post_clone.sh
│ └── ci_post_xcodebuild.sh
├── l10n.yaml
├── lib
├── actions
│ ├── actions.dart
│ ├── app.dart
│ ├── auth.dart
│ ├── date.dart
│ ├── deeplinks.dart
│ ├── default_rules.dart
│ ├── feedback.dart
│ ├── groups.dart
│ ├── invites.dart
│ ├── locale.dart
│ ├── members.dart
│ ├── profiles.dart
│ ├── replies.dart
│ ├── routing.dart
│ └── theme.dart
├── app.dart
├── app.freezed.dart
├── epics
│ ├── auth.dart
│ ├── default_rules.dart
│ ├── epics.dart
│ ├── groups.dart
│ ├── invites.dart
│ ├── members.dart
│ ├── profiles.dart
│ ├── replies.dart
│ ├── routing.dart
│ └── schedules.dart
├── go_router_builder.dart
├── go_router_builder.g.dart
├── l10n
│ ├── app_de.arb
│ ├── app_en.arb
│ ├── app_es.arb
│ ├── app_fr.arb
│ └── app_it.arb
├── main.dart
├── models
│ ├── default_reply.dart
│ ├── default_reply.freezed.dart
│ ├── default_reply.g.dart
│ ├── enum.dart
│ ├── group.dart
│ ├── group.freezed.dart
│ ├── group.g.dart
│ ├── invite.dart
│ ├── invite.freezed.dart
│ ├── invite.g.dart
│ ├── member.dart
│ ├── member.freezed.dart
│ ├── member.g.dart
│ ├── models.dart
│ ├── profile.dart
│ ├── profile.freezed.dart
│ ├── profile.g.dart
│ ├── reply.dart
│ ├── reply.freezed.dart
│ ├── reply.g.dart
│ ├── schedule.dart
│ ├── schedule.freezed.dart
│ ├── schedule.g.dart
│ ├── schedule_summary.dart
│ └── schedule_summary.freezed.dart
├── presentation
│ ├── containers
│ │ ├── containers.dart
│ │ ├── date_dropdown.dart
│ │ ├── date_fab.dart
│ │ ├── date_fab.freezed.dart
│ │ ├── group_details.dart
│ │ ├── group_details.freezed.dart
│ │ ├── group_events.dart
│ │ ├── group_events.freezed.dart
│ │ ├── group_form.dart
│ │ ├── group_form.freezed.dart
│ │ ├── group_manage.dart
│ │ ├── group_manage.freezed.dart
│ │ ├── group_member_details.dart
│ │ ├── group_member_details.freezed.dart
│ │ ├── group_members.dart
│ │ ├── group_members.freezed.dart
│ │ ├── group_schedule_details.dart
│ │ ├── group_schedule_details.freezed.dart
│ │ ├── home.dart
│ │ ├── home.freezed.dart
│ │ ├── locale.dart
│ │ ├── locale.freezed.dart
│ │ ├── profile.dart
│ │ ├── profile.freezed.dart
│ │ ├── schedules_list.dart
│ │ ├── schedules_list.freezed.dart
│ │ ├── theme_switcher.dart
│ │ └── theme_switcher.freezed.dart
│ ├── presentation.dart
│ ├── screens
│ │ ├── auth.dart
│ │ ├── group_create.dart
│ │ ├── group_details.dart
│ │ ├── group_manage.dart
│ │ ├── group_member_details.dart
│ │ ├── group_schedule_details.dart
│ │ ├── home.dart
│ │ ├── locale.dart
│ │ ├── profile.dart
│ │ ├── schedule_create.dart
│ │ ├── screens.dart
│ │ ├── select_contacts.dart
│ │ ├── settings.dart
│ │ └── settings_more.dart
│ └── widgets
│ │ ├── contact_form.dart
│ │ ├── date_dropdown.dart
│ │ ├── date_fab.dart
│ │ ├── default_rule_action_sheet.dart
│ │ ├── delete_profile_tile.dart
│ │ ├── empty_state.dart
│ │ ├── feedback_card.dart
│ │ ├── form_builder_recurrence_rule.dart
│ │ ├── group_events.dart
│ │ ├── group_form.dart
│ │ ├── group_form.stories.dart
│ │ ├── group_join.dart
│ │ ├── group_members.dart
│ │ ├── groups_list.dart
│ │ ├── image_crop.dart
│ │ ├── image_form_field.dart
│ │ ├── invite_modal.dart
│ │ ├── profile_picture.dart
│ │ ├── reply_button.dart
│ │ ├── schedule_form.dart
│ │ ├── schedule_member_tile.dart
│ │ ├── schedule_tile.dart
│ │ ├── schedules_list.dart
│ │ ├── sign_out_tile.dart
│ │ ├── theme_switcher_tile.dart
│ │ └── widgets.dart
├── reducers
│ ├── auth.dart
│ ├── feedback.dart
│ ├── groups.dart
│ ├── locale.dart
│ ├── reducers.dart
│ ├── remote_entities.dart
│ ├── root_reducer.dart
│ ├── selected_date.dart
│ ├── selected_group_id.dart
│ ├── selected_schedule_id.dart
│ └── theme.dart
├── repositories
│ ├── const.dart
│ ├── default_rules.dart
│ ├── groups.dart
│ ├── invites.dart
│ ├── members.dart
│ ├── profiles.dart
│ ├── replies.dart
│ ├── repositories.dart
│ ├── schedules.dart
│ ├── storage.dart
│ └── supabase.dart
├── router.dart
├── selectors
│ ├── auth.dart
│ ├── default_rules.dart
│ ├── feedback.dart
│ ├── groups.dart
│ ├── locale.dart
│ ├── members.dart
│ ├── profiles.dart
│ ├── replies.dart
│ ├── schedules.dart
│ ├── selected_date.dart
│ ├── selectors.dart
│ └── theme.dart
├── state
│ ├── app_state.dart
│ ├── app_state.freezed.dart
│ ├── app_state.g.dart
│ ├── auth_state.dart
│ ├── auth_state.freezed.dart
│ ├── auth_state.g.dart
│ ├── locale_state.dart
│ ├── locale_state.freezed.dart
│ ├── locale_state.g.dart
│ └── state.dart
├── util
│ ├── analytics.dart
│ ├── base32.dart
│ ├── colors.dart
│ ├── config.dart
│ ├── config.freezed.dart
│ ├── config.g.dart
│ ├── datetime.dart
│ ├── fakes.dart
│ ├── names.dart
│ ├── recurrence_rules.dart
│ ├── supabase_config.dart
│ ├── supabase_config.freezed.dart
│ ├── supabase_config.g.dart
│ └── util.dart
├── widgetbook.dart
└── widgetbook.directories.g.dart
├── linux
├── .gitignore
├── CMakeLists.txt
├── flutter
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
├── main.cc
├── my_application.cc
└── my_application.h
├── 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
│ │ └── Runner.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
├── pubspec.lock
├── pubspec.yaml
├── supabase
├── .branches
│ └── _current_branch
├── config.toml
├── config
│ └── supabase.json
├── functions
│ ├── .vscode
│ │ ├── extensions.json
│ │ └── settings.json
│ ├── _shared
│ │ └── cors.ts
│ └── delete_user_account
│ │ └── index.ts
├── migrations
│ ├── 20230706000000_dbdev.sql
│ ├── 20230707220325_schema.sql
│ ├── 20231023170520_functions.sql
│ └── 20231101184949_buckets.sql
├── seed.sql
├── tests
│ ├── groups_test.sql
│ └── rls_test.sql
└── util
│ ├── migrate.py
│ ├── poetry.lock
│ └── pyproject.toml
├── test
├── remote_entities_test.dart
├── remote_entity_reducer_tester.dart
├── repositories_test.dart
├── schedules_test.dart
├── selectors_test.dart
└── util_test.dart
├── web
├── .well-known
│ ├── apple-app-site-association
│ └── assetlinks.json
├── favicon.png
├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
├── index.html
├── manifest.json
└── splash
│ └── img
│ ├── dark-1x.png
│ ├── dark-2x.png
│ ├── dark-3x.png
│ ├── dark-4x.png
│ ├── light-1x.png
│ ├── light-2x.png
│ ├── light-3x.png
│ └── light-4x.png
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.ico
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug report"
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Smartphone (please complete the following information):**
27 | - GRUP version: [e.g. 1.6.1]
28 | - Device: [e.g. iPhone6]
29 | - OS: [e.g. iOS8.1]
30 | - (if web) Browser: [e.g. Safari, Chrome]
31 |
32 | **Additional context**
33 | Add any other context about the problem here.
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F9D1\U0001F4BB Feature request"
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "pub" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 | - What does this merge request implement?
4 | - What are the most important pieces of code to review?
5 | - Other bugs or minor issues you have fixed?
6 | - Fixes # [...]
7 |
8 | ## Impact
9 |
10 | - [ ] App
11 | - [ ] Supabase functions
12 | - [ ] Supabase migration
13 | - [ ] CI / GitHub
14 | - [ ] `pubspec.yaml`
15 |
16 | ## Testing steps
17 |
18 | What steps should the reviewer follow to test this change?
19 |
20 | ## Open points
21 |
22 | What do you intend to address later?
23 | What else is still unclear at the time of writing,
24 | and needs further discussion before implementing?
25 |
--------------------------------------------------------------------------------
/.github/workflows/verify.yaml:
--------------------------------------------------------------------------------
1 | name: Verify
2 |
3 | on:
4 | pull_request:
5 | merge_group:
6 |
7 | # We only care about the latest version: stop old jobs if a new commit is pushed.
8 | # The fallback on github.ref is for when the workflow is for a merge queue.
9 | concurrency:
10 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | verify:
15 | runs-on: ubuntu-latest
16 | if: github.event.pull_request.draft == false && startsWith(github.event.pull_request.title, 'Draft') == false
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 |
21 | - name: Get Flutter
22 | uses: subosito/flutter-action@v2
23 | with:
24 | channel: 'stable'
25 |
26 | - name: Download dependencies
27 | run: flutter pub get
28 |
29 | - name: Setup Supabase
30 | uses: supabase/setup-cli@v1
31 |
32 | - name: Start Supabase local for tests
33 | run: supabase start
34 |
35 | - name: Run tests
36 | run: |
37 | supabase status -o json > supabase/config/localhost.json
38 | flutter test
39 |
40 | - name: Compile web
41 | run: flutter build web
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .build/
9 | .buildlog/
10 | .history
11 | .svn/
12 | .swiftpm/
13 | migrate_working_dir/
14 |
15 | # IntelliJ related
16 | *.iml
17 | *.ipr
18 | *.iws
19 | .idea/
20 |
21 | # The .vscode folder contains launch configuration and tasks you configure in
22 | # VS Code which you may wish to be included in version control, so this line
23 | # is commented out by default.
24 | #.vscode/
25 |
26 | # Flutter/Dart/Pub related
27 | **/doc/api/
28 | **/ios/Flutter/.last_build_id
29 | .dart_tool/
30 | .flutter-plugins
31 | .flutter-plugins-dependencies
32 | .packages
33 | .pub-cache/
34 | .pub/
35 | /build/
36 | untranslated_messages.json
37 |
38 | # Symbolication related
39 | app.*.symbols
40 |
41 | # Obfuscation related
42 | app.*.map.json
43 |
44 | # Android Studio will place build artifacts here
45 | /android/app/debug
46 | /android/app/profile
47 | /android/app/release
48 |
49 | # Supabase
50 | supabase/.temp/
51 | supabase/config/localhost.json
52 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
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: 761747bfc538b5af34aa0d3fac380f1bc331ec49
17 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
18 | - platform: web
19 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
20 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | LICENSE
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | # > If you plan on using Freezed in combination with json_serializable,
13 | # > recent versions of json_serializable and meta may require you
14 | # > to disable the invalid_annotation_target warning.
15 | # https://pub.dev/packages/freezed
16 | analyzer:
17 | errors:
18 | invalid_annotation_target: ignore
19 |
20 | linter:
21 | # The lint rules applied to this project can be customized in the
22 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
23 | # included above or to enable additional rules. A list of all available lints
24 | # and their documentation is published at https://dart.dev/lints.
25 | #
26 | # Instead of disabling a lint rule for the entire project in the
27 | # section below, it can also be suppressed for a single line of code
28 | # or a specific dart file by using the `// ignore: name_of_lint` and
29 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
30 | # producing the lint.
31 | rules:
32 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
33 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
34 |
35 | # Additional information about this file can be found at
36 | # https://dart.dev/guides/language/analysis-options
37 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java:
--------------------------------------------------------------------------------
1 | // Generated file.
2 | //
3 | // If you wish to remove Flutter's multidex support, delete this entire file.
4 | //
5 | // Modifications to this file should be done in a copy under a different name
6 | // as this file may be regenerated.
7 |
8 | package io.flutter.app;
9 |
10 | import android.app.Application;
11 | import android.content.Context;
12 | import androidx.annotation.CallSuper;
13 | import androidx.multidex.MultiDex;
14 |
15 | /**
16 | * Extension of {@link android.app.Application}, adding multidex support.
17 | */
18 | public class FlutterMultiDexApplication extends Application {
19 | @Override
20 | @CallSuper
21 | protected void attachBaseContext(Context base) {
22 | super.attachBaseContext(base);
23 | MultiDex.install(this);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/is/giorgio/app/parousia/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package `is`.giorgio.app.parousia
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-hdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-mdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-night-hdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-night-mdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-v21/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-night-v21/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-night-xhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-night-xxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-night-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-night/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-v21/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/drawable/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 | -
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #191A1D
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ffffff
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = '../build'
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(':app')
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/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-8.10.2-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version '8.7.3' apply false
22 | id "org.jetbrains.kotlin.android" version "2.0.21" apply false
23 | }
24 |
25 | include ":app"
26 |
--------------------------------------------------------------------------------
/assets/env/local.json:
--------------------------------------------------------------------------------
1 | {
2 | "supabaseConfigPath": "supabase/config/localhost.json",
3 | "socialAuthWebClientId": "655087059227-32trs4upje1r6itvs3p1c1fs0m98m8ol.apps.googleusercontent.com",
4 | "socialAuthIosClientId": "655087059227-o10vdnfsvnkl9ct8isg8db3j74s0rfur.apps.googleusercontent.com"
5 | }
6 |
--------------------------------------------------------------------------------
/assets/env/supabase.json:
--------------------------------------------------------------------------------
1 | {
2 | "supabaseConfigPath": "supabase/config/supabase.json",
3 | "socialAuthWebClientId": "655087059227-32trs4upje1r6itvs3p1c1fs0m98m8ol.apps.googleusercontent.com",
4 | "socialAuthIosClientId": "655087059227-o10vdnfsvnkl9ct8isg8db3j74s0rfur.apps.googleusercontent.com"
5 | }
6 |
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin-Bold.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin-BoldItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin-Italic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin-Medium.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin-MediumItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin-Regular.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin-SemiBold.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_Condensed-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_Condensed-Bold.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_Condensed-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_Condensed-BoldItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_Condensed-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_Condensed-Italic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_Condensed-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_Condensed-Medium.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_Condensed-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_Condensed-MediumItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_Condensed-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_Condensed-Regular.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_Condensed-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_Condensed-SemiBold.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_Condensed-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_Condensed-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_SemiCondensed-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_SemiCondensed-Bold.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_SemiCondensed-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_SemiCondensed-BoldItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_SemiCondensed-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_SemiCondensed-Italic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_SemiCondensed-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_SemiCondensed-Medium.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_SemiCondensed-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_SemiCondensed-MediumItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_SemiCondensed-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_SemiCondensed-Regular.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_SemiCondensed-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_SemiCondensed-SemiBold.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Cabin_SemiCondensed-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Cabin_SemiCondensed-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Sniglet-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Sniglet-ExtraBold.ttf
--------------------------------------------------------------------------------
/assets/google_fonts/Sniglet-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/google_fonts/Sniglet-Regular.ttf
--------------------------------------------------------------------------------
/assets/icon/icon-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/icon/icon-dark.png
--------------------------------------------------------------------------------
/assets/icon/icon-grayscale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/icon/icon-grayscale.png
--------------------------------------------------------------------------------
/assets/icon/icon-monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/icon/icon-monochrome.png
--------------------------------------------------------------------------------
/assets/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/icon/icon.png
--------------------------------------------------------------------------------
/assets/images/add-events.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/images/add-events.webp
--------------------------------------------------------------------------------
/assets/images/add-members.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/images/add-members.webp
--------------------------------------------------------------------------------
/assets/images/events.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/images/events.webp
--------------------------------------------------------------------------------
/assets/images/home.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/assets/images/home.webp
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | ## :rocket: Release
4 |
5 | Any changes to `main` will trigger both GitHub Actions and Xcode Cloud workflows.
6 |
7 | These workflows will build and upload the app to the Play Store and App Store respectively.
8 | Upon upload, the will be available for internal testing in both stores.
9 |
10 | Additionally, the GitHub Action will create a new release with the app's version and changelog, marking it as a draft.
11 | It will also deploy the app to Cloudflare Pages.
12 |
13 | Once we decide to release the app, we manually request a review from the Play Store and App Store.
14 | Then, we can publish the release on GitHub.
15 |
16 | Finally, on the next PR we bump the version on `pubspec.yaml` to prepare for the next release.
17 | Only the `build-name` part - before the plus sign(+) - should be updated:
18 | the `build-number` is incremented automatically by the CI (GitHub Actions and Xcode Cloud).
19 |
--------------------------------------------------------------------------------
/flutter_launcher_icons.yaml:
--------------------------------------------------------------------------------
1 | # https://pub.dev/packages/flutter_launcher_icons
2 | # dart run flutter_launcher_icons
3 | flutter_launcher_icons:
4 | image_path: "assets/icon/icon.png"
5 |
6 | # image_path_android: "assets/icon/icon.png"
7 | min_sdk_android: 21 # android min sdk min:16, default 21
8 | adaptive_icon_background: "#ffffff"
9 | adaptive_icon_foreground: "assets/icon/icon.png"
10 | adaptive_icon_monochrome: "assets/icon/icon-monochrome.png"
11 |
12 | ios: true
13 | # image_path_ios: "assets/icon/icon.png"
14 | remove_alpha_ios: true # Remove alpha from iOS icons - not allowed in the App Store
15 | image_path_ios_tinted_grayscale: "assets/icon/icon-grayscale.png"
16 | image_path_ios_dark_transparent: "assets/icon/icon-dark.png"
17 | # image_path_ios_dark_transparent: "assets/icon/icon_dark.png"
18 | # image_path_ios_tinted_grayscale: "assets/icon/icon_tinted.png"
19 | # desaturate_tinted_to_grayscale_ios: true
20 |
21 | web:
22 | generate: true
23 | background_color: "#FFFFFF"
24 | theme_color: "#34558B"
25 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.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, '13.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @main
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/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-38x38@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-64x64@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-68x68@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-68x68@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-38x38@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-64x64@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-68x68@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-68x68@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Dark-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-38x38@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-64x64@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-68x68@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-68x68@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-Tinted-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "background.png",
5 | "idiom" : "universal"
6 | },
7 | {
8 | "appearances" : [
9 | {
10 | "appearance" : "luminosity",
11 | "value" : "dark"
12 | }
13 | ],
14 | "filename" : "darkbackground.png",
15 | "idiom" : "universal"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "LaunchImage.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "LaunchImageDark.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "LaunchImage@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "LaunchImageDark@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "LaunchImage@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "LaunchImageDark@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@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/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.developer.applesignin
8 |
9 | Default
10 |
11 | com.apple.developer.associated-domains
12 |
13 | applinks:grup.rsvp
14 | webcredentials:grup.rsvp
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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/ci_scripts/ci_post_clone.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # CI/CD for Flutter
4 | # From https://docs.flutter.dev/deployment/cd#post-clone-script
5 |
6 | # Fail this script if any subcommand fails.
7 | set -e
8 |
9 | # The default execution directory of this script is the ci_scripts directory.
10 | cd "$CI_PRIMARY_REPOSITORY_PATH" # change working directory to the root of your cloned repo.
11 |
12 | # Install Flutter using git.
13 | git clone https://github.com/flutter/flutter.git --depth 1 -b stable "$HOME/flutter"
14 | export PATH="$PATH:$HOME/flutter/bin"
15 |
16 | # Install Flutter artifacts for iOS (--ios), or macOS (--macos) platforms.
17 | flutter precache --ios
18 |
19 | # Install Flutter dependencies.
20 | flutter pub get
21 |
22 | # Install CocoaPods using Homebrew.
23 | export HOMEBREW_NO_AUTO_UPDATE=1 # disable homebrew's automatic updates.
24 | brew install cocoapods
25 |
26 | # Install CocoaPods dependencies.
27 | cd ios && pod install # run `pod install` in the `ios` directory.
28 |
29 | exit 0
30 |
--------------------------------------------------------------------------------
/ios/ci_scripts/ci_post_xcodebuild.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Fail this script if any subcommand fails.
4 | set -e
5 |
6 | # The default execution directory of this script is the ci_scripts directory.
7 | cd "$CI_PRIMARY_REPOSITORY_PATH" # change working directory to the root of your cloned repo.
8 |
9 | # Make Flutter and Dart available in the PATH (it was already installed in the ci_post_clone.sh script)
10 | export PATH="$PATH:$HOME/flutter/bin"
11 |
12 | # We need a common release name for Sentry.
13 | # This command gets the version from the pubspec.yaml file - without the +1 part.
14 | # SENTRY_DIST below will be the CI_BUILD_NUMBER.
15 | # This grep command is different from the one in GitHub Actions because here we are on macOS.
16 | SENTRY_RELEASE="is.giorgio.app.parousia@$(grep '^version:' pubspec.yaml | awk -F'[ +]' '{print $2}')+${CI_BUILD_NUMBER}"
17 | export SENTRY_RELEASE
18 |
19 | # https://docs.sentry.io/platforms/flutter/configuration/options/#dist
20 | # The dist parameter is used to separate different versions of the app in Sentry.
21 | export SENTRY_DIST="$CI_BUILD_NUMBER"
22 |
23 | # Run Sentry Dart plugin to upload source maps and debug symbols to Sentry.
24 | dart run sentry_dart_plugin
25 |
26 | exit 0
27 |
--------------------------------------------------------------------------------
/l10n.yaml:
--------------------------------------------------------------------------------
1 | arb-dir: lib/l10n
2 | template-arb-file: app_en.arb
3 | output-localization-file: app_localizations.dart
4 | untranslated-messages-file: untranslated_messages.json
5 |
--------------------------------------------------------------------------------
/lib/actions/actions.dart:
--------------------------------------------------------------------------------
1 | export 'app.dart';
2 | export 'auth.dart';
3 | export 'date.dart';
4 | export 'deeplinks.dart';
5 | export 'default_rules.dart';
6 | export 'feedback.dart';
7 | export 'groups.dart';
8 | export 'invites.dart';
9 | export 'locale.dart';
10 | export 'members.dart';
11 | export 'profiles.dart';
12 | export 'replies.dart';
13 | export 'routing.dart';
14 | export 'theme.dart';
15 |
--------------------------------------------------------------------------------
/lib/actions/app.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | /// Dispatched as onInit in the app widget
4 | @immutable
5 | class AppStartedAction {}
6 |
--------------------------------------------------------------------------------
/lib/actions/auth.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:supabase/supabase.dart';
3 |
4 | /// Action fired whenever the auth changed according to Supabase.
5 | @immutable
6 | class AuthStateChangedAction {
7 | const AuthStateChangedAction(this.authState);
8 |
9 | final AuthState authState;
10 | }
11 |
--------------------------------------------------------------------------------
/lib/actions/date.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | @immutable
4 | class SelectDateAction {
5 | final DateTime date;
6 |
7 | const SelectDateAction(this.date);
8 | }
9 |
--------------------------------------------------------------------------------
/lib/actions/deeplinks.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | /// Dispatched when a deeplink is used.
4 | @immutable
5 | class HandleDeeplinkAction {
6 | final String route;
7 | final List paths;
8 |
9 | const HandleDeeplinkAction._(this.route, this.paths);
10 |
11 | factory HandleDeeplinkAction(String route) {
12 | final paths = route.split('/').skip(1).toList();
13 | return HandleDeeplinkAction._(route, paths);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/actions/default_rules.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | @immutable
4 | class RequestDeleteDefaultRuleAction {
5 | final String memberId;
6 | final String scheduleId;
7 |
8 | const RequestDeleteDefaultRuleAction({
9 | required this.memberId,
10 | required this.scheduleId,
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/lib/actions/feedback.dart:
--------------------------------------------------------------------------------
1 | /// An action to indicate that the user has interacted with the feedback card
2 | class InteractedWithFeedback {}
3 |
--------------------------------------------------------------------------------
/lib/actions/groups.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:image_picker/image_picker.dart';
5 | import 'package:parousia/models/models.dart';
6 |
7 | /// Dispatched when a group is selected and the group details screen should be opened.
8 | @immutable
9 | class GroupDetailsOpenAction {
10 | const GroupDetailsOpenAction(this.groupId);
11 |
12 | final String groupId;
13 | }
14 |
15 | /// Dispatched when a group is selected and the group schedule details screen should be opened.
16 | @immutable
17 | class GroupScheduleDetailsOpenAction {
18 | const GroupScheduleDetailsOpenAction(this.scheduleId);
19 |
20 | final String scheduleId;
21 | }
22 |
23 | /// Dispatched when requesting to refresh all groups.
24 | @immutable
25 | class GroupRefreshAllAction {
26 | final Completer completer;
27 |
28 | GroupRefreshAllAction({completer}) : completer = completer ?? Completer();
29 | }
30 |
31 | /// Dispatched when a group is created or updated.
32 | @immutable
33 | class CreateGroupAction {
34 | const CreateGroupAction({required this.group, this.image});
35 |
36 | final Group group;
37 | final XFile? image;
38 | }
39 |
40 | /// Dispatched when a group is updated.
41 | @immutable
42 | class UpdateGroupAction {
43 | const UpdateGroupAction({required this.group, this.image});
44 |
45 | final Group group;
46 | final XFile? image;
47 | }
48 |
--------------------------------------------------------------------------------
/lib/actions/invites.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:parousia/models/models.dart';
3 |
4 | /// Dispatched when the user entered an invite code to join a group.
5 | @immutable
6 | class JoinWithInviteCodeAction {
7 | const JoinWithInviteCodeAction(this.code);
8 |
9 | final String code;
10 | }
11 |
12 | @immutable
13 | class SuccessUseInviteCode {
14 | const SuccessUseInviteCode();
15 |
16 | // TODO Could this be used to navigate to the group?
17 | // final String groupId;
18 | }
19 |
20 | @immutable
21 | class FailUseInviteCode {
22 | const FailUseInviteCode(this.error);
23 |
24 | final Object? error;
25 | }
26 |
27 | @immutable
28 | class InviteGroupMembersAction {
29 | const InviteGroupMembersAction({
30 | required this.groupId,
31 | required this.contacts,
32 | });
33 |
34 | final String groupId;
35 | final List contacts;
36 | }
37 |
38 | @immutable
39 | class NewMembersCreatedAction {
40 | const NewMembersCreatedAction(this.members);
41 |
42 | final List<(Member, ContactInvite)> members;
43 | }
44 |
--------------------------------------------------------------------------------
/lib/actions/locale.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | @immutable
4 | class ChangeLocaleAction {
5 | final Locale? locale;
6 |
7 | const ChangeLocaleAction(this.locale);
8 | }
9 |
--------------------------------------------------------------------------------
/lib/actions/members.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | /// Dispatched when a member is selected and the member details screen should be opened.
4 | @immutable
5 | class MemberDetailsOpenAction {
6 | const MemberDetailsOpenAction(this.memberId);
7 |
8 | final String memberId;
9 | }
10 |
--------------------------------------------------------------------------------
/lib/actions/profiles.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:image_picker/image_picker.dart';
3 |
4 | @immutable
5 | class SaveProfileAction {
6 | const SaveProfileAction({this.name, this.image});
7 |
8 | final String? name;
9 | final XFile? image;
10 | }
11 |
12 | @immutable
13 | class DeleteProfileAction {
14 | const DeleteProfileAction();
15 | }
16 |
--------------------------------------------------------------------------------
/lib/actions/replies.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | @immutable
4 | class RequestDeleteReplyAction {
5 | final String memberId;
6 | final String scheduleId;
7 | final DateTime instanceDate;
8 |
9 | const RequestDeleteReplyAction({
10 | required this.memberId,
11 | required this.scheduleId,
12 | required this.instanceDate,
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/lib/actions/routing.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | @immutable
4 | class NavigatePushAction {
5 | const NavigatePushAction(this.location, {this.extra});
6 |
7 | final String location;
8 | final Object? extra;
9 | }
10 |
11 | @immutable
12 | class NavigateReplaceAction {
13 | const NavigateReplaceAction(this.location);
14 |
15 | final String location;
16 | }
17 |
--------------------------------------------------------------------------------
/lib/actions/theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | /// Action to request to switch to the next theme.
4 | @immutable
5 | class NextThemeAction {}
6 |
--------------------------------------------------------------------------------
/lib/epics/auth.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:parousia/state/state.dart';
3 | import 'package:redux_epics/redux_epics.dart';
4 | import 'package:rxdart/rxdart.dart';
5 |
6 | /// All epics that handle auth
7 | Epic createAuthEpics() => combineEpics([
8 | _navigateToLastRouteEpic,
9 | ]);
10 |
11 | /// Redirect to the last route if the user is authenticated
12 | Stream _navigateToLastRouteEpic(
13 | Stream actions, EpicStore store) =>
14 | actions
15 | .whereType()
16 | .where((action) =>
17 | store.state.auth.user != null && store.state.auth.lastRoute != null)
18 | .map((action) => HandleDeeplinkAction(store.state.auth.lastRoute!));
19 |
--------------------------------------------------------------------------------
/lib/epics/epics.dart:
--------------------------------------------------------------------------------
1 | export 'auth.dart';
2 | export 'default_rules.dart';
3 | export 'groups.dart';
4 | export 'invites.dart';
5 | export 'members.dart';
6 | export 'profiles.dart';
7 | export 'replies.dart';
8 | export 'routing.dart';
9 | export 'schedules.dart';
10 |
--------------------------------------------------------------------------------
/lib/epics/routing.dart:
--------------------------------------------------------------------------------
1 | import 'package:go_router/go_router.dart';
2 | import 'package:parousia/actions/actions.dart';
3 | import 'package:parousia/state/state.dart';
4 | import 'package:redux_epics/redux_epics.dart';
5 | import 'package:rxdart/rxdart.dart';
6 |
7 | /// All epics that handle the navigation
8 | Epic createRouterEpics(GoRouter router) => combineEpics([
9 | _createRouterPushEpic(router),
10 | _createRouterReplaceEpic(router),
11 | // TODO(borgoat): create epics and actions for other router events
12 | ]);
13 |
14 | Epic _createRouterPushEpic(GoRouter router) {
15 | return (Stream actions, EpicStore store) => actions
16 | .whereType()
17 | .map((action) => router.push(action.location, extra: action.extra));
18 | }
19 |
20 | Epic _createRouterReplaceEpic(GoRouter router) {
21 | return (Stream actions, EpicStore store) => actions
22 | .whereType()
23 | .map((action) => router.replace(action.location));
24 | }
25 |
--------------------------------------------------------------------------------
/lib/models/default_reply.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 | import 'package:rrule/rrule.dart';
4 |
5 | import 'enum.dart';
6 |
7 | part 'default_reply.freezed.dart';
8 |
9 | part 'default_reply.g.dart';
10 |
11 | @freezed
12 | sealed class DefaultRule with _$DefaultRule {
13 | @JsonSerializable(fieldRename: FieldRename.snake)
14 | const factory DefaultRule({
15 | required String memberId,
16 | required String scheduleId,
17 | required ReplyOptions selectedOption,
18 | required RecurrenceRule recurrenceRule,
19 | DateTime? createdAt,
20 | DateTime? updatedAt,
21 | }) = _DefaultRule;
22 |
23 | factory DefaultRule.fromJson(Map json) =>
24 | _$DefaultRuleFromJson(json);
25 | }
26 |
--------------------------------------------------------------------------------
/lib/models/default_reply.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'default_reply.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$DefaultRuleImpl _$$DefaultRuleImplFromJson(Map json) =>
10 | _$DefaultRuleImpl(
11 | memberId: json['member_id'] as String,
12 | scheduleId: json['schedule_id'] as String,
13 | selectedOption:
14 | $enumDecode(_$ReplyOptionsEnumMap, json['selected_option']),
15 | recurrenceRule: RecurrenceRule.fromJson(
16 | json['recurrence_rule'] as Map),
17 | createdAt: json['created_at'] == null
18 | ? null
19 | : DateTime.parse(json['created_at'] as String),
20 | updatedAt: json['updated_at'] == null
21 | ? null
22 | : DateTime.parse(json['updated_at'] as String),
23 | );
24 |
25 | Map _$$DefaultRuleImplToJson(_$DefaultRuleImpl instance) =>
26 | {
27 | 'member_id': instance.memberId,
28 | 'schedule_id': instance.scheduleId,
29 | 'selected_option': _$ReplyOptionsEnumMap[instance.selectedOption]!,
30 | 'recurrence_rule': instance.recurrenceRule,
31 | 'created_at': instance.createdAt?.toIso8601String(),
32 | 'updated_at': instance.updatedAt?.toIso8601String(),
33 | };
34 |
35 | const _$ReplyOptionsEnumMap = {
36 | ReplyOptions.yes: 'yes',
37 | ReplyOptions.no: 'no',
38 | };
39 |
--------------------------------------------------------------------------------
/lib/models/enum.dart:
--------------------------------------------------------------------------------
1 | /// Defines the different methods of inviting a user to a group.
2 | enum InviteMethods {
3 | email,
4 | phone,
5 | code,
6 | }
7 |
8 | /// Reply options for the user to choose from
9 | enum ReplyOptions {
10 | yes,
11 | no,
12 | }
13 |
14 | /// The different roles a user can have in a group
15 | enum GroupRoles {
16 | admin,
17 | member,
18 | }
19 |
--------------------------------------------------------------------------------
/lib/models/group.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'group.freezed.dart';
5 |
6 | part 'group.g.dart';
7 |
8 | @freezed
9 | sealed class Group with _$Group {
10 | @JsonSerializable(fieldRename: FieldRename.snake)
11 | const factory Group({
12 | required String id,
13 | required String displayName,
14 | String? description,
15 | String? picture,
16 | DateTime? createdAt,
17 | DateTime? updatedAt,
18 | }) = _Group;
19 |
20 | factory Group.fromJson(Map json) => _$GroupFromJson(json);
21 | }
22 |
--------------------------------------------------------------------------------
/lib/models/group.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'group.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$GroupImpl _$$GroupImplFromJson(Map json) => _$GroupImpl(
10 | id: json['id'] as String,
11 | displayName: json['display_name'] as String,
12 | description: json['description'] as String?,
13 | picture: json['picture'] as String?,
14 | createdAt: json['created_at'] == null
15 | ? null
16 | : DateTime.parse(json['created_at'] as String),
17 | updatedAt: json['updated_at'] == null
18 | ? null
19 | : DateTime.parse(json['updated_at'] as String),
20 | );
21 |
22 | Map _$$GroupImplToJson(_$GroupImpl instance) =>
23 | {
24 | 'id': instance.id,
25 | 'display_name': instance.displayName,
26 | 'description': instance.description,
27 | 'picture': instance.picture,
28 | 'created_at': instance.createdAt?.toIso8601String(),
29 | 'updated_at': instance.updatedAt?.toIso8601String(),
30 | };
31 |
--------------------------------------------------------------------------------
/lib/models/invite.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | import 'enum.dart';
5 |
6 | part 'invite.freezed.dart';
7 |
8 | part 'invite.g.dart';
9 |
10 | @freezed
11 | sealed class Invite with _$Invite {
12 | @JsonSerializable(fieldRename: FieldRename.snake)
13 | const factory Invite({
14 | required String id,
15 | required String memberId,
16 | required InviteMethods method,
17 | required String value,
18 | DateTime? createdAt,
19 | DateTime? updatedAt,
20 | }) = _Invite;
21 |
22 | factory Invite.fromJson(Map json) => _$InviteFromJson(json);
23 | }
24 |
25 | /// A contact to invite to a group.
26 | class ContactInvite {
27 | final String? displayNameOverride;
28 | final List<(InviteMethods, String)> invites;
29 |
30 | const ContactInvite(
31 | {required this.displayNameOverride, required this.invites});
32 | }
33 |
--------------------------------------------------------------------------------
/lib/models/invite.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'invite.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$InviteImpl _$$InviteImplFromJson(Map json) => _$InviteImpl(
10 | id: json['id'] as String,
11 | memberId: json['member_id'] as String,
12 | method: $enumDecode(_$InviteMethodsEnumMap, json['method']),
13 | value: json['value'] as String,
14 | createdAt: json['created_at'] == null
15 | ? null
16 | : DateTime.parse(json['created_at'] as String),
17 | updatedAt: json['updated_at'] == null
18 | ? null
19 | : DateTime.parse(json['updated_at'] as String),
20 | );
21 |
22 | Map _$$InviteImplToJson(_$InviteImpl instance) =>
23 | {
24 | 'id': instance.id,
25 | 'member_id': instance.memberId,
26 | 'method': _$InviteMethodsEnumMap[instance.method]!,
27 | 'value': instance.value,
28 | 'created_at': instance.createdAt?.toIso8601String(),
29 | 'updated_at': instance.updatedAt?.toIso8601String(),
30 | };
31 |
32 | const _$InviteMethodsEnumMap = {
33 | InviteMethods.email: 'email',
34 | InviteMethods.phone: 'phone',
35 | InviteMethods.code: 'code',
36 | };
37 |
--------------------------------------------------------------------------------
/lib/models/member.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 | import 'package:parousia/models/models.dart';
4 |
5 | part 'member.freezed.dart';
6 |
7 | part 'member.g.dart';
8 |
9 | @freezed
10 | sealed class Member with _$Member {
11 | @JsonSerializable(fieldRename: FieldRename.snake)
12 | const factory Member({
13 | required String id,
14 | required String groupId,
15 | required GroupRoles role,
16 | String? profileId,
17 | String? displayNameOverride,
18 | DateTime? createdAt,
19 | DateTime? updatedAt,
20 | }) = _Member;
21 |
22 | factory Member.fromJson(Map json) => _$MemberFromJson(json);
23 | }
24 |
--------------------------------------------------------------------------------
/lib/models/member.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'member.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$MemberImpl _$$MemberImplFromJson(Map json) => _$MemberImpl(
10 | id: json['id'] as String,
11 | groupId: json['group_id'] as String,
12 | role: $enumDecode(_$GroupRolesEnumMap, json['role']),
13 | profileId: json['profile_id'] as String?,
14 | displayNameOverride: json['display_name_override'] as String?,
15 | createdAt: json['created_at'] == null
16 | ? null
17 | : DateTime.parse(json['created_at'] as String),
18 | updatedAt: json['updated_at'] == null
19 | ? null
20 | : DateTime.parse(json['updated_at'] as String),
21 | );
22 |
23 | Map _$$MemberImplToJson(_$MemberImpl instance) =>
24 | {
25 | 'id': instance.id,
26 | 'group_id': instance.groupId,
27 | 'role': _$GroupRolesEnumMap[instance.role]!,
28 | 'profile_id': instance.profileId,
29 | 'display_name_override': instance.displayNameOverride,
30 | 'created_at': instance.createdAt?.toIso8601String(),
31 | 'updated_at': instance.updatedAt?.toIso8601String(),
32 | };
33 |
34 | const _$GroupRolesEnumMap = {
35 | GroupRoles.admin: 'admin',
36 | GroupRoles.member: 'member',
37 | };
38 |
--------------------------------------------------------------------------------
/lib/models/models.dart:
--------------------------------------------------------------------------------
1 | export 'default_reply.dart';
2 | export 'enum.dart';
3 | export 'group.dart';
4 | export 'invite.dart';
5 | export 'member.dart';
6 | export 'profile.dart';
7 | export 'reply.dart';
8 | export 'schedule.dart';
9 | export 'schedule_summary.dart';
10 |
--------------------------------------------------------------------------------
/lib/models/profile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | part 'profile.freezed.dart';
5 | part 'profile.g.dart';
6 |
7 | @freezed
8 | sealed class Profile with _$Profile {
9 | @JsonSerializable(fieldRename: FieldRename.snake)
10 | const factory Profile({
11 | required String id,
12 | String? displayName,
13 | String? picture,
14 | DateTime? createdAt,
15 | DateTime? updatedAt,
16 | }) = _Profile;
17 |
18 | factory Profile.fromJson(Map json) =>
19 | _$ProfileFromJson(json);
20 | }
21 |
--------------------------------------------------------------------------------
/lib/models/profile.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'profile.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$ProfileImpl _$$ProfileImplFromJson(Map json) =>
10 | _$ProfileImpl(
11 | id: json['id'] as String,
12 | displayName: json['display_name'] as String?,
13 | picture: json['picture'] as String?,
14 | createdAt: json['created_at'] == null
15 | ? null
16 | : DateTime.parse(json['created_at'] as String),
17 | updatedAt: json['updated_at'] == null
18 | ? null
19 | : DateTime.parse(json['updated_at'] as String),
20 | );
21 |
22 | Map _$$ProfileImplToJson(_$ProfileImpl instance) =>
23 | {
24 | 'id': instance.id,
25 | 'display_name': instance.displayName,
26 | 'picture': instance.picture,
27 | 'created_at': instance.createdAt?.toIso8601String(),
28 | 'updated_at': instance.updatedAt?.toIso8601String(),
29 | };
30 |
--------------------------------------------------------------------------------
/lib/models/reply.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 |
4 | import 'enum.dart';
5 |
6 | part 'reply.freezed.dart';
7 |
8 | part 'reply.g.dart';
9 |
10 | @freezed
11 | sealed class Reply with _$Reply {
12 | @JsonSerializable(fieldRename: FieldRename.snake)
13 | const factory Reply({
14 | required String memberId,
15 | required String scheduleId,
16 | required DateTime instanceDate,
17 | required ReplyOptions selectedOption,
18 | DateTime? createdAt,
19 | DateTime? updatedAt,
20 | }) = _Reply;
21 |
22 | factory Reply.fromJson(Map json) => _$ReplyFromJson(json);
23 | }
24 |
--------------------------------------------------------------------------------
/lib/models/reply.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'reply.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$ReplyImpl _$$ReplyImplFromJson(Map json) => _$ReplyImpl(
10 | memberId: json['member_id'] as String,
11 | scheduleId: json['schedule_id'] as String,
12 | instanceDate: DateTime.parse(json['instance_date'] as String),
13 | selectedOption:
14 | $enumDecode(_$ReplyOptionsEnumMap, json['selected_option']),
15 | createdAt: json['created_at'] == null
16 | ? null
17 | : DateTime.parse(json['created_at'] as String),
18 | updatedAt: json['updated_at'] == null
19 | ? null
20 | : DateTime.parse(json['updated_at'] as String),
21 | );
22 |
23 | Map _$$ReplyImplToJson(_$ReplyImpl instance) =>
24 | {
25 | 'member_id': instance.memberId,
26 | 'schedule_id': instance.scheduleId,
27 | 'instance_date': instance.instanceDate.toIso8601String(),
28 | 'selected_option': _$ReplyOptionsEnumMap[instance.selectedOption]!,
29 | 'created_at': instance.createdAt?.toIso8601String(),
30 | 'updated_at': instance.updatedAt?.toIso8601String(),
31 | };
32 |
33 | const _$ReplyOptionsEnumMap = {
34 | ReplyOptions.yes: 'yes',
35 | ReplyOptions.no: 'no',
36 | };
37 |
--------------------------------------------------------------------------------
/lib/models/schedule.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 | import 'package:rrule/rrule.dart';
4 |
5 | part 'schedule.freezed.dart';
6 |
7 | part 'schedule.g.dart';
8 |
9 | @freezed
10 | sealed class Schedule with _$Schedule {
11 | @JsonSerializable(fieldRename: FieldRename.snake)
12 | const factory Schedule({
13 | required String id,
14 | required String groupId,
15 | required String displayName,
16 | required DateTime startDate,
17 | required RecurrenceRule recurrenceRule,
18 | DateTime? createdAt,
19 | DateTime? updatedAt,
20 | }) = _Schedule;
21 |
22 | factory Schedule.fromJson(Map json) =>
23 | _$ScheduleFromJson(json);
24 | }
25 |
--------------------------------------------------------------------------------
/lib/models/schedule.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'schedule.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$ScheduleImpl _$$ScheduleImplFromJson(Map json) =>
10 | _$ScheduleImpl(
11 | id: json['id'] as String,
12 | groupId: json['group_id'] as String,
13 | displayName: json['display_name'] as String,
14 | startDate: DateTime.parse(json['start_date'] as String),
15 | recurrenceRule: RecurrenceRule.fromJson(
16 | json['recurrence_rule'] as Map),
17 | createdAt: json['created_at'] == null
18 | ? null
19 | : DateTime.parse(json['created_at'] as String),
20 | updatedAt: json['updated_at'] == null
21 | ? null
22 | : DateTime.parse(json['updated_at'] as String),
23 | );
24 |
25 | Map _$$ScheduleImplToJson(_$ScheduleImpl instance) =>
26 | {
27 | 'id': instance.id,
28 | 'group_id': instance.groupId,
29 | 'display_name': instance.displayName,
30 | 'start_date': instance.startDate.toIso8601String(),
31 | 'recurrence_rule': instance.recurrenceRule,
32 | 'created_at': instance.createdAt?.toIso8601String(),
33 | 'updated_at': instance.updatedAt?.toIso8601String(),
34 | };
35 |
--------------------------------------------------------------------------------
/lib/presentation/containers/containers.dart:
--------------------------------------------------------------------------------
1 | export 'date_dropdown.dart';
2 | export 'date_fab.dart';
3 | export 'group_details.dart';
4 | export 'group_events.dart';
5 | export 'group_form.dart';
6 | export 'group_manage.dart';
7 | export 'group_member_details.dart';
8 | export 'group_members.dart';
9 | export 'group_schedule_details.dart';
10 | export 'home.dart';
11 | export 'locale.dart';
12 | export 'profile.dart';
13 | export 'schedules_list.dart';
14 | export 'theme_switcher.dart';
15 |
--------------------------------------------------------------------------------
/lib/presentation/containers/date_dropdown.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_redux/flutter_redux.dart';
3 | import 'package:parousia/actions/actions.dart';
4 | import 'package:parousia/state/state.dart';
5 | import 'package:redux/redux.dart';
6 | import 'package:parousia/presentation/widgets/widgets.dart';
7 |
8 | class DateDropdownContainer extends StatelessWidget {
9 | const DateDropdownContainer({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return StoreConnector(
14 | distinct: true,
15 | builder: (context, vm) => DateDropdown(
16 | date: vm.selectedDate,
17 | onDateChanged: vm.onDateChanged,
18 | ),
19 | converter: _ViewModel.fromStore,
20 | );
21 | }
22 | }
23 |
24 | class _ViewModel {
25 | final DateTime selectedDate;
26 | final ValueChanged onDateChanged;
27 |
28 | _ViewModel({
29 | required this.selectedDate,
30 | required this.onDateChanged,
31 | });
32 |
33 | static _ViewModel fromStore(Store store) {
34 | return _ViewModel(
35 | selectedDate: store.state.selectedDate,
36 | onDateChanged: (value) => store.dispatch(SelectDateAction(value)),
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/presentation/containers/date_fab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_redux/flutter_redux.dart';
3 | import 'package:freezed_annotation/freezed_annotation.dart';
4 | import 'package:parousia/actions/actions.dart';
5 | import 'package:parousia/presentation/widgets/widgets.dart';
6 | import 'package:parousia/state/state.dart';
7 | import 'package:redux/redux.dart';
8 |
9 | part 'date_fab.freezed.dart';
10 |
11 | class DateFabContainer extends StatelessWidget {
12 | const DateFabContainer({super.key});
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return StoreConnector(
17 | distinct: true,
18 | builder: (context, vm) => DateFab(
19 | date: vm.selectedDate,
20 | onDateChanged: vm.onDateChanged,
21 | ),
22 | converter: _ViewModel.fromStore,
23 | );
24 | }
25 | }
26 |
27 | @freezed
28 | sealed class _ViewModel with _$ViewModel {
29 | const factory _ViewModel({
30 | required DateTime selectedDate,
31 | required ValueChanged onDateChanged,
32 | }) = __ViewModel;
33 |
34 | static _ViewModel fromStore(Store store) {
35 | return _ViewModel(
36 | selectedDate: store.state.selectedDate,
37 | onDateChanged: (value) => store.dispatch(SelectDateAction(value)),
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/presentation/containers/group_manage.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_redux/flutter_redux.dart';
4 | import 'package:freezed_annotation/freezed_annotation.dart';
5 | import 'package:parousia/models/models.dart';
6 | import 'package:parousia/presentation/presentation.dart';
7 | import 'package:parousia/state/state.dart';
8 | import 'package:redux/redux.dart';
9 | import 'package:redux_entity/redux_entity.dart';
10 |
11 | part 'group_manage.freezed.dart';
12 |
13 | class GroupManageContainer extends StatelessWidget {
14 | final String groupId;
15 |
16 | const GroupManageContainer({
17 | super.key,
18 | required this.groupId,
19 | });
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return StoreConnector(
24 | distinct: true,
25 | converter: (store) => _ViewModel.fromStore(store, groupId),
26 | builder: (context, vm) => GroupManageScreen(
27 | loading: vm.loading,
28 | group: vm.group,
29 | onDelete: vm.onDelete,
30 | ),
31 | );
32 | }
33 | }
34 |
35 | @freezed
36 | sealed class _ViewModel with _$ViewModel {
37 | const factory _ViewModel({
38 | required bool loading,
39 | Group? group,
40 | required ValueSetter onDelete,
41 | }) = __ViewModel;
42 |
43 | static _ViewModel fromStore(Store store, String groupId) {
44 | final group = store.state.groups.entities[groupId];
45 |
46 | return _ViewModel(
47 | group: group,
48 | loading: store.state.groups.creating ||
49 | store.state.groups.loadingAll ||
50 | (store.state.groups.loadingIds[groupId] ?? false),
51 | onDelete: (groupId) => store.dispatch(RequestDeleteOne(groupId)),
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/presentation/containers/locale.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_redux/flutter_redux.dart';
4 | import 'package:freezed_annotation/freezed_annotation.dart';
5 | import 'package:parousia/actions/actions.dart';
6 | import 'package:parousia/presentation/presentation.dart';
7 | import 'package:parousia/selectors/selectors.dart';
8 | import 'package:parousia/state/state.dart';
9 | import 'package:redux/redux.dart';
10 |
11 | part 'locale.freezed.dart';
12 |
13 | class LocaleContainer extends StatelessWidget {
14 | const LocaleContainer({super.key});
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return StoreConnector(
19 | distinct: true,
20 | converter: _ViewModel.fromStore,
21 | builder: (context, vm) => LocaleScreen(
22 | selectedLocale: vm.locale,
23 | onLocaleChanged: vm.changeLocale,
24 | ),
25 | );
26 | }
27 | }
28 |
29 | @freezed
30 | sealed class _ViewModel with _$ViewModel {
31 | const factory _ViewModel({
32 | Locale? locale,
33 | required ValueChanged changeLocale,
34 | }) = __ViewModel;
35 |
36 | factory _ViewModel.fromStore(Store store) {
37 | return _ViewModel(
38 | locale: localeSelector(store.state),
39 | changeLocale: (Locale? locale) =>
40 | store.dispatch(ChangeLocaleAction(locale)),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/presentation/containers/profile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_redux/flutter_redux.dart';
4 | import 'package:freezed_annotation/freezed_annotation.dart';
5 | import 'package:parousia/actions/actions.dart';
6 | import 'package:parousia/models/models.dart';
7 | import 'package:parousia/presentation/presentation.dart';
8 | import 'package:parousia/state/state.dart';
9 | import 'package:redux/redux.dart';
10 |
11 | part 'profile.freezed.dart';
12 |
13 | class ProfileContainer extends StatelessWidget {
14 | final bool? userNavigated;
15 |
16 | const ProfileContainer({super.key, this.userNavigated});
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return StoreConnector(
21 | builder: (context, vm) => ProfileScreen(
22 | profile: vm.profile,
23 | userNavigated: userNavigated,
24 | onSave: vm.onSave,
25 | ),
26 | converter: _ViewModel.fromStore,
27 | );
28 | }
29 | }
30 |
31 | @freezed
32 | sealed class _ViewModel with _$ViewModel {
33 | // TODO add loading state
34 |
35 | const factory _ViewModel({
36 | Profile? profile,
37 | required OnProfileSaveCallback onSave,
38 | }) = __ViewModel;
39 |
40 | static _ViewModel fromStore(Store store) {
41 | return _ViewModel(
42 | profile: store.state.profiles.entities[store.state.auth.user?.id],
43 | onSave: (result) {
44 | final (name, image) = result;
45 | store.dispatch(SaveProfileAction(name: name, image: image));
46 | },
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/presentation/containers/theme_switcher.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_redux/flutter_redux.dart';
4 | import 'package:freezed_annotation/freezed_annotation.dart';
5 | import 'package:parousia/actions/actions.dart';
6 | import 'package:parousia/presentation/presentation.dart';
7 | import 'package:parousia/selectors/selectors.dart';
8 | import 'package:parousia/state/state.dart';
9 | import 'package:redux/redux.dart';
10 |
11 | part 'theme_switcher.freezed.dart';
12 |
13 | class ThemeSwitcherContainer extends StatelessWidget {
14 | const ThemeSwitcherContainer({super.key});
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return StoreConnector(
19 | distinct: true,
20 | converter: _ViewModel.fromStore,
21 | builder: (context, vm) =>
22 | ThemeSwitcherTile(themeMode: vm.themeMode, onTap: vm.nextTheme));
23 | }
24 | }
25 |
26 | @freezed
27 | sealed class _ViewModel with _$ViewModel {
28 | const factory _ViewModel({
29 | required ThemeMode themeMode,
30 | required VoidCallback nextTheme,
31 | }) = __ViewModel;
32 |
33 | factory _ViewModel.fromStore(Store store) {
34 | return _ViewModel(
35 | themeMode: themeModeSelector(store.state),
36 | nextTheme: () => store.dispatch(NextThemeAction()),
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/presentation/presentation.dart:
--------------------------------------------------------------------------------
1 | /// Contains all stateful and stateless widgets used in the presentation layer.
2 | /// This includes all screens, widgets, and containers.
3 | /// * [containers] - Smart widgets that use StoreProvider to access the state.
4 | /// * [screens] - Scaffold widgets that are the root of a page.
5 | /// * [widgets] - Stateless widgets and stateful widgets that do not use StoreProvider.
6 | library;
7 |
8 | export 'containers/containers.dart';
9 | export 'screens/screens.dart';
10 | export 'widgets/widgets.dart';
11 |
--------------------------------------------------------------------------------
/lib/presentation/screens/locale.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 |
4 | class LocaleScreen extends StatelessWidget {
5 | final Locale? selectedLocale;
6 | final ValueChanged onLocaleChanged;
7 |
8 | // TODO replace with something more robust
9 | // https://pub.dev/packages/flutter_localized_locales
10 | static const localeNames = {
11 | 'de': 'Deutsch',
12 | 'en': 'English',
13 | 'es': 'Español',
14 | 'fr': 'Français',
15 | 'it': 'Italiano',
16 | };
17 |
18 | const LocaleScreen({
19 | super.key,
20 | this.selectedLocale,
21 | required this.onLocaleChanged,
22 | });
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | final l10n = AppLocalizations.of(context)!;
27 |
28 | final radioButtons = AppLocalizations.supportedLocales.map(
29 | (l) => RadioListTile.adaptive(
30 | title: Text(localeNames[l.languageCode] ?? l.languageCode),
31 | groupValue: selectedLocale,
32 | value: l,
33 | onChanged: (l) => onLocaleChanged(l!),
34 | ),
35 | );
36 |
37 | return Scaffold(
38 | appBar: AppBar(
39 | title: Text(l10n.language),
40 | ),
41 | body: Column(
42 | children: [
43 | RadioListTile.adaptive(
44 | title: Text(l10n.systemLanguage),
45 | groupValue: selectedLocale,
46 | value: null,
47 | onChanged: (l) => onLocaleChanged(null),
48 | ),
49 | ...radioButtons,
50 | ],
51 | ),
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/presentation/screens/schedule_create.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 | import 'package:go_router/go_router.dart';
4 | import 'package:parousia/models/models.dart';
5 | import 'package:parousia/presentation/presentation.dart';
6 |
7 | class ScheduleCreateScreen extends StatefulWidget {
8 | const ScheduleCreateScreen({super.key});
9 |
10 | @override
11 | createState() => _ScheduleCreateScreenState();
12 | }
13 |
14 | class _ScheduleCreateScreenState extends State {
15 | bool _showSaveButton = false;
16 | Schedule? _schedule;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | final l10n = AppLocalizations.of(context)!;
21 |
22 | return Scaffold(
23 | appBar: AppBar(
24 | title: Text(l10n.createNewEvent),
25 | ),
26 | body: SingleChildScrollView(
27 | padding: const EdgeInsets.all(16.0),
28 | child: ScheduleForm(
29 | onChanged: (schedule) {
30 | setState(() {
31 | _showSaveButton = true;
32 | });
33 | _schedule = schedule;
34 | },
35 | ),
36 | ),
37 | floatingActionButton: _showSaveButton
38 | ? FloatingActionButton.extended(
39 | onPressed: () => context.pop(_schedule),
40 | label: Text(l10n.save),
41 | icon: const Icon(Icons.check),
42 | )
43 | : null,
44 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/presentation/screens/screens.dart:
--------------------------------------------------------------------------------
1 | export 'auth.dart';
2 | export 'group_create.dart';
3 | export 'group_details.dart';
4 | export 'group_manage.dart';
5 | export 'group_member_details.dart';
6 | export 'group_schedule_details.dart';
7 | export 'home.dart';
8 | export 'locale.dart';
9 | export 'profile.dart';
10 | export 'schedule_create.dart';
11 | export 'select_contacts.dart';
12 | export 'settings.dart';
13 | export 'settings_more.dart';
14 |
--------------------------------------------------------------------------------
/lib/presentation/screens/settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 | import 'package:parousia/go_router_builder.dart';
4 | import 'package:parousia/presentation/presentation.dart';
5 |
6 | /// A screen that allows the user to configure app settings.
7 | class SettingsScreen extends StatelessWidget {
8 | const SettingsScreen({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | final l10n = AppLocalizations.of(context)!;
13 | return Scaffold(
14 | appBar: AppBar(
15 | title: Text(l10n.settings),
16 | ),
17 | body: ListView(
18 | children: [
19 | ListTile(
20 | title: Text(l10n.language),
21 | leading: const Icon(Icons.translate),
22 | onTap: () => LocaleRoute().push(context),
23 | ),
24 | const ThemeSwitcherContainer(),
25 | ListTile(
26 | title: Text(l10n.settingsMoreInfo),
27 | leading: const Icon(Icons.info_outline),
28 | onTap: () => SettingsMoreRoute().push(context),
29 | ),
30 | const SignOutTile(),
31 | ],
32 | ),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/presentation/screens/settings_more.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 | import 'package:parousia/go_router_builder.dart';
4 | import 'package:parousia/presentation/presentation.dart';
5 |
6 | /// Screen for less common settings.
7 | class SettingsMoreScreen extends StatelessWidget {
8 | const SettingsMoreScreen({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | final l10n = AppLocalizations.of(context)!;
13 | return Scaffold(
14 | appBar: AppBar(
15 | title: Text(l10n.settingsMoreInfo),
16 | ),
17 | body: ListView(
18 | children: [
19 | ListTile(
20 | title: Text('Licenses'),
21 | leading: const Icon(Icons.copyright),
22 | onTap: () => LicensesRoute().push(context),
23 | ),
24 | const DeleteProfileTile(),
25 | ],
26 | ),
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/date_dropdown.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:intl/intl.dart';
3 |
4 | class DateDropdown extends StatelessWidget {
5 | final _dateFormat = DateFormat.MMMMEEEEd();
6 | final DateTime? date;
7 | final ValueChanged? onDateChanged;
8 |
9 | DateDropdown({
10 | super.key,
11 | this.date,
12 | this.onDateChanged,
13 | });
14 |
15 | Future _selectDate(BuildContext context) async {
16 | final DateTime? newDate = await showDatePicker(
17 | context: context,
18 | initialDate: date,
19 | firstDate: DateTime.now().add(const Duration(days: -365)),
20 | lastDate: DateTime(2101),
21 | );
22 |
23 | if (newDate != null && newDate != date) {
24 | onDateChanged?.call(newDate);
25 | }
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | return Padding(
31 | padding: const EdgeInsets.symmetric(vertical: 16),
32 | child: ElevatedButton.icon(
33 | icon: const Icon(Icons.event),
34 | label: Text(date != null ? _dateFormat.format(date!) : ''),
35 | onPressed: () => _selectDate(context),
36 | ),
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/date_fab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
3 | import 'package:intl/intl.dart';
4 |
5 | class DateFab extends StatelessWidget {
6 | final _dateFormat = DateFormat.MMMMEEEEd();
7 | final DateTime? date;
8 | final ValueChanged? onDateChanged;
9 |
10 | DateFab({
11 | super.key,
12 | this.date,
13 | this.onDateChanged,
14 | });
15 |
16 | _selectNewDate(BuildContext context) async {
17 | final newDate = await showDatePicker(
18 | context: context,
19 | initialDate: date ?? DateTime.now(),
20 | firstDate: DateTime.now().add(const Duration(days: -365)),
21 | lastDate: DateTime(9999),
22 | );
23 |
24 | if (newDate != null) {
25 | onDateChanged?.call(newDate);
26 | }
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return FloatingActionButton.extended(
32 | onPressed: () => _selectNewDate(context),
33 | icon: const FaIcon(FontAwesomeIcons.calendarDay),
34 | label: Text(date != null ? _dateFormat.format(date!) : ''),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/empty_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class EmptyState extends StatelessWidget {
4 | final Widget? text;
5 | final String? image;
6 |
7 | const EmptyState({super.key, this.text, this.image});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | final theme = Theme.of(context);
12 |
13 | final imageUri = image != null ? 'assets/images/$image' : null;
14 |
15 | return Padding(
16 | padding: const EdgeInsets.all(16.0),
17 | child: Column(
18 | children: [
19 | if (text != null) text!,
20 | if (image != null)
21 | Expanded(
22 | child: Container(
23 | decoration: BoxDecoration(
24 | image: DecorationImage(
25 | image: AssetImage(imageUri!),
26 | invertColors: theme.brightness == Brightness.dark,
27 | ),
28 | ),
29 | ),
30 | ),
31 | ],
32 | ),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/group_form.stories.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:parousia/util/util.dart';
3 | import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
4 |
5 | import 'group_form.dart';
6 |
7 | @widgetbook.UseCase(
8 | type: GroupForm,
9 | name: 'with data',
10 | )
11 | Widget withDataUseCase(BuildContext context) {
12 | return GroupForm(onSave: (_) {}, group: Fake.group());
13 | }
14 |
15 | @widgetbook.UseCase(
16 | type: GroupForm,
17 | name: 'empty',
18 | )
19 | Widget emptyUseCase(BuildContext context) {
20 | return GroupForm(onSave: (_) {});
21 | }
22 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/groups_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:parousia/go_router_builder.dart';
3 | import 'package:parousia/models/models.dart';
4 | import 'package:parousia/presentation/presentation.dart';
5 |
6 | /// A widget to display a list of groups and route to their details.
7 | class GroupsList extends StatelessWidget {
8 | final Iterable? groups;
9 |
10 | const GroupsList({super.key, this.groups});
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | final theme = Theme.of(context);
15 | return ListView.separated(
16 | separatorBuilder: (context, index) => const SizedBox(height: 8),
17 | itemBuilder: (context, index) {
18 | final group = groups!.elementAt(index);
19 | final picture = group.picture;
20 | final displayName = group.displayName;
21 | final description = group.description;
22 |
23 | return ListTile(
24 | leading: Hero(
25 | tag: picture ?? group.hashCode,
26 | child: ProfilePicture(
27 | image: picture != null ? NetworkImage(picture) : null,
28 | icon: Icons.group,
29 | radius: 24,
30 | color: theme.colorScheme.secondary,
31 | ),
32 | ),
33 | title: Hero(
34 | tag: group,
35 | child: Text(displayName, style: theme.textTheme.headlineMedium)),
36 | subtitle: description != null
37 | ? Text(description,
38 | overflow: TextOverflow.fade, softWrap: false, maxLines: 1)
39 | : null,
40 | onTap: () =>
41 | GroupDetailsRoute(groupId: group.id.toString()).push(context),
42 | );
43 | },
44 | itemCount: groups?.length ?? 0,
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/image_crop.dart:
--------------------------------------------------------------------------------
1 | import 'dart:typed_data';
2 |
3 | import 'package:crop_your_image/crop_your_image.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class ImageCrop extends StatefulWidget {
7 | final Uint8List imageData;
8 | final ValueSetter? onCrop;
9 |
10 | const ImageCrop({super.key, required this.imageData, this.onCrop});
11 |
12 | @override
13 | ImageCropState createState() => ImageCropState();
14 | }
15 |
16 | class ImageCropState extends State {
17 | final _controller = CropController();
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | final imageData = widget.imageData;
22 |
23 | return Scaffold(
24 | appBar: AppBar(
25 | actions: [
26 | IconButton(
27 | icon: Icon(Icons.cut),
28 | onPressed: () {
29 | _controller.crop();
30 | },
31 | ),
32 | ],
33 | ),
34 | body: Crop(
35 | controller: _controller,
36 | image: imageData,
37 | aspectRatio: 1,
38 | radius: 20,
39 | interactive: true,
40 | withCircleUi: true,
41 | progressIndicator: const CircularProgressIndicator(),
42 | onCropped: (result) {
43 | switch (result) {
44 | case CropSuccess(:final croppedImage):
45 | widget.onCrop?.call(croppedImage);
46 | case CropFailure():
47 | widget.onCrop?.call(null);
48 | }
49 | },
50 | ),
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/profile_picture.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:parousia/util/util.dart';
3 |
4 | class ProfilePicture extends StatelessWidget {
5 | const ProfilePicture({
6 | super.key,
7 | this.onPressed,
8 | this.image,
9 | this.name,
10 | this.radius,
11 | this.loadingValue = 1,
12 | this.icon,
13 | this.color,
14 | });
15 |
16 | final VoidCallback? onPressed;
17 | final ImageProvider? image;
18 | final String? name;
19 | final double? radius;
20 | final double? loadingValue;
21 | final IconData? icon;
22 | final Color? color;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | final theme = Theme.of(context);
27 | final nameInitials = getNameInitials(name);
28 | final padding = radius != null ? radius! / 12.0 : 1.0;
29 | final color = this.color ?? theme.colorScheme.primary;
30 | final backgroundColor =
31 | HSLColor.fromColor(color).withLightness(0.9).toColor();
32 |
33 | return ElevatedButton(
34 | onPressed: onPressed,
35 | style: OutlinedButton.styleFrom(shape: CircleBorder()),
36 | child: Stack(
37 | alignment: Alignment.center,
38 | children: [
39 | CircleAvatar(
40 | radius: radius,
41 | backgroundColor: backgroundColor,
42 | foregroundImage: image,
43 | child: nameInitials != null && nameInitials.isNotEmpty
44 | ? Text(nameInitials)
45 | : Icon(icon, size: radius, color: color),
46 | ),
47 | Positioned.fill(
48 | child: CircularProgressIndicator(
49 | strokeWidth: padding,
50 | value: loadingValue,
51 | color: color,
52 | ),
53 | ),
54 | ],
55 | ),
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/schedule_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:intl/intl.dart';
3 | import 'package:parousia/models/models.dart';
4 | import 'package:parousia/presentation/widgets/widgets.dart';
5 |
6 | class ScheduleTile extends StatelessWidget {
7 | final timeFormat = DateFormat.jm();
8 | final ScheduleInstanceSummary schedule;
9 | final ValueChanged? onReplyChanged;
10 | final ValueChanged? onScheduleTapped;
11 |
12 | ScheduleTile({
13 | super.key,
14 | required this.schedule,
15 | this.onReplyChanged,
16 | this.onScheduleTapped,
17 | });
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return ListTile(
22 | title: Text(schedule.displayName),
23 | subtitle: Text(timeFormat.format(schedule.instanceDate)),
24 | trailing: Badge.count(
25 | count: schedule.yesCount,
26 | backgroundColor: Colors.green,
27 | alignment: Alignment.topLeft,
28 | child: ReplyButtons(
29 | reply: schedule.myReply,
30 | defaultReply: schedule.myDefaultReply,
31 | onReplyChanged: onReplyChanged,
32 | ),
33 | ),
34 | onTap: () => onScheduleTapped?.call(schedule),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/theme_switcher_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3 |
4 | /// A ListTile that shows the current theme mode and allows the user to change it.
5 | class ThemeSwitcherTile extends StatelessWidget {
6 | final ThemeMode themeMode;
7 | final VoidCallback onTap;
8 |
9 | const ThemeSwitcherTile({
10 | super.key,
11 | nextTheme,
12 | required this.themeMode,
13 | required this.onTap,
14 | });
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | final l10n = AppLocalizations.of(context)!;
19 |
20 | final (icon, subtitle) = switch (themeMode) {
21 | ThemeMode.system => (Icons.brightness_auto, l10n.themeSystem),
22 | ThemeMode.light => (Icons.sunny, l10n.themeLight),
23 | ThemeMode.dark => (Icons.dark_mode, l10n.themeDark),
24 | };
25 |
26 | return ListTile(
27 | title: Text(l10n.changeTheme),
28 | subtitle: Text(subtitle),
29 | leading: Icon(icon),
30 | onTap: onTap,
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/presentation/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'date_dropdown.dart';
2 | export 'date_fab.dart';
3 | export 'default_rule_action_sheet.dart';
4 | export 'delete_profile_tile.dart';
5 | export 'empty_state.dart';
6 | export 'form_builder_recurrence_rule.dart';
7 | export 'group_events.dart';
8 | export 'group_form.dart';
9 | export 'group_join.dart';
10 | export 'group_members.dart';
11 | export 'groups_list.dart';
12 | export 'image_crop.dart';
13 | export 'image_form_field.dart';
14 | export 'invite_modal.dart';
15 | export 'profile_picture.dart';
16 | export 'reply_button.dart';
17 | export 'schedule_form.dart';
18 | export 'schedule_member_tile.dart';
19 | export 'schedule_tile.dart';
20 | export 'schedules_list.dart';
21 | export 'sign_out_tile.dart';
22 | export 'theme_switcher_tile.dart';
23 | export 'feedback_card.dart';
24 |
--------------------------------------------------------------------------------
/lib/reducers/auth.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:parousia/state/state.dart';
3 | import 'package:redux/redux.dart';
4 |
5 | final authReducer = combineReducers([
6 | TypedReducer(_authStateChanged).call,
7 | TypedReducer(_deeplinkUnauthenticated).call,
8 | ]);
9 |
10 | AuthState _authStateChanged(
11 | AuthState authState, AuthStateChangedAction action) {
12 | final user = action.authState.session?.user;
13 | if (user != null) {
14 | return authState.copyWith(
15 | status: AuthStatus.authenticated,
16 | user: user,
17 | );
18 | }
19 |
20 | return authState;
21 | }
22 |
23 | AuthState _deeplinkUnauthenticated(
24 | AuthState authState, HandleDeeplinkAction action) {
25 | final isLastRoute = action.route == authState.lastRoute;
26 | final user = authState.user;
27 | if (user != null && !isLastRoute) return authState;
28 |
29 | return authState.copyWith(
30 | lastRoute: isLastRoute ? null : action.route,
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/lib/reducers/feedback.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:redux/redux.dart';
3 |
4 | final feedbackReducer = combineReducers([
5 | TypedReducer(_interactedWithFeedback).call,
6 | ]);
7 |
8 | /// Whenever the user interacts with the feedback, we should hide it.
9 | bool _interactedWithFeedback(
10 | bool? shouldShowFeedback, InteractedWithFeedback action) {
11 | return true;
12 | }
13 |
--------------------------------------------------------------------------------
/lib/reducers/groups.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:parousia/models/models.dart';
3 | import 'package:redux/redux.dart';
4 | import 'package:redux_entity/redux_entity.dart';
5 |
6 | import 'remote_entities.dart';
7 |
8 | final combinedGroupsReducer = combineReducers([
9 | TypedReducer, JoinWithInviteCodeAction>(
10 | (state, action) => state.copyWith(creating: true),
11 | ).call,
12 | TypedReducer, SuccessUseInviteCode>(
13 | (state, action) => state.copyWith(creating: false),
14 | ).call,
15 | TypedReducer, FailUseInviteCode>(
16 | (state, action) => state.copyWith(creating: false, error: action.error),
17 | ).call,
18 | groupsReducer.call,
19 | ]);
20 |
--------------------------------------------------------------------------------
/lib/reducers/locale.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:parousia/state/state.dart';
3 | import 'package:redux/redux.dart';
4 |
5 | final localeReducer = combineReducers([
6 | TypedReducer(_changeLocale).call,
7 | ]);
8 |
9 | LocaleState? _changeLocale(LocaleState? locale, ChangeLocaleAction action) {
10 | final locale = action.locale;
11 |
12 | if (locale == null) {
13 | return null;
14 | }
15 |
16 | return LocaleState(
17 | languageCode: locale.languageCode,
18 | scriptCode: locale.scriptCode,
19 | countryCode: locale.countryCode,
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/lib/reducers/reducers.dart:
--------------------------------------------------------------------------------
1 | export 'root_reducer.dart';
2 |
--------------------------------------------------------------------------------
/lib/reducers/remote_entities.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/models/models.dart';
2 | import 'package:redux_entity/redux_entity.dart';
3 |
4 | final profilesReducer =
5 | RemoteEntityReducer, Profile>();
6 |
7 | final groupsReducer = RemoteEntityReducer, Group>(
8 | selectId: (group) => group.id.toString(),
9 | );
10 |
11 | final membersReducer = RemoteEntityReducer, Member>(
12 | selectId: (member) => member.id.toString(),
13 | );
14 |
15 | final invitesReducer = RemoteEntityReducer, Invite>(
16 | selectId: (invite) => invite.id.toString(),
17 | );
18 |
19 | final schedulesReducer =
20 | RemoteEntityReducer, Schedule>(
21 | selectId: (schedule) => schedule.id.toString(),
22 | );
23 |
24 | final defaultRulesReducer =
25 | RemoteEntityReducer, DefaultRule>(
26 | selectId: (defaultRule) =>
27 | "${defaultRule.memberId}-${defaultRule.scheduleId}",
28 | );
29 |
30 | final repliesReducer = RemoteEntityReducer, Reply>(
31 | selectId: (reply) =>
32 | "${reply.memberId}-${reply.scheduleId}-${reply.instanceDate}",
33 | );
34 |
--------------------------------------------------------------------------------
/lib/reducers/root_reducer.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:parousia/reducers/feedback.dart';
3 | import 'package:parousia/state/state.dart';
4 | import 'package:supabase_flutter/supabase_flutter.dart';
5 |
6 | import 'auth.dart';
7 | import 'groups.dart';
8 | import 'locale.dart';
9 | import 'remote_entities.dart';
10 | import 'selected_date.dart';
11 | import 'selected_group_id.dart';
12 | import 'selected_schedule_id.dart';
13 | import 'theme.dart';
14 |
15 | /// The root reducer combines all the reducers for the app into one.
16 | AppState rootReducer(AppState state, dynamic action) {
17 | // TODO: use combineReducers?
18 | if (action is AuthStateChangedAction &&
19 | action.authState.event == AuthChangeEvent.signedOut) {
20 | return AppState.initialState();
21 | }
22 |
23 | return AppState(
24 | groups: combinedGroupsReducer(state.groups, action),
25 | profiles: profilesReducer(state.profiles, action),
26 | members: membersReducer(state.members, action),
27 | invites: invitesReducer(state.invites, action),
28 | schedules: schedulesReducer(state.schedules, action),
29 | defaultRules: defaultRulesReducer(state.defaultRules, action),
30 | replies: repliesReducer(state.replies, action),
31 | auth: authReducer(state.auth, action),
32 | themeMode: themeReducer(state.themeMode, action),
33 | locale: localeReducer(state.locale, action),
34 | selectedDate: selectedDateReducer(state.selectedDate, action),
35 | selectedGroupId: selectedGroupIdReducer(state.selectedGroupId, action),
36 | selectedScheduleId:
37 | selectedScheduleIdReducer(state.selectedScheduleId, action),
38 | hasSeenFeedbackCard: feedbackReducer(state.hasSeenFeedbackCard, action),
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/lib/reducers/selected_date.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:redux/redux.dart';
3 |
4 | final selectedDateReducer = combineReducers([
5 | TypedReducer(_changeDate).call,
6 | ]);
7 |
8 | DateTime _changeDate(DateTime currentDate, SelectDateAction action) {
9 | return action.date;
10 | }
11 |
--------------------------------------------------------------------------------
/lib/reducers/selected_group_id.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:redux/redux.dart';
3 |
4 | final selectedGroupIdReducer = combineReducers([
5 | TypedReducer(_changeGroup).call,
6 | ]);
7 |
8 | String _changeGroup(String? currentGroupId, GroupDetailsOpenAction action) {
9 | return action.groupId;
10 | }
11 |
--------------------------------------------------------------------------------
/lib/reducers/selected_schedule_id.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/actions/actions.dart';
2 | import 'package:redux/redux.dart';
3 |
4 | final selectedScheduleIdReducer = combineReducers([
5 | TypedReducer(_changeSchedule).call,
6 | ]);
7 |
8 | String _changeSchedule(
9 | String? currentScheduleId, GroupScheduleDetailsOpenAction action) {
10 | return action.scheduleId;
11 | }
12 |
--------------------------------------------------------------------------------
/lib/reducers/theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:parousia/actions/actions.dart';
3 | import 'package:redux/redux.dart';
4 |
5 | final themeReducer = combineReducers([
6 | TypedReducer(_nextTheme).call,
7 | ]);
8 |
9 | ThemeMode _nextTheme(ThemeMode themeMode, NextThemeAction action) {
10 | return ThemeMode.values[(themeMode.index - 1) % ThemeMode.values.length];
11 | }
12 |
--------------------------------------------------------------------------------
/lib/repositories/const.dart:
--------------------------------------------------------------------------------
1 | /// Enum for all tables in the database.
2 | enum Tables {
3 | roles,
4 | profiles,
5 | groups,
6 | members,
7 | invites,
8 | schedules,
9 | default_rules,
10 | replies,
11 | }
12 |
13 | /// Enum with all bucket IDs in Supabase.
14 | enum Buckets {
15 | public,
16 | private,
17 | }
18 |
--------------------------------------------------------------------------------
/lib/repositories/default_rules.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/models/models.dart';
2 |
3 | import 'const.dart';
4 | import 'supabase.dart';
5 |
6 | class DefaultRulesRepository extends SupabaseRepository with Postgrest {
7 | DefaultRulesRepository({required super.supabase})
8 | : super(tableName: Tables.default_rules);
9 |
10 | Future> getDefaultRules(String groupId) async {
11 | return table()
12 | .select('*,members!inner(*)')
13 | .eq('members.group_id', groupId)
14 | .withConverter((data) => data.map(DefaultRule.fromJson));
15 | }
16 |
17 | Future createDefaultRule(DefaultRule reply) async {
18 | return table()
19 | .upsert({
20 | 'schedule_id': reply.scheduleId,
21 | 'member_id': reply.memberId,
22 | 'selected_option': reply.selectedOption.name,
23 | 'recurrence_rule': reply.recurrenceRule,
24 | })
25 | .select()
26 | .single()
27 | .withConverter((data) => DefaultRule.fromJson(data));
28 | }
29 |
30 | Future deleteDefaultRule(
31 | {required String memberId, required String scheduleId}) async {
32 | return table()
33 | .delete()
34 | .eq('member_id', memberId)
35 | .eq('schedule_id', scheduleId);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/repositories/profiles.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/models/models.dart';
2 | import 'package:supabase_flutter/supabase_flutter.dart';
3 |
4 | import 'const.dart';
5 | import 'supabase.dart';
6 |
7 | class ProfilesRepository extends SupabaseRepository with Postgrest {
8 | const ProfilesRepository({required super.supabase})
9 | : super(tableName: Tables.profiles);
10 |
11 | Future getProfileById(String id) async {
12 | return table()
13 | .select()
14 | .eq('id', id)
15 | .single()
16 | .withConverter(Profile.fromJson);
17 | }
18 |
19 | Future updateProfile({
20 | required String id,
21 | String? displayName,
22 | String? pictureUrl,
23 | }) async {
24 | return supabase.auth.updateUser(UserAttributes(data: {
25 | if (displayName != null) 'full_name': displayName,
26 | if (pictureUrl != null) 'avatar_url': pictureUrl,
27 | }));
28 | }
29 |
30 | Future deleteProfile() async {
31 | await supabase.functions.invoke('delete_user_account');
32 | }
33 |
34 | Future signOut() async =>
35 | await supabase.auth.signOut(scope: SignOutScope.global);
36 | }
37 |
--------------------------------------------------------------------------------
/lib/repositories/replies.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/models/models.dart';
2 | import 'package:parousia/util/util.dart';
3 |
4 | import 'const.dart';
5 | import 'supabase.dart';
6 |
7 | class RepliesRepository extends SupabaseRepository with Postgrest {
8 | const RepliesRepository({required super.supabase})
9 | : super(tableName: Tables.replies);
10 |
11 | Future> getRepliesForDay(String groupId, DateTime day) async {
12 | return getRepliesForDateRange(groupId, day.toUtc().getDayRange());
13 | }
14 |
15 | Future> getRepliesForDateRange(
16 | String groupId, DateTimeRange dateRange) async {
17 | return table()
18 | .select('*,members!inner(*)')
19 | .eq('members.group_id', groupId)
20 | .gte('instance_date', dateRange.start)
21 | .lt('instance_date', dateRange.end)
22 | .withConverter((data) => data.map(Reply.fromJson));
23 | }
24 |
25 | Future createReply(Reply reply) async {
26 | return table()
27 | .upsert({
28 | 'schedule_id': reply.scheduleId,
29 | 'member_id': reply.memberId,
30 | 'instance_date': reply.instanceDate.toIso8601String(),
31 | 'selected_option': reply.selectedOption.name,
32 | }, onConflict: 'member_id, schedule_id, instance_date')
33 | .select()
34 | .single()
35 | .withConverter((data) => Reply.fromJson(data));
36 | }
37 |
38 | Future deleteReply({
39 | required String memberId,
40 | required String scheduleId,
41 | required DateTime instanceDate,
42 | }) async {
43 | return table()
44 | .delete()
45 | .eq('member_id', memberId)
46 | .eq('schedule_id', scheduleId)
47 | .eq('instance_date', instanceDate.toIso8601String());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/repositories/repositories.dart:
--------------------------------------------------------------------------------
1 | export 'default_rules.dart';
2 | export 'groups.dart';
3 | export 'invites.dart';
4 | export 'members.dart';
5 | export 'profiles.dart';
6 | export 'replies.dart';
7 | export 'schedules.dart';
8 | export 'storage.dart';
9 |
--------------------------------------------------------------------------------
/lib/repositories/schedules.dart:
--------------------------------------------------------------------------------
1 | import 'package:parousia/models/models.dart';
2 | import 'package:uuid/uuid.dart';
3 |
4 | import 'const.dart';
5 | import 'supabase.dart';
6 |
7 | class SchedulesRepository extends SupabaseRepository with Postgrest {
8 | const SchedulesRepository({required super.supabase})
9 | : super(tableName: Tables.schedules);
10 |
11 | Future createSchedule(Schedule schedule) async {
12 | return table()
13 | .insert({
14 | 'id': const Uuid().v7(),
15 | 'group_id': schedule.groupId,
16 | 'display_name': schedule.displayName,
17 | 'start_date': schedule.startDate.toIso8601String(),
18 | 'recurrence_rule': schedule.recurrenceRule,
19 | })
20 | .select()
21 | .single()
22 | .withConverter((data) => Schedule.fromJson(data));
23 | }
24 |
25 | Future> getGroupSchedules(String groupId) async {
26 | return table()
27 | .select('*,replies(*),default_rules(*)')
28 | .eq('group_id', groupId)
29 | .withConverter((data) => data.map(Schedule.fromJson));
30 | }
31 |
32 | Future deleteSchedule(String scheduleId) async {
33 | return table().delete().eq('id', scheduleId);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/repositories/storage.dart:
--------------------------------------------------------------------------------
1 | import 'package:image_picker/image_picker.dart';
2 | import 'package:mime/mime.dart';
3 | import 'package:supabase/supabase.dart';
4 |
5 | import 'const.dart';
6 | import 'supabase.dart';
7 |
8 | /// Repository for uploading files to Supabase Storage.
9 | class StorageRepository extends SupabaseRepository with Storage {
10 | const StorageRepository({required super.supabase})
11 | : super(bucketName: Buckets.public);
12 |
13 | /// Uploads a file to the public bucket.
14 | Future uploadPublicXFile(String key, XFile file) async {
15 | final ext =
16 | file.mimeType != null ? extensionFromMime(file.mimeType!) : 'jpg';
17 | final path = '${supabase.auth.currentUser!.id}/$key.$ext';
18 | final bytes = await file.readAsBytes();
19 |
20 | final response = await bucket().uploadBinary(
21 | path,
22 | bytes,
23 | fileOptions: FileOptions(
24 | contentType: file.mimeType,
25 | ),
26 | );
27 |
28 | return bucket().getPublicUrl(path);
29 | }
30 |
31 | /// Deletes all files owned by the current user.
32 | Future deleteUserFiles() async {
33 | final String userId = supabase.auth.currentUser!.id;
34 | final files = await bucket().list(path: userId);
35 | if (files.isEmpty) return;
36 |
37 | final filesPaths = files.map((file) => '$userId/${file.name}').toList();
38 | await bucket().remove(filesPaths);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/repositories/supabase.dart:
--------------------------------------------------------------------------------
1 | import 'package:supabase_flutter/supabase_flutter.dart';
2 |
3 | import 'const.dart';
4 |
5 | abstract class SupabaseRepository {
6 | final SupabaseClient supabase;
7 | final Tables? tableName;
8 | final Buckets? bucketName;
9 |
10 | const SupabaseRepository({
11 | required this.supabase,
12 | this.tableName,
13 | this.bucketName,
14 | });
15 | }
16 |
17 | mixin Postgrest on SupabaseRepository {
18 | PostgrestQueryBuilder table() => supabase.rest.from(tableName!.name);
19 | }
20 |
21 | mixin Storage on SupabaseRepository {
22 | StorageFileApi bucket() => supabase.storage.from(bucketName!.name);
23 | }
24 |
--------------------------------------------------------------------------------
/lib/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:go_router/go_router.dart';
2 |
3 | import 'go_router_builder.dart';
4 |
5 | final router = GoRouter(
6 | routes: $appRoutes,
7 | redirect: (context, state) {
8 | return state.uri.scheme.isNotEmpty ? HomeScreenRoute().location : null;
9 | },
10 | );
11 |
--------------------------------------------------------------------------------
/lib/selectors/auth.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | User? selectAuthUser(AppState state) => state.auth.user;
4 |
5 | final selectAuthUserId =
6 | createSelector1(selectAuthUser, (authUser) => authUser?.id);
7 |
--------------------------------------------------------------------------------
/lib/selectors/default_rules.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | Iterable selectAllDefaultRules(AppState state) =>
4 | state.defaultRules.entities.values;
5 |
--------------------------------------------------------------------------------
/lib/selectors/feedback.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | bool selectHasSeenFeedbackCard(AppState state) =>
4 | state.hasSeenFeedbackCard ?? false;
5 |
6 | final selectShouldShowFeedbackCard = createSelector2(
7 | selectHasSeenFeedbackCard,
8 | selectOwnProfile,
9 | (hasSeenFeedbackCard, ownProfile) =>
10 | !hasSeenFeedbackCard &&
11 | ownProfile?.createdAt?.isBefore(
12 | DateTime.now().subtract(const Duration(days: 3)),
13 | ) ==
14 | true,
15 | );
16 |
--------------------------------------------------------------------------------
/lib/selectors/groups.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | Map selectGroupEntities(AppState state) => state.groups.entities;
4 |
5 | final selectGroup = createSelector2(
6 | selectGroupId, selectGroupEntities, (id, entities) => entities[id]);
7 |
--------------------------------------------------------------------------------
/lib/selectors/locale.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | /// Provide the user-configured locale.
4 | Locale? localeSelector(AppState state) {
5 | final locale = state.locale;
6 |
7 | if (locale == null) return null;
8 |
9 | return Locale.fromSubtags(
10 | languageCode: locale.languageCode,
11 | scriptCode: locale.scriptCode,
12 | countryCode: locale.countryCode,
13 | );
14 | }
15 |
16 | /// Provide the recurrence rule localised text encoder.
17 | Future rruleL10nSelector(AppState state) {
18 | // TODO Support more locales
19 | // final locale = state.locale;
20 |
21 | return RruleL10nEn.create();
22 | }
23 |
--------------------------------------------------------------------------------
/lib/selectors/members.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | RemoteEntityState membersSelector(AppState state) => state.members;
4 |
5 | String? selectGroupId(AppState state) => state.selectedGroupId;
6 |
7 | final groupMembersSelector = createSelector2(
8 | membersSelector,
9 | selectGroupId,
10 | (members, groupId) => members.entities.values
11 | .where((member) => member.groupId.toString() == groupId)
12 | .sorted((a, b) => b.id.compareTo(a.id)));
13 |
14 | final groupMembersWithProfilesSelector = createSelector2(
15 | groupMembersSelector,
16 | profilesSelector,
17 | (members, profiles) =>
18 | members.map((member) => (member, profiles.entities[member.profileId])));
19 |
20 | final selectMyMember = createSelector2(
21 | groupMembersWithProfilesSelector,
22 | selectAuthUserId,
23 | (members, userId) =>
24 | members.firstWhereOrNull((m) => m.$1.profileId == userId));
25 |
26 | final selectIsAdmin =
27 | createSelector1(selectMyMember, (m) => m?.$1.role == GroupRoles.admin);
28 |
--------------------------------------------------------------------------------
/lib/selectors/profiles.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | RemoteEntityState profilesSelector(AppState state) => state.profiles;
4 |
5 | final selectOwnProfile = createSelector2(selectAuthUserId, profilesSelector,
6 | (authUserId, profiles) => profiles.entities[authUserId]);
7 |
--------------------------------------------------------------------------------
/lib/selectors/replies.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | Iterable selectAllReplies(AppState state) =>
4 | state.replies.entities.values;
5 |
--------------------------------------------------------------------------------
/lib/selectors/selected_date.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | DateTime selectedDateSelector(AppState state) => state.selectedDate;
4 |
5 | /// Provide the begin and end of day for the selected date.
6 | final selectedDateRangeSelector =
7 | createSelector1(selectedDateSelector, (date) => date.getDayRange());
8 |
--------------------------------------------------------------------------------
/lib/selectors/theme.dart:
--------------------------------------------------------------------------------
1 | part of 'selectors.dart';
2 |
3 | /// Provide the current theme mode.
4 | ThemeMode themeModeSelector(AppState state) => state.themeMode;
5 |
--------------------------------------------------------------------------------
/lib/state/auth_state.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart'; // ignore: unused_import
2 | import 'package:freezed_annotation/freezed_annotation.dart';
3 | import 'package:supabase/supabase.dart';
4 |
5 | part 'auth_state.freezed.dart';
6 | part 'auth_state.g.dart';
7 |
8 | enum AuthStatus { initial, authenticated, unauthenticated }
9 |
10 | @freezed
11 | sealed class AuthState with _$AuthState {
12 | const factory AuthState({
13 | required AuthStatus status,
14 | @UserJsonConverter() User? user,
15 | String? lastRoute,
16 | }) = _AuthState;
17 |
18 | factory AuthState.fromJson(Map json) =>
19 | _$AuthStateFromJson(json);
20 | }
21 |
22 | /// Converts a [User] object to and from JSON.
23 | class UserJsonConverter implements JsonConverter> {
24 | const UserJsonConverter();
25 |
26 | @override
27 | User fromJson(Map json) {
28 | final user = User.fromJson(json);
29 | if (user == null) {
30 | throw const FormatException('invalid user');
31 | }
32 | return user;
33 | }
34 |
35 | @override
36 | Map toJson(User object) => object.toJson();
37 | }
38 |
--------------------------------------------------------------------------------
/lib/state/auth_state.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'auth_state.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | _$AuthStateImpl _$$AuthStateImplFromJson(Map json) =>
10 | _$AuthStateImpl(
11 | status: $enumDecode(_$AuthStatusEnumMap, json['status']),
12 | user: _$JsonConverterFromJson