├── .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, User>( 13 | json['user'], const UserJsonConverter().fromJson), 14 | lastRoute: json['lastRoute'] as String?, 15 | ); 16 | 17 | Map _$$AuthStateImplToJson(_$AuthStateImpl instance) => 18 | { 19 | 'status': _$AuthStatusEnumMap[instance.status]!, 20 | 'user': _$JsonConverterToJson, User>( 21 | instance.user, const UserJsonConverter().toJson), 22 | 'lastRoute': instance.lastRoute, 23 | }; 24 | 25 | const _$AuthStatusEnumMap = { 26 | AuthStatus.initial: 'initial', 27 | AuthStatus.authenticated: 'authenticated', 28 | AuthStatus.unauthenticated: 'unauthenticated', 29 | }; 30 | 31 | Value? _$JsonConverterFromJson( 32 | Object? json, 33 | Value? Function(Json json) fromJson, 34 | ) => 35 | json == null ? null : fromJson(json as Json); 36 | 37 | Json? _$JsonConverterToJson( 38 | Value? value, 39 | Json? Function(Value value) toJson, 40 | ) => 41 | value == null ? null : toJson(value); 42 | -------------------------------------------------------------------------------- /lib/state/locale_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; // ignore: unused_import 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | part 'locale_state.freezed.dart'; 5 | part 'locale_state.g.dart'; 6 | 7 | @freezed 8 | sealed class LocaleState with _$LocaleState { 9 | const factory LocaleState({ 10 | required String languageCode, 11 | String? scriptCode, 12 | String? countryCode, 13 | }) = _LocaleState; 14 | 15 | factory LocaleState.fromJson(Map json) => 16 | _$LocaleStateFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/state/locale_state.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'locale_state.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$LocaleStateImpl _$$LocaleStateImplFromJson(Map json) => 10 | _$LocaleStateImpl( 11 | languageCode: json['languageCode'] as String, 12 | scriptCode: json['scriptCode'] as String?, 13 | countryCode: json['countryCode'] as String?, 14 | ); 15 | 16 | Map _$$LocaleStateImplToJson(_$LocaleStateImpl instance) => 17 | { 18 | 'languageCode': instance.languageCode, 19 | 'scriptCode': instance.scriptCode, 20 | 'countryCode': instance.countryCode, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/state/state.dart: -------------------------------------------------------------------------------- 1 | export 'auth_state.dart'; 2 | export 'locale_state.dart'; 3 | export 'app_state.dart'; 4 | -------------------------------------------------------------------------------- /lib/util/analytics.dart: -------------------------------------------------------------------------------- 1 | import 'package:posthog_flutter/posthog_flutter.dart'; 2 | import 'package:sentry_flutter/sentry_flutter.dart'; 3 | 4 | /// Initialize PostHog 5 | Future initPostHog() async { 6 | final config = 7 | PostHogConfig('phc_maDGNEUn4Xpo0Nys1jNI2GnZjEQTVD2IIymbcRxBPta'); 8 | config.host = 'https://eu.i.posthog.com'; 9 | config.sendFeatureFlagEvents = false; 10 | config.preloadFeatureFlags = false; 11 | final posthog = Posthog(); 12 | await posthog.setup(config); 13 | } 14 | 15 | /// Initialize Sentry 16 | Future initSentry(AppRunner? appRunner) async { 17 | await SentryFlutter.init( 18 | (options) { 19 | options.dsn = 20 | 'https://377e317b8978ea82dfbc3980ba960339@o4508580592484352.ingest.de.sentry.io/4508580610572368'; 21 | options.tracesSampleRate = 0.5; 22 | options.profilesSampleRate = 0.5; 23 | }, 24 | appRunner: appRunner, 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /lib/util/base32.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | const _crockford = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; 4 | 5 | /// A [TextInputFormatter] that formats input as a Crockford Base32 string, 6 | /// replacing visually similar characters and adding a hyphen in the middle. 7 | class Base32TextInputFormatter implements TextInputFormatter { 8 | @override 9 | TextEditingValue formatEditUpdate( 10 | TextEditingValue oldValue, TextEditingValue newValue) { 11 | final normalised = cleanBase32Code(newValue.text); 12 | final formatted = formatBase32Code(normalised); 13 | 14 | return TextEditingValue( 15 | text: formatted, 16 | selection: TextSelection.collapsed(offset: formatted.length), 17 | ); 18 | } 19 | } 20 | 21 | String cleanBase32Code(String code) { 22 | return code 23 | .toUpperCase() 24 | .replaceAll(RegExp(r'O'), '0') 25 | .replaceAll(RegExp(r'[IL]'), '1') 26 | .split('') 27 | .where((char) => _crockford.contains(char)) 28 | .join(); 29 | } 30 | 31 | String formatBase32Code(String code) { 32 | return code.length > 4 33 | ? "${code.substring(0, 4)}-${code.substring(4)}" 34 | : code; 35 | } 36 | -------------------------------------------------------------------------------- /lib/util/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/painting.dart'; 2 | 3 | /// Returns a color based on the hash code of the given object. 4 | Color getColorFromHashCode(Object object) => 5 | HSLColor.fromAHSL(1, object.hashCode % 360, 0.2, 0.5).toColor(); 6 | -------------------------------------------------------------------------------- /lib/util/config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | 7 | part 'config.freezed.dart'; 8 | part 'config.g.dart'; 9 | 10 | class ConfigService { 11 | static final ConfigService _instance = ConfigService._internal(); 12 | late Config config; 13 | 14 | ConfigService._internal(); 15 | 16 | factory ConfigService() { 17 | return _instance; 18 | } 19 | 20 | Future initialize() async { 21 | const env = kReleaseMode ? 'supabase' : 'local'; 22 | final envConfigFile = await rootBundle.loadString('assets/env/$env.json'); 23 | final config = 24 | Config.fromJson(json.decode(envConfigFile) as Map); 25 | this.config = config; 26 | } 27 | } 28 | 29 | @freezed 30 | sealed class Config with _$Config { 31 | const factory Config({ 32 | required String supabaseConfigPath, 33 | required String socialAuthWebClientId, 34 | required String socialAuthIosClientId, 35 | }) = _Config; 36 | 37 | factory Config.fromJson(Map json) => _$ConfigFromJson(json); 38 | } 39 | -------------------------------------------------------------------------------- /lib/util/config.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'config.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$ConfigImpl _$$ConfigImplFromJson(Map json) => _$ConfigImpl( 10 | supabaseConfigPath: json['supabaseConfigPath'] as String, 11 | socialAuthWebClientId: json['socialAuthWebClientId'] as String, 12 | socialAuthIosClientId: json['socialAuthIosClientId'] as String, 13 | ); 14 | 15 | Map _$$ConfigImplToJson(_$ConfigImpl instance) => 16 | { 17 | 'supabaseConfigPath': instance.supabaseConfigPath, 18 | 'socialAuthWebClientId': instance.socialAuthWebClientId, 19 | 'socialAuthIosClientId': instance.socialAuthIosClientId, 20 | }; 21 | -------------------------------------------------------------------------------- /lib/util/datetime.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | export 'package:flutter/material.dart' show DateTimeRange; 4 | 5 | extension DayRange on DateTime { 6 | DateTime getDayStart() { 7 | return copyWith( 8 | hour: 0, 9 | minute: 0, 10 | second: 0, 11 | millisecond: 0, 12 | microsecond: 0, 13 | isUtc: true, 14 | ); 15 | } 16 | 17 | DateTime getDayEnd() { 18 | return copyWith( 19 | hour: 23, 20 | minute: 59, 21 | second: 59, 22 | millisecond: 999, 23 | microsecond: 999, 24 | isUtc: true, 25 | ); 26 | } 27 | 28 | DateTimeRange getDayRange() { 29 | final start = getDayStart(); 30 | final end = getDayEnd(); 31 | 32 | return DateTimeRange(start: start, end: end); 33 | } 34 | 35 | DateTimeRange getNdayRange(int n) { 36 | final start = getDayStart().add(Duration(days: -n)); 37 | final end = getDayEnd().add(Duration(days: n)); 38 | 39 | return DateTimeRange(start: start, end: end); 40 | } 41 | } 42 | 43 | extension DateTimeRangeCompare on DateTimeRange { 44 | bool contains(DateTime dateTime) { 45 | return start.isBefore(dateTime) && end.isAfter(dateTime); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/util/fakes.dart: -------------------------------------------------------------------------------- 1 | import 'package:faker/faker.dart'; 2 | import 'package:parousia/models/models.dart'; 3 | import 'package:parousia/util/recurrence_rules.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | /// Gnenerate fake data for testing 7 | class Fake { 8 | static Profile profile() => Profile( 9 | id: const Uuid().v4(), 10 | displayName: faker.person.name(), 11 | picture: faker.image.loremPicsum(height: 128, width: 128), 12 | ); 13 | 14 | static Group group() => Group( 15 | id: const Uuid().v7(), 16 | displayName: faker.company.name(), 17 | description: faker.lorem.sentence(), 18 | picture: faker.image.loremPicsum(height: 512, width: 512), 19 | ); 20 | 21 | static Schedule schedule({String? groupId}) => Schedule( 22 | id: const Uuid().v7(), 23 | groupId: groupId ?? const Uuid().v7(), 24 | displayName: faker.lorem.sentence(), 25 | recurrenceRule: CommonRecurrenceRules.daily, 26 | startDate: DateTime.now(), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /lib/util/names.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// Convert a name into upper case initials 4 | String? getNameInitials(String? name) => name 5 | ?.split(' ') 6 | .map((e) => e.isNotEmpty ? e.characters.first : '') 7 | .take(3) 8 | .join('') 9 | .toUpperCase(); 10 | -------------------------------------------------------------------------------- /lib/util/recurrence_rules.dart: -------------------------------------------------------------------------------- 1 | import 'package:rrule/rrule.dart'; 2 | 3 | class CommonRecurrenceRules { 4 | static final RecurrenceRule once = RecurrenceRule( 5 | frequency: Frequency.daily, 6 | count: 1, 7 | ); 8 | 9 | static final RecurrenceRule daily = RecurrenceRule( 10 | frequency: Frequency.daily, 11 | interval: 1, 12 | ); 13 | 14 | static final RecurrenceRule weekly = RecurrenceRule( 15 | frequency: Frequency.weekly, 16 | interval: 1, 17 | ); 18 | 19 | static final RecurrenceRule monthly = RecurrenceRule( 20 | frequency: Frequency.monthly, 21 | interval: 1, 22 | ); 23 | 24 | static final RecurrenceRule yearly = RecurrenceRule( 25 | frequency: Frequency.yearly, 26 | interval: 1, 27 | ); 28 | 29 | static final RecurrenceRule weekdays = RecurrenceRule( 30 | frequency: Frequency.weekly, 31 | interval: 1, 32 | byWeekDays: [ 33 | ByWeekDayEntry(DateTime.monday), 34 | ByWeekDayEntry(DateTime.tuesday), 35 | ByWeekDayEntry(DateTime.wednesday), 36 | ByWeekDayEntry(DateTime.thursday), 37 | ByWeekDayEntry(DateTime.friday), 38 | ], 39 | ); 40 | 41 | static final RecurrenceRule weekends = RecurrenceRule( 42 | frequency: Frequency.weekly, 43 | interval: 1, 44 | byWeekDays: [ 45 | ByWeekDayEntry(DateTime.saturday), 46 | ByWeekDayEntry(DateTime.sunday), 47 | ], 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /lib/util/supabase_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/foundation.dart'; // ignore: unused_import 5 | import 'package:freezed_annotation/freezed_annotation.dart'; 6 | 7 | part 'supabase_config.freezed.dart'; 8 | part 'supabase_config.g.dart'; 9 | 10 | @freezed 11 | class SupabaseConfig with _$SupabaseConfig { 12 | @JsonSerializable(fieldRename: FieldRename.screamingSnake) 13 | const factory SupabaseConfig({ 14 | required String anonKey, 15 | required String apiUrl, 16 | String? dbUrl, 17 | String? graphqlUrl, 18 | String? inbucketUrl, 19 | String? jwtSecret, 20 | String? serviceRoleKey, 21 | String? studioUrl, 22 | }) = _SupabaseConfig; 23 | 24 | factory SupabaseConfig.fromJson(Map json) => 25 | _$SupabaseConfigFromJson(json); 26 | 27 | factory SupabaseConfig.fromString(String jsonString) => 28 | SupabaseConfig.fromJson(jsonDecode(jsonString)); 29 | 30 | factory SupabaseConfig.fromFile(File file) => 31 | SupabaseConfig.fromString(file.readAsStringSync()); 32 | 33 | factory SupabaseConfig.fromPath(String path) => 34 | SupabaseConfig.fromFile(File(path)); 35 | } 36 | -------------------------------------------------------------------------------- /lib/util/supabase_config.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'supabase_config.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$SupabaseConfigImpl _$$SupabaseConfigImplFromJson(Map json) => 10 | _$SupabaseConfigImpl( 11 | anonKey: json['ANON_KEY'] as String, 12 | apiUrl: json['API_URL'] as String, 13 | dbUrl: json['DB_URL'] as String?, 14 | graphqlUrl: json['GRAPHQL_URL'] as String?, 15 | inbucketUrl: json['INBUCKET_URL'] as String?, 16 | jwtSecret: json['JWT_SECRET'] as String?, 17 | serviceRoleKey: json['SERVICE_ROLE_KEY'] as String?, 18 | studioUrl: json['STUDIO_URL'] as String?, 19 | ); 20 | 21 | Map _$$SupabaseConfigImplToJson( 22 | _$SupabaseConfigImpl instance) => 23 | { 24 | 'ANON_KEY': instance.anonKey, 25 | 'API_URL': instance.apiUrl, 26 | 'DB_URL': instance.dbUrl, 27 | 'GRAPHQL_URL': instance.graphqlUrl, 28 | 'INBUCKET_URL': instance.inbucketUrl, 29 | 'JWT_SECRET': instance.jwtSecret, 30 | 'SERVICE_ROLE_KEY': instance.serviceRoleKey, 31 | 'STUDIO_URL': instance.studioUrl, 32 | }; 33 | -------------------------------------------------------------------------------- /lib/util/util.dart: -------------------------------------------------------------------------------- 1 | export 'analytics.dart'; 2 | export 'base32.dart'; 3 | export 'colors.dart'; 4 | export 'datetime.dart'; 5 | export 'fakes.dart'; 6 | export 'names.dart'; 7 | export 'recurrence_rules.dart'; 8 | export 'supabase_config.dart'; 9 | -------------------------------------------------------------------------------- /lib/widgetbook.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | import 'package:widgetbook/widgetbook.dart'; 4 | import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; 5 | 6 | import 'widgetbook.directories.g.dart'; 7 | 8 | void main() { 9 | runApp(const WidgetbookApp()); 10 | } 11 | 12 | @widgetbook.App() 13 | class WidgetbookApp extends StatelessWidget { 14 | const WidgetbookApp({super.key}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Widgetbook.material( 19 | directories: directories, 20 | addons: [ 21 | AccessibilityAddon(), 22 | AlignmentAddon(), 23 | DeviceFrameAddon(devices: Devices.all), 24 | LocalizationAddon( 25 | locales: AppLocalizations.supportedLocales, 26 | localizationsDelegates: AppLocalizations.localizationsDelegates, 27 | initialLocale: const Locale('en'), 28 | ), 29 | MaterialThemeAddon( 30 | themes: [ 31 | WidgetbookTheme(name: 'Light', data: ThemeData.light()), 32 | WidgetbookTheme(name: 'Dark', data: ThemeData.dark()), 33 | ], 34 | ), 35 | InspectorAddon(), 36 | ], 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/widgetbook.directories.g.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // ignore_for_file: type=lint 3 | // ignore_for_file: unused_import, prefer_relative_imports, directives_ordering 4 | 5 | // GENERATED CODE - DO NOT MODIFY BY HAND 6 | 7 | // ************************************************************************** 8 | // AppGenerator 9 | // ************************************************************************** 10 | 11 | // ignore_for_file: no_leading_underscores_for_library_prefixes 12 | import 'package:parousia/presentation/widgets/group_form.stories.dart' as _i2; 13 | import 'package:widgetbook/widgetbook.dart' as _i1; 14 | 15 | final directories = <_i1.WidgetbookNode>[ 16 | _i1.WidgetbookFolder( 17 | name: 'presentation', 18 | children: [ 19 | _i1.WidgetbookFolder( 20 | name: 'widgets', 21 | children: [ 22 | _i1.WidgetbookComponent( 23 | name: 'GroupForm', 24 | useCases: [ 25 | _i1.WidgetbookUseCase( 26 | name: 'empty', 27 | builder: _i2.emptyUseCase, 28 | ), 29 | _i1.WidgetbookUseCase( 30 | name: 'with data', 31 | builder: _i2.withDataUseCase, 32 | ), 33 | ], 34 | ) 35 | ], 36 | ) 37 | ], 38 | ) 39 | ]; 40 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void fl_register_plugins(FlPluginRegistry* registry) { 15 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 16 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 17 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 18 | g_autoptr(FlPluginRegistrar) gtk_registrar = 19 | fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); 20 | gtk_plugin_register_with_registrar(gtk_registrar); 21 | g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = 22 | fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); 23 | sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); 24 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 25 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 26 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 27 | } 28 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_linux 7 | gtk 8 | sentry_flutter 9 | url_launcher_linux 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.15' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = parousia 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = is.giorgio.app.parousia 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 is.giorgio.app. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.device.camera 10 | 11 | com.apple.security.files.user-selected.read-only 12 | 13 | com.apple.security.network.client 14 | 15 | com.apple.security.network.server 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | com.posthog.posthog.AUTO_INIT 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.camera 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /supabase/.branches/_current_branch: -------------------------------------------------------------------------------- 1 | main -------------------------------------------------------------------------------- /supabase/config/supabase.json: -------------------------------------------------------------------------------- 1 | { 2 | "ANON_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdxeXd1YXF5d3RjemZocGNubHFtIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODg3NjIzMTEsImV4cCI6MjAwNDMzODMxMX0.m5Yg9Qu3xMzOFTA4cqxcen3ndOG2MVtWqbItK3r1al4", 3 | "API_URL": "https://gqywuaqywtczfhpcnlqm.supabase.co" 4 | } 5 | -------------------------------------------------------------------------------- /supabase/functions/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["denoland.vscode-deno"] 3 | } 4 | -------------------------------------------------------------------------------- /supabase/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "editor.defaultFormatter": "denoland.vscode-deno" 5 | } 6 | -------------------------------------------------------------------------------- /supabase/functions/_shared/cors.ts: -------------------------------------------------------------------------------- 1 | // https://supabase.com/docs/guides/functions/cors 2 | 3 | // When you're building a Supabase Function, you'll need to handle CORS headers. 4 | // This is a common pattern for serverless functions, and it's important to ensure that your function can be invoked from a browser. 5 | export const corsHeaders = { 6 | "Access-Control-Allow-Origin": "*", 7 | "Access-Control-Allow-Headers": 8 | "authorization, x-client-info, apikey, content-type", 9 | }; 10 | -------------------------------------------------------------------------------- /supabase/migrations/20230706000000_dbdev.sql: -------------------------------------------------------------------------------- 1 | create extension if not exists http with schema extensions; 2 | create extension if not exists pg_tle; 3 | drop extension if exists "supabase-dbdev"; 4 | select pgtle.uninstall_extension_if_exists('supabase-dbdev'); 5 | select 6 | pgtle.install_extension( 7 | 'supabase-dbdev', 8 | resp.contents ->> 'version', 9 | 'PostgreSQL package manager', 10 | resp.contents ->> 'sql' 11 | ) 12 | from http( 13 | ( 14 | 'GET', 15 | 'https://api.database.dev/rest/v1/' 16 | || 'package_versions?select=sql,version' 17 | || '&package_name=eq.supabase-dbdev' 18 | || '&order=version.desc' 19 | || '&limit=1', 20 | array[ 21 | ('apiKey', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhtdXB0cHBsZnZpaWZyYndtbXR2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODAxMDczNzIsImV4cCI6MTk5NTY4MzM3Mn0.z2CN0mvO2No8wSi46Gw59DFGCTJrzM0AQKsu_5k134s')::http_header 22 | ], 23 | null, 24 | null 25 | ) 26 | ) x, 27 | lateral ( 28 | select 29 | ((row_to_json(x) -> 'content') #>> '{}')::json -> 0 30 | ) resp(contents); 31 | create extension "supabase-dbdev"; 32 | select dbdev.install('supabase-dbdev'); 33 | drop extension if exists "supabase-dbdev"; 34 | create extension "supabase-dbdev"; 35 | 36 | -- Install the pg_idkit extension 37 | drop extension if exists "kiwicopple-pg_idkit"; 38 | select dbdev.install('kiwicopple-pg_idkit'); 39 | create extension "kiwicopple-pg_idkit"; 40 | -------------------------------------------------------------------------------- /supabase/migrations/20231101184949_buckets.sql: -------------------------------------------------------------------------------- 1 | -- Configure a couple of buckets for public and private files. 2 | insert into storage.buckets 3 | (id, name, public) 4 | values ('public', 'public', true); 5 | -- ('private', 'private', false); 6 | 7 | create policy "Allow authenticated uploads to public" 8 | on storage.objects 9 | for insert 10 | to authenticated 11 | with check ( 12 | bucket_id = 'public' and 13 | ((storage.foldername(name))[1] = auth.uid()::text) 14 | ); 15 | 16 | create policy "Allow users to read their own public files" 17 | on storage.objects 18 | for select 19 | to authenticated 20 | using (owner_id = auth.uid()::text); 21 | 22 | create policy "Allow users to delete their own public files" 23 | on storage.objects 24 | for delete 25 | to authenticated 26 | using (owner_id = auth.uid()::text); 27 | 28 | 29 | -- create policy "Allow authenticated uploads and reads to private" 30 | -- on storage.objects 31 | -- for insert 32 | -- to authenticated 33 | -- with check ( 34 | -- bucket_id = 'private' and 35 | -- ((storage.foldername(name))[1] = auth.uid()::text) 36 | -- ); 37 | 38 | -- TODO read policy 39 | -- create policy "Allow users to read their own private files" 40 | -- on storage.objects for select 41 | -- to authenticated 42 | -- using (owner_id = auth.uid()::text); 43 | -------------------------------------------------------------------------------- /supabase/seed.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/supabase/seed.sql -------------------------------------------------------------------------------- /supabase/tests/groups_test.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | select plan(5); 3 | 4 | -- call auth.login_as_user('giorgio.azzinnaro@gmail.com'); 5 | 6 | -- Test case 1: Test if create_group returns a valid group_id 7 | select ok( 8 | create_group('test group') is not null, 9 | 'create_group should return a valid group_id' 10 | ); 11 | 12 | -- Test case 2: Test if the group exists in the groups table 13 | select is( 14 | (select count(*) from groups where display_name = 'test group'), 15 | 1, 16 | 'The group should exist in the groups table' 17 | ); 18 | 19 | -- Test case 3: Test if a member exists in the members table 20 | select is( 21 | (select count(*) 22 | from members 23 | where group_id = (select id from groups where display_name = 'test group') 24 | and role_id = 1), 25 | 1, 26 | 'A member with role_id 1 should exist in the members table for the created group' 27 | ); 28 | 29 | select ok( 30 | create_group('test group') is null, 31 | 'create_group should return null if the group already exists' 32 | ); 33 | 34 | commit; 35 | -------------------------------------------------------------------------------- /supabase/tests/rls_test.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | SELECT plan(1); 3 | 4 | -- Examples: https://pgtap.org/documentation.html 5 | 6 | SELECT has_column( 7 | 'auth', 8 | 'users', 9 | 'id', 10 | 'id should exist' 11 | ); 12 | 13 | SELECT * 14 | FROM finish(); 15 | ROLLBACK; 16 | -------------------------------------------------------------------------------- /supabase/util/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "appforit-util" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Giorgio Azzinnaro "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.13" 10 | google-cloud-firestore = "^2.19.0" 11 | supabase = "^2.10.0" 12 | pandas = "^2.2.3" 13 | 14 | 15 | [build-system] 16 | requires = ["poetry-core"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /test/remote_entities_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:parousia/models/models.dart'; 2 | import 'package:parousia/reducers/remote_entities.dart'; 3 | import 'package:parousia/util/util.dart'; 4 | import 'package:redux_entity/redux_entity.dart'; 5 | import 'package:test/test.dart'; 6 | 7 | import 'remote_entity_reducer_tester.dart'; 8 | 9 | void main() { 10 | group(profilesReducer, () { 11 | generator() => Fake.profile(); 12 | 13 | final tester = RemoteEntityReducerTester< 14 | RemoteEntityReducer, Profile>, 15 | RemoteEntityState, 16 | Profile>(); 17 | 18 | return tester.testAll( 19 | profilesReducer, generator, const RemoteEntityState()); 20 | }); 21 | 22 | group(groupsReducer, () { 23 | generator() => Fake.group(); 24 | 25 | final tester = RemoteEntityReducerTester< 26 | RemoteEntityReducer, Group>, 27 | RemoteEntityState, 28 | Group>(); 29 | 30 | return tester.testAll( 31 | groupsReducer, generator, const RemoteEntityState()); 32 | }); 33 | 34 | group(schedulesReducer, () { 35 | generator() => Fake.schedule(); 36 | 37 | final tester = RemoteEntityReducerTester< 38 | RemoteEntityReducer, Schedule>, 39 | RemoteEntityState, 40 | Schedule>(); 41 | 42 | return tester.testAll( 43 | schedulesReducer, generator, const RemoteEntityState()); 44 | }); 45 | 46 | // TODO tests for nested entities 47 | } 48 | -------------------------------------------------------------------------------- /test/util_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:parousia/util/util.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('getNameInitials', () { 6 | test('should return the initials of a name', 7 | () => expect(getNameInitials('John Doe'), 'JD')); 8 | 9 | test('should support simple emojis', 10 | () => expect(getNameInitials('👨 Test'), '👨T')); 11 | 12 | test('should support complex emojis', 13 | () => expect(getNameInitials('👨‍👩‍👧‍👦 Ciao'), '👨‍👩‍👧‍👦C')); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /web/.well-known/apple-app-site-association: -------------------------------------------------------------------------------- 1 | { 2 | "applinks": { 3 | "apps": [], 4 | "details": [ 5 | { 6 | "appIDs": [ 7 | "D5Q258YWK7.is.giorgio.app.parousia" 8 | ], 9 | "paths": [ 10 | "*" 11 | ], 12 | "components": [ 13 | { 14 | "/": "/*" 15 | } 16 | ] 17 | } 18 | ] 19 | }, 20 | "webcredentials": { 21 | "apps": [ 22 | "D5Q258YWK7.is.giorgio.app.parousia" 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/.well-known/assetlinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "relation": ["delegate_permission/common.handle_all_urls"], 4 | "target": { 5 | "namespace": "android_app", 6 | "package_name": "is.giorgio.app.parousia", 7 | "sha256_cert_fingerprints": 8 | ["02:A1:58:46:37:61:E8:D3:41:95:03:59:18:19:C6:51:7B:53:07:8D:0F:A5:D4:6F:D3:AF:C9:8F:7E:81:54:1B"] 9 | } 10 | } 11 | ] -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GRUP", 3 | "short_name": "AFIT", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#FFFFFF", 7 | "theme_color": "#34558B", 8 | "description": "Group Scheduling Made Simple", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /web/splash/img/dark-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/splash/img/dark-1x.png -------------------------------------------------------------------------------- /web/splash/img/dark-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/splash/img/dark-2x.png -------------------------------------------------------------------------------- /web/splash/img/dark-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/splash/img/dark-3x.png -------------------------------------------------------------------------------- /web/splash/img/dark-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/splash/img/dark-4x.png -------------------------------------------------------------------------------- /web/splash/img/light-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/splash/img/light-1x.png -------------------------------------------------------------------------------- /web/splash/img/light-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/splash/img/light-2x.png -------------------------------------------------------------------------------- /web/splash/img/light-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/splash/img/light-3x.png -------------------------------------------------------------------------------- /web/splash/img/light-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/web/splash/img/light-4x.png -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void RegisterPlugins(flutter::PluginRegistry* registry) { 16 | AppLinksPluginCApiRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("AppLinksPluginCApi")); 18 | FileSelectorWindowsRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 20 | SentryFlutterPluginRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("SentryFlutterPlugin")); 22 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 24 | UrlLauncherWindowsRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 26 | } 27 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | app_links 7 | file_selector_windows 8 | sentry_flutter 9 | share_plus 10 | url_launcher_windows 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"parousia", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruprsvp/grup/d6604e0093e627bcf680db25b824dd1ae39236cd/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------