Showing preview only (1,372K chars total). Download the full file or copy to clipboard to get everything.
Repository: magweter/spacepad
Branch: main
Commit: 215510b0ea9c
Files: 461
Total size: 1.2 MB
Directory structure:
gitextract_qzg9plm7/
├── .github/
│ └── workflows/
│ ├── docker-build.yml
│ └── tests.yml
├── .gitignore
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE.md
├── LICENSE_PRO.md
├── README.md
├── app/
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── android/
│ │ ├── .gitignore
│ │ ├── app/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ ├── debug/
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── kotlin/
│ │ │ │ │ └── com/
│ │ │ │ │ └── magweter/
│ │ │ │ │ └── spacepad/
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── res/
│ │ │ │ ├── drawable/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values/
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night/
│ │ │ │ └── styles.xml
│ │ │ └── profile/
│ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle.kts
│ │ ├── gradle/
│ │ │ └── wrapper/
│ │ │ └── gradle-wrapper.properties
│ │ ├── gradle.properties
│ │ └── settings.gradle.kts
│ ├── ios/
│ │ ├── .gitignore
│ │ ├── Flutter/
│ │ │ ├── AppFrameworkInfo.plist
│ │ │ ├── Debug.xcconfig
│ │ │ └── Release.xcconfig
│ │ ├── Podfile
│ │ ├── Runner/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── LaunchImage.imageset/
│ │ │ │ ├── Contents.json
│ │ │ │ └── README.md
│ │ │ ├── Base.lproj/
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ └── Runner-Bridging-Header.h
│ │ ├── 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
│ │ └── RunnerTests/
│ │ └── RunnerTests.swift
│ ├── lib/
│ │ ├── components/
│ │ │ ├── action_button.dart
│ │ │ ├── action_panel.dart
│ │ │ ├── admin_actions.dart
│ │ │ ├── authenticated_background.dart
│ │ │ ├── authenticated_image.dart
│ │ │ ├── calendar_modal.dart
│ │ │ ├── custom_booking_modal.dart
│ │ │ ├── event_line.dart
│ │ │ ├── frosted_panel.dart
│ │ │ ├── solid_button.dart
│ │ │ ├── spinner.dart
│ │ │ └── toast.dart
│ │ ├── controllers/
│ │ │ ├── dashboard_controller.dart
│ │ │ ├── display_controller.dart
│ │ │ └── login_controller.dart
│ │ ├── date_format_helper.dart
│ │ ├── exceptions/
│ │ │ └── api_exception.dart
│ │ ├── main.dart
│ │ ├── models/
│ │ │ ├── device_model.dart
│ │ │ ├── display_data_model.dart
│ │ │ ├── display_model.dart
│ │ │ ├── display_settings_model.dart
│ │ │ ├── event_model.dart
│ │ │ ├── event_status.dart
│ │ │ └── user_model.dart
│ │ ├── pages/
│ │ │ ├── dashboard_page.dart
│ │ │ ├── display_page.dart
│ │ │ ├── login_page.dart
│ │ │ └── splash_page.dart
│ │ ├── services/
│ │ │ ├── api_service.dart
│ │ │ ├── auth_service.dart
│ │ │ ├── device_service.dart
│ │ │ ├── display_service.dart
│ │ │ ├── font_service.dart
│ │ │ └── server_service.dart
│ │ ├── theme.dart
│ │ └── translations/
│ │ └── translations.dart
│ ├── pubspec.yaml
│ └── test/
│ └── widget_test.dart
├── backend/
│ ├── .editorconfig
│ ├── .gitattributes
│ ├── .gitignore
│ ├── Dockerfile
│ ├── FARO_SETUP.md
│ ├── README.md
│ ├── WORKSPACE_SETUP.md
│ ├── app/
│ │ ├── Console/
│ │ │ └── Commands/
│ │ │ ├── CheckMarketingTriggers.php
│ │ │ ├── CleanupExpiredEvents.php
│ │ │ ├── RenewEventSubscriptions.php
│ │ │ ├── SendHeartbeat.php
│ │ │ ├── TriggerRegistrationWebhookForMissingNames.php
│ │ │ ├── UpdateLemonSqueezySubscriptions.php
│ │ │ └── ValidateLicense.php
│ │ ├── Data/
│ │ │ ├── CalendarWebhookData.php
│ │ │ ├── DisplayWebhookData.php
│ │ │ ├── InstanceData.php
│ │ │ ├── LicenseData.php
│ │ │ ├── OrderWebhookData.php
│ │ │ ├── PermissionResult.php
│ │ │ ├── UserData.php
│ │ │ └── UserWebhookData.php
│ │ ├── Enums/
│ │ │ ├── AccountStatus.php
│ │ │ ├── DisplayStatus.php
│ │ │ ├── EventSource.php
│ │ │ ├── EventStatus.php
│ │ │ ├── GoogleBookingMethod.php
│ │ │ ├── OAuthDriver.php
│ │ │ ├── PermissionType.php
│ │ │ ├── Plan.php
│ │ │ ├── Provider.php
│ │ │ ├── UsageType.php
│ │ │ ├── UserStatus.php
│ │ │ └── WorkspaceRole.php
│ │ ├── Events/
│ │ │ ├── TrialExpiredOrCancelled.php
│ │ │ ├── UserActivatedAfter24h.php
│ │ │ ├── UserInactive.php
│ │ │ ├── UserNotActivatedAfter24h.php
│ │ │ ├── UserOnboarded.php
│ │ │ ├── UserPassive.php
│ │ │ └── UserRegistered.php
│ │ ├── Exceptions/
│ │ │ └── Handler.php
│ │ ├── Helpers/
│ │ │ ├── DisplaySettings.php
│ │ │ └── Settings.php
│ │ ├── Http/
│ │ │ ├── Controllers/
│ │ │ │ ├── API/
│ │ │ │ │ ├── ApiController.php
│ │ │ │ │ ├── Auth/
│ │ │ │ │ │ └── AuthController.php
│ │ │ │ │ ├── Cloud/
│ │ │ │ │ │ └── InstanceController.php
│ │ │ │ │ ├── DeviceController.php
│ │ │ │ │ ├── DisplayController.php
│ │ │ │ │ └── EventController.php
│ │ │ │ ├── AdminController.php
│ │ │ │ ├── Auth/
│ │ │ │ │ ├── AuthController.php
│ │ │ │ │ ├── GoogleController.php
│ │ │ │ │ ├── LoginController.php
│ │ │ │ │ ├── MicrosoftController.php
│ │ │ │ │ ├── RegisterController.php
│ │ │ │ │ └── SocialAuthController.php
│ │ │ │ ├── BoardController.php
│ │ │ │ ├── CalDAVAccountsController.php
│ │ │ │ ├── CalendarController.php
│ │ │ │ ├── Controller.php
│ │ │ │ ├── DashboardController.php
│ │ │ │ ├── DisplayController.php
│ │ │ │ ├── DisplaySettingsController.php
│ │ │ │ ├── GoogleAccountsController.php
│ │ │ │ ├── GoogleWebhookController.php
│ │ │ │ ├── LicenseController.php
│ │ │ │ ├── OnboardingController.php
│ │ │ │ ├── OutlookAccountsController.php
│ │ │ │ ├── OutlookWebhookController.php
│ │ │ │ ├── RoomController.php
│ │ │ │ ├── UsageController.php
│ │ │ │ └── WorkspaceController.php
│ │ │ ├── Middleware/
│ │ │ │ ├── CheckUserActive.php
│ │ │ │ ├── CheckUserOnboarding.php
│ │ │ │ └── UpdateLastActivity.php
│ │ │ ├── Requests/
│ │ │ │ ├── API/
│ │ │ │ │ ├── Auth/
│ │ │ │ │ │ └── LoginRequest.php
│ │ │ │ │ ├── BookEventRequest.php
│ │ │ │ │ ├── ChangeDisplayRequest.php
│ │ │ │ │ ├── InstanceHeartbeatRequest.php
│ │ │ │ │ └── ValidateInstanceRequest.php
│ │ │ │ ├── ActivateLicenseRequest.php
│ │ │ │ ├── Auth/
│ │ │ │ │ ├── LoginRequest.php
│ │ │ │ │ ├── OAuth2TokenRequest.php
│ │ │ │ │ └── RegisterRequest.php
│ │ │ │ ├── CreateBoardRequest.php
│ │ │ │ ├── CreateDisplayRequest.php
│ │ │ │ ├── UpdateBoardRequest.php
│ │ │ │ └── UpdateDisplayCustomizationRequest.php
│ │ │ └── Resources/
│ │ │ └── API/
│ │ │ ├── DeviceResource.php
│ │ │ ├── DisplayDataResource.php
│ │ │ ├── DisplayResource.php
│ │ │ ├── DisplaySettingsResource.php
│ │ │ ├── EventResource.php
│ │ │ └── UserResource.php
│ │ ├── Infrastructure/
│ │ │ └── Cloud/
│ │ │ └── LicenseService.php
│ │ ├── Listeners/
│ │ │ ├── ActivateUser.php
│ │ │ ├── SendOnboardingCompleteNotification.php
│ │ │ ├── SendOrderCreatedNotification.php
│ │ │ ├── SendRegistrationNotification.php
│ │ │ ├── SendTrialExpiredOrCancelledNotification.php
│ │ │ ├── SendUserActivatedAfter24hNotification.php
│ │ │ ├── SendUserInactiveNotification.php
│ │ │ ├── SendUserNotActivatedAfter24hNotification.php
│ │ │ └── SendUserPassiveNotification.php
│ │ ├── Models/
│ │ │ ├── Board.php
│ │ │ ├── CalDAVAccount.php
│ │ │ ├── Calendar.php
│ │ │ ├── Device.php
│ │ │ ├── Display.php
│ │ │ ├── DisplaySetting.php
│ │ │ ├── Event.php
│ │ │ ├── EventSubscription.php
│ │ │ ├── GoogleAccount.php
│ │ │ ├── Instance.php
│ │ │ ├── OutlookAccount.php
│ │ │ ├── PersonalAccessToken.php
│ │ │ ├── Room.php
│ │ │ ├── Setting.php
│ │ │ ├── User.php
│ │ │ ├── Workspace.php
│ │ │ └── WorkspaceMember.php
│ │ ├── Notifications/
│ │ │ └── MagicLoginNotification.php
│ │ ├── Observers/
│ │ │ └── EventObserver.php
│ │ ├── Policies/
│ │ │ ├── BoardPolicy.php
│ │ │ └── DisplayPolicy.php
│ │ ├── Providers/
│ │ │ ├── AppServiceProvider.php
│ │ │ └── AuthServiceProvider.php
│ │ ├── Services/
│ │ │ ├── CalDAVService.php
│ │ │ ├── DisplayService.php
│ │ │ ├── EventService.php
│ │ │ ├── GoogleService.php
│ │ │ ├── ImageService.php
│ │ │ ├── InstanceService.php
│ │ │ └── OutlookService.php
│ │ └── Traits/
│ │ ├── HasLastActivity.php
│ │ ├── HasUlid.php
│ │ └── RespondsWithApiResponse.php
│ ├── artisan
│ ├── bootstrap/
│ │ ├── app.php
│ │ ├── cache/
│ │ │ └── .gitignore
│ │ ├── opentelemetry.php
│ │ └── providers.php
│ ├── composer.json
│ ├── config/
│ │ ├── app.php
│ │ ├── auth.php
│ │ ├── broadcasting.php
│ │ ├── cache.php
│ │ ├── database.php
│ │ ├── faro.php
│ │ ├── filesystems.php
│ │ ├── googletagmanager.php
│ │ ├── lemon-squeezy.php
│ │ ├── logging.php
│ │ ├── magiclink.php
│ │ ├── mail.php
│ │ ├── queue.php
│ │ ├── recaptchav3.php
│ │ ├── sanctum.php
│ │ ├── sentry.php
│ │ ├── services.php
│ │ ├── session.php
│ │ ├── settings.php
│ │ └── wave.php
│ ├── database/
│ │ ├── .gitignore
│ │ ├── factories/
│ │ │ ├── BoardFactory.php
│ │ │ ├── CalDAVAccountFactory.php
│ │ │ ├── CalendarFactory.php
│ │ │ ├── DeviceFactory.php
│ │ │ ├── DisplayFactory.php
│ │ │ ├── EventSubscriptionFactory.php
│ │ │ ├── GoogleAccountFactory.php
│ │ │ ├── InstanceFactory.php
│ │ │ ├── OutlookAccountFactory.php
│ │ │ ├── RoomFactory.php
│ │ │ ├── UserFactory.php
│ │ │ └── WorkspaceFactory.php
│ │ ├── migrations/
│ │ │ ├── 2014_10_12_000000_create_users_table.php
│ │ │ ├── 2014_10_12_100000_create_password_reset_tokens_table.php
│ │ │ ├── 2017_07_06_000000_create_table_magic_links.php
│ │ │ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ │ │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php
│ │ │ ├── 2021_03_06_211907_add_access_code_to_magic_links_table.php
│ │ │ ├── 2024_03_19_000000_add_usage_type_to_users_table.php
│ │ │ ├── 2024_10_08_193424_create_outlook_accounts_table.php
│ │ │ ├── 2024_10_08_193455_create_calendars_table.php
│ │ │ ├── 2024_10_12_203020_create_displays_table.php
│ │ │ ├── 2024_10_17_212003_create_event_subscriptions_table.php
│ │ │ ├── 2025_01_12_122905_create_devices_table.php
│ │ │ ├── 2025_01_12_190259_create_rooms_table.php
│ │ │ ├── 2025_05_04_204354_remove_unique_from_outlook_accounts.php
│ │ │ ├── 2025_05_07_181029_create_sessions_table.php
│ │ │ ├── 2025_05_07_181034_create_cache_table.php
│ │ │ ├── 2025_05_17_130507_create_google_accounts_table.php
│ │ │ ├── 2025_05_17_153857_add_google_account_id_to_calendars_table.php
│ │ │ ├── 2025_05_18_010101_remove_unique_from_google_accounts.php
│ │ │ ├── 2025_05_18_010201_remove_unique_from_calendars.php
│ │ │ ├── 2025_05_18_114502_add_status_to_accounts.php
│ │ │ ├── 2025_05_21_000000_add_google_account_id_to_event_subscriptions.php
│ │ │ ├── 2025_05_23_000000_create_caldav_accounts_table.php
│ │ │ ├── 2025_05_23_000001_add_caldav_account_id_to_calendars_table.php
│ │ │ ├── 2025_05_23_201433_add_google_id_to_users_table.php
│ │ │ ├── 2025_05_27_203928_add_last_activity_at_to_users_table.php
│ │ │ ├── 2025_05_27_204843_add_last_activity_at_to_devices_table.php
│ │ │ ├── 2025_05_28_193657_add_is_billing_exempt_to_users_table.php
│ │ │ ├── 2025_05_28_194845_add_is_unlimited_to_users_table.php
│ │ │ ├── 2025_06_08_000001_add_terms_accepted_at_to_users_table.php
│ │ │ ├── 2025_06_09_115819_drop_is_billing_exempt_from_users_table.php
│ │ │ ├── 2025_06_09_122516_add_hosted_domain_to_google_accounts_table.php
│ │ │ ├── 2025_06_09_122702_add_tenant_id_to_outlook_accounts_table.php
│ │ │ ├── 2025_06_09_125231_add_uid_to_devices_table.php
│ │ │ ├── 2025_06_09_150001_create_instances_table.php
│ │ │ ├── 2025_06_15_000000_create_settings_table.php
│ │ │ ├── 2025_06_15_120000_change_billable_id_to_ulid_on_lemonsqueezy_tables.php
│ │ │ ├── 2025_06_16_000000_create_events_table.php
│ │ │ ├── 2025_07_05_000000_create_display_settings_table.php
│ │ │ ├── 2025_07_05_000001_alter_avatar_column_on_google_accounts_table.php
│ │ │ ├── 2025_07_27_000000_add_is_admin_to_users_table.php
│ │ │ ├── 2025_11_28_000000_add_permission_type_to_outlook_accounts_table.php
│ │ │ ├── 2025_11_28_000001_add_permission_type_to_google_accounts_table.php
│ │ │ ├── 2025_11_28_000002_add_permission_type_to_caldav_accounts_table.php
│ │ │ ├── 2025_12_03_000000_add_service_account_file_path_to_google_accounts_table.php
│ │ │ ├── 2025_12_04_000000_add_booking_method_to_google_accounts_table.php
│ │ │ ├── 2025_12_05_000000_encrypt_existing_tokens_in_google_and_outlook_accounts.php
│ │ │ ├── 2025_12_06_000003_add_first_name_and_last_name_to_users_table.php
│ │ │ ├── 2025_12_30_000000_create_workspaces_table.php
│ │ │ ├── 2025_12_30_000001_create_workspace_members_table.php
│ │ │ ├── 2025_12_30_000002_add_workspace_id_to_tables.php
│ │ │ ├── 2025_12_30_000003_add_workspace_id_to_accounts_tables.php
│ │ │ ├── 2025_12_30_000004_create_workspaces_for_existing_users.php
│ │ │ ├── 2026_02_28_000000_increase_events_description_column_size.php
│ │ │ ├── 2026_02_28_000001_increase_caldav_accounts_password_column_size.php
│ │ │ ├── 2026_02_28_120000_create_boards_table.php
│ │ │ ├── 2026_02_28_120001_create_board_displays_table.php
│ │ │ ├── 2026_02_28_120002_add_theme_to_boards_table.php
│ │ │ ├── 2026_02_28_120003_add_logo_to_boards_table.php
│ │ │ ├── 2026_02_28_120004_add_display_options_to_boards_table.php
│ │ │ ├── 2026_02_28_120005_add_additional_settings_to_boards_table.php
│ │ │ ├── 2026_02_28_120007_add_title_and_subtitle_to_boards_table.php
│ │ │ ├── 2026_02_28_120008_add_view_mode_to_boards_table.php
│ │ │ └── 2026_02_28_140000_add_boards_count_to_instances_table.php
│ │ └── seeders/
│ │ └── DatabaseSeeder.php
│ ├── docs/
│ │ ├── CODING_STANDARDS.md
│ │ └── WORKSPACE_SETUP.md
│ ├── lang/
│ │ ├── de/
│ │ │ └── boards.php
│ │ ├── en/
│ │ │ ├── boards.php
│ │ │ └── validation.php
│ │ ├── es/
│ │ │ └── boards.php
│ │ ├── fr/
│ │ │ └── boards.php
│ │ ├── nl/
│ │ │ └── boards.php
│ │ └── sv/
│ │ └── boards.php
│ ├── package.json
│ ├── phpunit.xml
│ ├── public/
│ │ ├── .htaccess
│ │ ├── images/
│ │ │ └── backgrounds/
│ │ │ └── README.md
│ │ ├── index.php
│ │ ├── robots.txt
│ │ └── site.webmanifest
│ ├── requests/
│ │ ├── .gitignore
│ │ ├── api/
│ │ │ ├── activate.http
│ │ │ ├── auth/
│ │ │ │ └── login.http
│ │ │ ├── book-room.http
│ │ │ ├── cancel-event.http
│ │ │ ├── change-display.http
│ │ │ ├── check-in-event.http
│ │ │ ├── get-display-data.http
│ │ │ ├── get-displays.http
│ │ │ ├── get-events.http
│ │ │ ├── get-me.http
│ │ │ ├── heartbeat.http
│ │ │ └── outlook/
│ │ │ ├── get-outlook-calendars.http
│ │ │ └── outlook-auth.http
│ │ ├── graph/
│ │ │ ├── get-calendar-by-email.http
│ │ │ ├── get-calendars.http
│ │ │ ├── get-events.http
│ │ │ └── get-rooms.http
│ │ └── webhook-tests.http
│ ├── resources/
│ │ ├── css/
│ │ │ └── app.css
│ │ ├── js/
│ │ │ ├── app.js
│ │ │ ├── bootstrap.js
│ │ │ └── echo.js
│ │ └── views/
│ │ ├── .gitkeep
│ │ ├── auth/
│ │ │ ├── login.blade.php
│ │ │ └── register.blade.php
│ │ ├── components/
│ │ │ ├── alerts/
│ │ │ │ └── alert.blade.php
│ │ │ ├── calendars/
│ │ │ │ └── picker.blade.php
│ │ │ ├── cards/
│ │ │ │ └── card.blade.php
│ │ │ ├── displays/
│ │ │ │ └── table-row.blade.php
│ │ │ ├── icons/
│ │ │ │ ├── arrow-left.blade.php
│ │ │ │ ├── brush.blade.php
│ │ │ │ ├── building.blade.php
│ │ │ │ ├── caldav.blade.php
│ │ │ │ ├── calendar.blade.php
│ │ │ │ ├── display.blade.php
│ │ │ │ ├── external.blade.php
│ │ │ │ ├── google.blade.php
│ │ │ │ ├── information.blade.php
│ │ │ │ ├── logout.blade.php
│ │ │ │ ├── microsoft.blade.php
│ │ │ │ ├── pause.blade.php
│ │ │ │ ├── play.blade.php
│ │ │ │ ├── plus.blade.php
│ │ │ │ ├── room.blade.php
│ │ │ │ ├── settings.blade.php
│ │ │ │ ├── trash.blade.php
│ │ │ │ └── users.blade.php
│ │ │ ├── impersonation-banner.blade.php
│ │ │ ├── modals/
│ │ │ │ ├── google-service-account.blade.php
│ │ │ │ ├── license-key.blade.php
│ │ │ │ ├── manage-subscription.blade.php
│ │ │ │ ├── select-google-booking-method.blade.php
│ │ │ │ └── select-permission.blade.php
│ │ │ ├── rooms/
│ │ │ │ └── picker.blade.php
│ │ │ └── scripts/
│ │ │ ├── clarity.blade.php
│ │ │ └── faro.blade.php
│ │ ├── errors/
│ │ │ ├── 403.blade.php
│ │ │ ├── 404.blade.php
│ │ │ ├── 419.blade.php
│ │ │ ├── 429.blade.php
│ │ │ └── 500.blade.php
│ │ ├── layouts/
│ │ │ ├── base.blade.php
│ │ │ ├── blank.blade.php
│ │ │ └── error.blade.php
│ │ ├── pages/
│ │ │ ├── admin/
│ │ │ │ └── user.blade.php
│ │ │ ├── admin.blade.php
│ │ │ ├── boards/
│ │ │ │ ├── form.blade.php
│ │ │ │ ├── index.blade.php
│ │ │ │ └── show.blade.php
│ │ │ ├── caldav-accounts/
│ │ │ │ └── create.blade.php
│ │ │ ├── dashboard.blade.php
│ │ │ ├── displays/
│ │ │ │ ├── create.blade.php
│ │ │ │ ├── customization.blade.php
│ │ │ │ └── settings.blade.php
│ │ │ ├── onboarding.blade.php
│ │ │ └── usage/
│ │ │ └── index.blade.php
│ │ └── vendor/
│ │ ├── googletagmanager/
│ │ │ └── body.blade.php
│ │ └── pagination/
│ │ └── tailwind.blade.php
│ ├── routes/
│ │ ├── api.php
│ │ ├── channels.php
│ │ ├── console.php
│ │ └── web.php
│ ├── storage/
│ │ ├── app/
│ │ │ └── .gitignore
│ │ ├── framework/
│ │ │ ├── .gitignore
│ │ │ ├── cache/
│ │ │ │ └── .gitignore
│ │ │ ├── sessions/
│ │ │ │ └── .gitignore
│ │ │ ├── testing/
│ │ │ │ └── .gitignore
│ │ │ └── views/
│ │ │ └── .gitignore
│ │ └── logs/
│ │ └── .gitignore
│ ├── tailwind.config.js
│ ├── tests/
│ │ ├── Feature/
│ │ │ ├── API/
│ │ │ │ ├── AuthControllerTest.php
│ │ │ │ └── EventControllerTest.php
│ │ │ ├── AdminBoardsTest.php
│ │ │ ├── BoardControllerTest.php
│ │ │ ├── BoardUsageTest.php
│ │ │ ├── DisplaySettingsApiTest.php
│ │ │ ├── InstanceHeartbeatTest.php
│ │ │ └── PageReachabilityTest.php
│ │ ├── Pest.php
│ │ ├── TestCase.php
│ │ └── Unit/
│ │ ├── DisplaySettingsTest.php
│ │ ├── SettingsTest.php
│ │ └── WorkspaceUsageTest.php
│ └── vite.config.js
├── deployment/
│ └── docker-compose.mariadb-redis.yml
├── docker-compose.dev.yml
├── docker-compose.prod.yml
├── docker-compose.yml
├── docs/
│ ├── REVERSE_PROXY.md
│ ├── SETUP.md
│ └── UPGRADE_GUIDE.md
└── k6/
├── README.md
├── load-test.js
└── tags.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/docker-build.yml
================================================
name: Build and Push Docker Image
on:
push:
branches:
- main
- dev
paths:
- 'backend/**'
- '.github/workflows/docker-build.yml'
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: docker-container
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
# For main branch
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=stable,enable=${{ github.ref == 'refs/heads/main' }}
# For dev branch
type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }}
# Common tags
type=ref,event=branch
type=sha,format=short
type=raw,value=${{ github.ref_name }}-${{ github.sha }},enable=${{ github.ref != 'refs/heads/main' }}
- name: Get git tag
id: git_tag
run: |
GIT_TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
echo "tag=${GIT_TAG}" >> $GITHUB_OUTPUT
echo "Found tag: ${GIT_TAG}"
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./backend
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_TAG=${{ steps.git_tag.outputs.tag }}
GIT_COMMIT=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
================================================
FILE: .github/workflows/tests.yml
================================================
name: Tests
on:
push:
branches:
- main
- dev
paths:
- 'backend/**'
- '.github/workflows/tests.yml'
pull_request:
branches:
- main
- dev
paths:
- 'backend/**'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, opentelemetry, protobuf
coverage: xdebug
- name: Copy .env
working-directory: backend
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Get Composer Cache Directory
working-directory: backend
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('backend/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Dependencies
working-directory: backend
run: |
composer self-update
composer install --prefer-dist --no-progress --no-scripts
composer dump-autoload
- name: Generate key
working-directory: backend
run: php artisan key:generate
- name: Directory Permissions
working-directory: backend
run: chmod -R 777 storage bootstrap/cache
- name: Execute tests (via Pest)
working-directory: backend
run: vendor/bin/pest --coverage-text --coverage-clover=coverage.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: backend/coverage.xml
fail_ci_if_error: true
================================================
FILE: .gitignore
================================================
.env
database.sqlite
.claude/
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Spacepad is a privacy-focused room display application that shows real-time room availability, synced with calendars from Google, Microsoft, and CalDAV providers. The project consists of:
- **Frontend (Flutter app)**: Cross-platform mobile app for room displays
- **Backend (Laravel API)**: RESTful API handling authentication, calendar integration, and webhook processing
## Architecture
### Flutter App (`/app/`)
- **MVC Pattern**: Controllers handle business logic, Services manage API communication
- **State Management**: GetX for dependency injection and state management
- **Main Components**:
- `controllers/`: Business logic controllers (DashboardController, DisplayController, LoginController)
- `services/`: API communication services (ApiService, AuthService, EventService, DisplayService)
- `models/`: Data models (EventModel, DisplayModel, DeviceModel, UserModel)
- `pages/`: UI screens (LoginPage, DashboardPage, DisplayPage, SplashPage)
- `components/`: Reusable UI components (ActionButton, EventLine, Spinner, Toast)
### Laravel Backend (`/backend/`)
- **Clean Architecture**: Controllers, Services, Models, and Data classes
- **Authentication**: Laravel Sanctum for API authentication
- **Calendar Integration**: Google Calendar API, Microsoft Graph API, CalDAV
- **Main Components**:
- `app/Http/Controllers/API/`: API controllers for mobile app
- `app/Services/`: Business logic services (EventService, GoogleService, OutlookService, CalDAVService)
- `app/Models/`: Eloquent models (User, Display, Event, GoogleAccount, OutlookAccount)
- `app/Data/`: Data transfer objects using Spatie Laravel Data
## Common Development Commands
### Flutter App
```bash
# Navigate to app directory
cd app
# Install dependencies
flutter pub get
# Run the app in development
flutter run
# Build for Android
flutter build apk
# Build for iOS
flutter build ios
# Run tests
flutter test
# Generate launcher icons
flutter pub run flutter_launcher_icons:main
```
### Laravel Backend
```bash
# Navigate to backend directory
cd backend
# Install PHP dependencies
composer install
# Install Node.js dependencies
npm install
# Run development server (with queue, logs, and vite)
composer dev
# Run individual services
php artisan serve # Web server
php artisan queue:listen --tries=1 # Queue worker
php artisan pail --timeout=0 # Log viewer
npm run dev # Vite asset bundler
# Database operations
php artisan migrate # Run migrations
php artisan db:seed # Seed database
php artisan migrate:fresh --seed # Fresh migration with seeding
# Clear caches
php artisan config:clear
php artisan cache:clear
php artisan route:clear
# Run tests
php artisan test
./vendor/bin/pest
# Code formatting
./vendor/bin/pint
```
## Key Architecture Patterns
### Flutter App Patterns
- **GetX Controllers**: Handle state management and business logic
- **Service Layer**: Abstracts API calls and external dependencies
- **Repository Pattern**: Services act as repositories for data access
- **Translations**: Internationalization support with GetX translations
### Laravel Backend Patterns
- **API Resources**: Transform model data for API responses
- **Service Classes**: Encapsulate business logic and external API interactions
- **Data Classes**: Type-safe data transfer objects
- **Middleware**: Authentication and request processing
- **Webhooks**: Handle real-time calendar updates from external providers
## Environment Configuration
### Flutter App
- Uses `.env` file for environment variables
- Key variables: API endpoints, environment settings
### Laravel Backend
- Uses `.env` file for configuration
- Key variables: Database, cache, queue, calendar API credentials, webhook URLs
## Testing
### Flutter
- Widget tests in `/app/test/`
- Run with `flutter test`
### Laravel
- Feature and Unit tests in `/backend/tests/`
- Uses Pest PHP testing framework
- Run with `php artisan test` or `./vendor/bin/pest`
## External Integrations
### Calendar Providers
- **Google Calendar**: Uses Google Calendar API v3
- **Microsoft 365**: Uses Microsoft Graph API
- **CalDAV**: Generic CalDAV protocol support
### Licensing
- **LemonSqueezy**: Handles subscription billing for Pro features
- **License validation**: Cloud-based instance validation system
## Development Notes
- **PHP Requirements**: Backend requires PHP 8.4+ (composer.json specifies ^8.4)
- **Flutter Version**: Uses Flutter 3.29.0+ with Dart 3.7.0+
- **Database**: SQLite for local development, supports other databases for production
- **Queue System**: Laravel queues for background job processing
- **Real-time Updates**: Webhook handlers for calendar change notifications
- **Cross-platform**: Flutter app supports iOS and Android
- **Internationalization**: Support for English, Dutch, French, Spanish, and German
## Security Considerations
- API authentication via Laravel Sanctum tokens
- Device-specific authentication and display assignment
- User activity tracking and session management
- Webhook signature validation for external calendar providers
- Environment-based configuration for sensitive data
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Spacepad
Thank you for your interest in contributing to Spacepad! This document provides guidelines and instructions for contributing to our project.
## How to Contribute
### Reporting Bugs
- Check if the bug has already been reported in the [Issues](https://github.com/magweter/spacepad/issues) section
- If not, create a new issue with a clear title and description
- Include as much relevant information as possible (steps to reproduce, expected behavior, actual behavior, screenshots, etc.)
- Use the bug report template if available
### Suggesting Enhancements
- Check if the enhancement has already been suggested in the [Issues](https://github.com/magweter/spacepad/issues) section
- If not, create a new issue with a clear title and description
- Explain why this enhancement would be useful to most users
- Use the feature request template if available
### Pull Requests
1. Fork the repository
2. Create a new branch for your feature or bugfix (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Run tests to ensure your changes don't break existing functionality
5. Commit your changes (`git commit -m 'Add some amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
### Development Setup
1. Clone your fork of the repository
2. Install dependencies:
```bash
composer install
pnpm install
```
3. Set up your environment:
```bash
cp .env.example .env
php artisan key:generate
```
4. Start the development server:
```bash
docker-compose up -d
```
### Coding Standards
- Follow the [PSR-12](https://www.php-fig.org/psr/psr-12/) coding style guide for PHP
- Always use import statements instead of inline fully qualified class names - See [Coding Standards](backend/docs/CODING_STANDARDS.md) for details
- Use ESLint and Prettier for JavaScript/TypeScript
- Write meaningful commit messages
- Add comments for complex logic
- Update documentation as needed
### Testing
- Write tests for new features
- Ensure all tests pass before submitting a pull request
- Run the test suite:
```bash
php artisan test
```
## Documentation
- Update the README.md if needed
- Add inline documentation for complex functions
- Update API documentation if you change endpoints
- Add examples for new features
## Release Process
1. Update version numbers in relevant files
2. Update the CHANGELOG.md
3. Create a new release on GitHub
4. Tag the release with the version number
## Questions?
If you have any questions, please open an issue or contact the maintainers.
Thank you for contributing to Spacepad!
================================================
FILE: LICENSE.md
================================================
Spacepad Community License (Sustainable Use License)
----------------------------------------------------
Copyright (c) 2025 Spacepad.io
Permission is hereby granted, free of charge, to any individual or organization (the "User") to use, copy, modify, and self-host this software (the “Software”) under the following conditions:
1. **Permitted Use**
- You may use and modify the Software for **personal**, **educational**, or **non-commercial personal** purposes.
- You may also use the Software in a **limited commercial** or **non-commercial organizational** setting under the following condition:
- The deployment includes **no more than 1 active room display** at any time.
- The deployment is **self-hosted**.
- Use beyond this limit requires a paid Pro license, regardless of commercial or non-commercial status.
2. **Non-Commercial Organizations (e.g. nonprofits, schools)**
- Organizations using the Software non-commercially (such as nonprofits, educational institutions, or NGOs) **must obtain a Pro license** for use beyond 1 display.
- Eligible non-commercial organizations are entitled to a **50% discount** on licensing fees.
3. **Commercial Use Restrictions**
- Use of the Software with **more than 1 display**, or to access premium features, requires a valid **Spacepad Pro License**.
- You may not offer the Software as a hosted service (SaaS) or embed it in commercial products without written permission.
4. **Attribution**
- You may remove or modify Spacepad branding only with a valid Pro license.
- Attribution in the form of a visible link or credit is appreciated but not required for personal use.
5. **No Warranty**
- This Software is provided "as is", without warranty of any kind.
6. **Termination**
- This license is automatically terminated if these terms are violated.
- Continued use beyond these terms requires a Pro license or separate written agreement.
To purchase a license or request a nonprofit discount, visit: https://spacepad.io/pricing
Contact: support@spacepad.io
================================================
FILE: LICENSE_PRO.md
================================================
Spacepad Pro License (Commercial Use)
--------------------------------------
Copyright (c) 2025 Spacepad.io
This license grants you (the "Licensee") the right to use Spacepad in commercial deployments beyond what is permitted in the Community License.
1. **Scope of License**
- You may deploy Spacepad in a commercial environment with the amount of displays specified by your purchased plan. For details of these plans see [pricing](https://spacepad.io/pricing).
- You may use all included Pro features.
2. **Conditions**
- This license is **non-transferable** and limited to your organization or client.
- You may not sublicense, sell, or resell Spacepad or host it as a service to third parties without a separate agreement.
3. **Delivery and Activation**
- A valid license key may be required to unlock Pro features.
- Use of license keys is subject to monitoring and rate-limiting.
4. **Support**
- Commercial licenses include email support and update access during the active term of the license.
5. **Term**
- Your license is valid for the subscription period purchased (monthly or yearly).
- Renewal is required to continue using Pro features after expiration.
6. **Termination**
- This license is revoked if the terms are violated.
- License keys may be deactivated in the event of abuse, fraud, or breach of terms.
For questions or volume licensing, contact: support@spacepad.io
================================================
FILE: README.md
================================================
<p align="center" style="margin-top: 120px">
<h1 align="center">Spacepad</h3>
<p align="center">Simple room displays for every workplace. Display room availability in real-time, <br>synced with your rooms and calendars — ideal for tablets outside meeting spaces or home-offices. <br>Suitable for both small offices and larger deployments.
<br />
<br />
<a href="https://spacepad.io">Website</a>
·
<a href="https://github.com/magweter/spacepad/issues">Report Issue</a>
·
<a href="https://github.com/magweter/spacepad/discussions">Suggest Feature</a>
</p>
</p>

## Our Mission
We’re building focused, fun tools for modern offices — tools that just work, without enterprise BS.
Spacepad strives to be the perfect all-encompassing room display solution for SMB's.
<br><br>
✅ Simple: Easy to deploy and use<br>
🔐 Privacy-first: Self hosted and open source auditable<br>
💸 Fair and sustainable: We offer paid features to keep development active<br>
❤️ Designed with care: Beautiful on tablets, easy on the eye<br>
## Features
Spacepad offers a comprehensive suite of features to make managing and viewing room availability effortless.
### Core Features
- **Real-time room availability** - Events sync instantly and display current room status
- **Multi-room overview boards** - Create beautiful dashboards showing multiple rooms at once with customizable layouts (card, table, or grid view)
- **On-device room booking** - Book rooms directly from the display with preset durations (15/30/60 min) or custom time slots
- **Room check-in** - Check in to reserved meetings with configurable grace periods
- **Event cancellation** - Cancel current meetings directly from the display
- **Full day schedule** - View all upcoming events for the day on each display
### Calendar Integrations
- **Google Calendar** - Full integration with Google Workspace calendars
- **Microsoft 365** - Seamless sync with Outlook calendars via Microsoft Graph API
- **CalDAV** - Support for any CalDAV-compatible provider (Nextcloud, iCloud, etc.)
- **Real-time webhooks** - Instant updates when calendar events change
### Customization & Branding
- **Custom themes** - Dark, light, or system theme support
- **Custom logos** - Upload your organization's logo to displays and boards
- **Font selection** - Choose from multiple Google Fonts (Inter, Roboto, Open Sans, Lato, Poppins, Montserrat)
- **Multi-language support** - Available in English, Dutch, French, German, Spanish, and Swedish
- **Privacy controls** - Hide meeting titles for privacy-sensitive environments
- **Display settings** - Configure what information to show (booker name, next event, transitioning status)
### Workspace & Collaboration
- **Workspaces** - Organize displays, calendars, and boards by workspace
- **Team collaboration** - Share workspaces with team members with role-based access
- **Workspace-scoped resources** - All displays, calendars, and boards are organized per workspace
### Deployment Options
- **Cloud Hosted** - Get started in minutes with zero maintenance
- **Self Hosted** - Full control over your data with Docker deployment
- **Cross-platform** - Native iOS and Android apps built with Flutter
> [!TIP]
> The product is developing rapidly and we're happily accepting feedback and suggestions. Have a look at our [roadmap](#roadmap) on the implementation of new features or open a new [discussion](https://github.com/magweter/spacepad/discussions) to share ideas.
## 🔧 Get Started
### ☁️ Cloud Hosted (Easiest)
Looking to get started quickly? Get started in minutes using our cloud.
1. Visit [spacepad.io](https://spacepad.io)
2. Create a free account
3. Set up your first display — the first one is free forever
4. Add more displays at $6/month each
Great for fast deployments with zero maintenance.
### 🏗️ Self Hosted
Self hosting Spacepad is the perfect solution for businesses or enthousiasts who want control over their data.
As we believe in open source and personal tinkering, we want to support these communities.
🙎♂️ If you’re a hobbyist or home user, enjoy Spacepad self hosted without limits — completely for free.
🏢 If you're a business using Spacepad, we ask you to purchase a self-hosted license. We offer simple, sustainable and affordable flat-tiered pricing. Have a look at [Spacepad Pricing](https://spacepad.io/pricing).
For full setup instructions, see [Setup Guide](docs/SETUP.md).
## 🛠 Licensing
Spacepad is dual-licensed:
- 🧑💻 **Community License** ([LICENSE.md](LICENSE.md))
For personal use and self-hosted commercial use with up to 1 display.
- 🏢 **Pro License** ([LICENSE_PRO.md](LICENSE_PRO.md))
Required for commercial use with multiple displays or Pro features.
Purchasing a license helps support continued development.
## 🤝 Contributing
We love open source and welcome your contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) to get started.
## 📅 Roadmap Highlights
- [x] Microsoft 365 / Outlook integration
- [x] Self-hosted deployment with Docker
- [x] Google Workspace / Google Calendar integration
- [x] CalDAV support (Nextcloud, iCloud, etc.)
- [x] On-device room booking with custom time slots
- [x] Room check-in and release functionality
- [x] Full day event schedule display
- [x] Custom themes, logos, and fonts
- [x] Multi-room overview boards (Pro feature)
- [x] Workspace management and collaboration
- [x] Multi-language support (6 languages)
- [x] Privacy controls for meeting titles
- [x] Multiple board view modes (card, table, grid)
Feature requests? We're all ears! Please open a new [discussion](https://github.com/magweter/spacepad/discussions).
================================================
FILE: app/.gitignore
================================================
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
.env
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# FVM Version Cache
.fvm/
================================================
FILE: app/.metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "2d17299f20f3eb164ef21bc80b8079ba293e5985"
channel: "master"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 2d17299f20f3eb164ef21bc80b8079ba293e5985
base_revision: 2d17299f20f3eb164ef21bc80b8079ba293e5985
- platform: android
create_revision: 2d17299f20f3eb164ef21bc80b8079ba293e5985
base_revision: 2d17299f20f3eb164ef21bc80b8079ba293e5985
- platform: ios
create_revision: 2d17299f20f3eb164ef21bc80b8079ba293e5985
base_revision: 2d17299f20f3eb164ef21bc80b8079ba293e5985
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
================================================
FILE: app/README.md
================================================
# spacepad
A simple and fun meeting room occupancy display.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
================================================
FILE: app/analysis_options.yaml
================================================
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
================================================
FILE: app/android/.gitignore
================================================
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
================================================
FILE: app/android/app/.gitignore
================================================
.cxx/
================================================
FILE: app/android/app/build.gradle.kts
================================================
import java.util.Properties
import java.io.FileInputStream
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
// Load keystore properties from key.properties
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
namespace = "com.magweter.spacepad"
compileSdk = flutter.compileSdkVersion
ndkVersion = "27.0.12077973"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
defaultConfig {
applicationId = "com.magweter.spacepad"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
signingConfig = signingConfigs.getByName("release")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
flutter {
source = "../.."
}
================================================
FILE: app/android/app/src/debug/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
================================================
FILE: app/android/app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="Spacepad"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
================================================
FILE: app/android/app/src/main/kotlin/com/magweter/spacepad/MainActivity.kt
================================================
package com.magweter.spacepad
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
================================================
FILE: app/android/app/src/main/res/drawable/launch_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
================================================
FILE: app/android/app/src/main/res/drawable-v21/launch_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
================================================
FILE: app/android/app/src/main/res/values/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
================================================
FILE: app/android/app/src/main/res/values-night/styles.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
================================================
FILE: app/android/app/src/profile/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
================================================
FILE: app/android/build.gradle.kts
================================================
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
subprojects {
plugins.withType<com.android.build.gradle.BasePlugin> {
extensions.findByType<com.android.build.gradle.BaseExtension>()?.apply {
if (namespace == null) {
namespace = group.toString()
}
}
}
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
================================================
FILE: app/android/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
================================================
FILE: app/android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
================================================
FILE: app/android/settings.gradle.kts
================================================
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "2.2.21" apply false
}
include(":app")
================================================
FILE: app/ios/.gitignore
================================================
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
================================================
FILE: app/ios/Flutter/AppFrameworkInfo.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>
================================================
FILE: app/ios/Flutter/Debug.xcconfig
================================================
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
================================================
FILE: app/ios/Flutter/Release.xcconfig
================================================
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
================================================
FILE: app/ios/Podfile
================================================
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
================================================
FILE: app/ios/Runner/AppDelegate.swift
================================================
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
================================================
FILE: app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
================================================
FILE: app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
================================================
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
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.
================================================
FILE: app/ios/Runner/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
================================================
FILE: app/ios/Runner/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
================================================
FILE: app/ios/Runner/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Spacepad</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>spacepad</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
================================================
FILE: app/ios/Runner/Runner-Bridging-Header.h
================================================
#import "GeneratedPluginRegistrant.h"
================================================
FILE: app/ios/Runner.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
04CBFBCE232CB3029E7ED08A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA6696D39F3FA1106D16702B /* Pods_Runner.framework */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3670718E77EE3226CF664312 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03B40A5A39F58BC328EECDE1 /* Pods_RunnerTests.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
03B40A5A39F58BC328EECDE1 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0861BD94EAEC750534439CD7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
288B2822D826A92AAEFB3A7A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
62CD59CF29A95104CB65D9E7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8993A6110D12561E9348200F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
EA6696D39F3FA1106D16702B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
EDAA5666919238FE907B7EE2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
FE7BEC1B12808A29558CCC47 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
64E37CD377E15AE198178E4F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3670718E77EE3226CF664312 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
04CBFBCE232CB3029E7ED08A /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
C0DBB1C66027CD5924273FC7 /* Pods */,
9C087B018664F9F7D6AF9B81 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
9C087B018664F9F7D6AF9B81 /* Frameworks */ = {
isa = PBXGroup;
children = (
EA6696D39F3FA1106D16702B /* Pods_Runner.framework */,
03B40A5A39F58BC328EECDE1 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
C0DBB1C66027CD5924273FC7 /* Pods */ = {
isa = PBXGroup;
children = (
0861BD94EAEC750534439CD7 /* Pods-Runner.debug.xcconfig */,
62CD59CF29A95104CB65D9E7 /* Pods-Runner.release.xcconfig */,
288B2822D826A92AAEFB3A7A /* Pods-Runner.profile.xcconfig */,
EDAA5666919238FE907B7EE2 /* Pods-RunnerTests.debug.xcconfig */,
8993A6110D12561E9348200F /* Pods-RunnerTests.release.xcconfig */,
FE7BEC1B12808A29558CCC47 /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
0FCF9A8EB03F9B4634484BD6 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
64E37CD377E15AE198178E4F /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
CE1F428B58F0533B9F926766 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
AAA2B55502893C358B6E22B5 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
0FCF9A8EB03F9B4634484BD6 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
AAA2B55502893C358B6E22B5 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
CE1F428B58F0533B9F926766 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = DGZY9K7USV;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Spacepad;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.magweter.spacepad;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = EDAA5666919238FE907B7EE2 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.magweter.spacepad.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 8993A6110D12561E9348200F /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.magweter.spacepad.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FE7BEC1B12808A29558CCC47 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.magweter.spacepad.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = DGZY9K7USV;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Spacepad;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.magweter.spacepad;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = DGZY9K7USV;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Spacepad;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.magweter.spacepad;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
================================================
FILE: app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
================================================
FILE: app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: app/ios/Runner.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
================================================
FILE: app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
================================================
FILE: app/ios/RunnerTests/RunnerTests.swift
================================================
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}
================================================
FILE: app/lib/components/action_button.dart
================================================
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tailwind_components/tailwind_components.dart';
import 'package:spacepad/components/frosted_panel.dart';
class ActionButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final Color? borderColor;
final Color? textColor;
final bool isPhone;
final double cornerRadius;
final bool disabled;
final bool isLoading;
const ActionButton({
super.key,
required this.text,
required this.onPressed,
required this.isPhone,
required this.cornerRadius,
this.borderColor,
this.textColor,
this.disabled = false,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
final Color effectiveBorderColor = borderColor ?? TWColors.gray_500.withAlpha(160);
final bool isDisabled = disabled || isLoading;
return Opacity(
opacity: isDisabled ? 0.5 : 1.0,
child: Container(
margin: EdgeInsets.only(top: isPhone ? 10 : 20, bottom: isPhone ? 10 : 20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(cornerRadius),
),
child: FrostedPanel(
borderRadius: cornerRadius,
blurIntensity: 18,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(cornerRadius),
onTap: isDisabled ? null : onPressed,
child: Stack(
children: [
Padding(
padding: EdgeInsets.symmetric(
vertical: isPhone ? 12 : 16,
horizontal: isPhone ? 20 : 28,
),
child: SizedBox(
height: isPhone ? 22 : 26, // Fixed height to match text line height
child: Stack(
alignment: Alignment.center,
children: [
// Keep text in layout to maintain button width, but make it invisible when loading
Opacity(
opacity: isLoading ? 0 : 1,
child: Text(
text.tr,
style: TextStyle(
color: textColor ?? TWColors.white,
fontSize: isPhone ? 16 : 20,
fontWeight: FontWeight.w700,
height: 1.0, // Ensure consistent line height
),
textAlign: TextAlign.center,
),
),
// Show loading indicator on top when loading
if (isLoading)
SizedBox(
width: isPhone ? 20 : 24,
height: isPhone ? 20 : 24,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
textColor ?? TWColors.white,
),
),
),
],
),
),
),
if (disabled && !isLoading)
Positioned.fill(
child: CustomPaint(
painter: _DiagonalStrikethroughPainter(
color: effectiveBorderColor,
),
),
),
],
),
),
),
),
),
);
}
}
class _DiagonalStrikethroughPainter extends CustomPainter {
final Color color;
static const double borderWidth = 2;
_DiagonalStrikethroughPainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = borderWidth;
// Draw from the middle of the border on each corner
canvas.drawLine(
Offset(1, size.height - 1),
Offset(size.width - 1, 1),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
================================================
FILE: app/lib/components/action_panel.dart
================================================
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:spacepad/components/action_button.dart';
import 'package:tailwind_components/tailwind_components.dart';
class ActionPanel extends StatelessWidget {
final dynamic controller;
final bool isPhone;
final double cornerRadius;
const ActionPanel({
super.key,
required this.controller,
required this.isPhone,
required this.cornerRadius,
});
@override
Widget build(BuildContext context) {
final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
return SpaceRow(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Show booking options when not reserved
Obx(() {
final showBooking = !controller.isReserved && controller.bookingEnabled;
final showCheckIn = controller.isCheckInActive && controller.checkInEnabled;
if (showBooking) {
final isBooking = controller.isBooking.value;
final bookingDuration = controller.bookingDuration.value;
// If both booking and check-in are visible, combine them
if (showCheckIn && !controller.showBookingOptions.value) {
// Show "book_now" button and check-in button together
return SpaceRow(
mainAxisSize: MainAxisSize.min,
spaceBetween: isPhone ? 12 : 16,
mainAxisAlignment: MainAxisAlignment.start,
children: [
ActionButton(
text: 'book_now',
onPressed: isBooking ? null : () => controller.toggleBookingOptions(),
isPhone: isPhone,
cornerRadius: cornerRadius,
isLoading: isBooking && bookingDuration == null,
),
ActionButton(
text: 'check_in',
onPressed: () => controller.checkIn(),
isPhone: isPhone,
cornerRadius: cornerRadius,
),
],
);
}
return controller.showBookingOptions.value ?
(isPortrait ?
// Portrait: Horizontally scrollable container wrapped in Flexible
Flexible(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Show all options, but disable and strikethrough if not available
for (var min in [15, 30, 60])
Padding(
padding: EdgeInsets.only(right: min == 60 ? 0 : (isPhone ? 12 : 16)),
child: ActionButton(
text: '$min min',
onPressed: (controller.availableBookingDurations.contains(min) && !isBooking)
? () => controller.bookRoom(min)
: null,
isPhone: isPhone,
cornerRadius: cornerRadius,
disabled: !controller.availableBookingDurations.contains(min) || (isBooking && bookingDuration != min),
isLoading: isBooking && bookingDuration == min, // Only show loading on the clicked button
),
),
if (controller.hasCustomBooking) ...[
SizedBox(width: isPhone ? 12 : 16),
ActionButton(
text: 'custom',
onPressed: isBooking ? null : () => controller.showCustomBookingModal(context, isPhone, cornerRadius),
isPhone: isPhone,
cornerRadius: cornerRadius,
disabled: isBooking,
isLoading: isBooking && bookingDuration == null, // Show loading if custom booking is in progress
),
],
SizedBox(width: isPhone ? 16 : 24),
ActionButton(
text: 'cancel',
onPressed: isBooking ? null : () => controller.hideBookingOptions(),
isPhone: isPhone,
cornerRadius: cornerRadius,
disabled: isBooking,
),
],
),
),
) :
// Landscape: Keep buttons in a single row
Row(
mainAxisSize: MainAxisSize.min,
children: [
// Show all options, but disable and strikethrough if not available
for (var min in [15, 30, 60])
Padding(
padding: EdgeInsets.only(right: min == 60 ? 0 : (isPhone ? 12 : 16)),
child: ActionButton(
text: '$min min',
onPressed: (controller.availableBookingDurations.contains(min) && !isBooking)
? () => controller.bookRoom(min)
: null,
isPhone: isPhone,
cornerRadius: cornerRadius,
disabled: !controller.availableBookingDurations.contains(min) || (isBooking && bookingDuration != min),
isLoading: isBooking && bookingDuration == min, // Only show loading on the clicked button
),
),
if (controller.hasCustomBooking) ...[
SizedBox(width: isPhone ? 12 : 16),
ActionButton(
text: 'custom',
onPressed: isBooking ? null : () => controller.showCustomBookingModal(context, isPhone, cornerRadius),
isPhone: isPhone,
cornerRadius: cornerRadius,
disabled: isBooking,
isLoading: isBooking && bookingDuration == null, // Show loading if custom booking is in progress
),
],
SizedBox(width: isPhone ? 16 : 24),
ActionButton(
text: 'cancel',
onPressed: isBooking ? null : () => controller.hideBookingOptions(),
isPhone: isPhone,
cornerRadius: cornerRadius,
disabled: isBooking,
),
],
)
) :
ActionButton(
text: 'book_now',
onPressed: isBooking ? null : () => controller.toggleBookingOptions(),
isPhone: isPhone,
cornerRadius: cornerRadius,
isLoading: isBooking && bookingDuration == null, // Only show loading if no specific duration button was clicked
);
}
return SizedBox.shrink();
}),
// Show cancel button and custom booking when reserved (meeting is active)
Obx(() {
if (controller.isReserved && !controller.isCheckInActive && controller.bookingEnabled) {
final isBooking = controller.isBooking.value;
return SpaceRow(
mainAxisSize: MainAxisSize.min,
spaceBetween: isPhone ? 12 : 16,
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (controller.canCancelCurrentEvent)
ActionButton(
text: 'cancel_event',
onPressed: controller.isCancelling.value ? null : () => controller.cancelCurrentEvent(),
textColor: Colors.white,
isPhone: isPhone,
cornerRadius: cornerRadius,
isLoading: controller.isCancelling.value,
),
if (controller.hasCustomBooking)
ActionButton(
text: 'reserve',
onPressed: isBooking ? null : () => controller.showCustomBookingModal(context, isPhone, cornerRadius),
isPhone: isPhone,
cornerRadius: cornerRadius,
disabled: isBooking,
isLoading: isBooking && controller.bookingDuration.value == null,
),
],
);
}
return SizedBox.shrink();
}),
Obx(() {
// Show check-in button separately when booking is not enabled or reserved
// Hide check-in button when booking options are expanded
final showBooking = !controller.isReserved && controller.bookingEnabled;
final showCheckIn = controller.isCheckInActive && controller.checkInEnabled;
// Don't show check-in button when booking options are expanded
if (showBooking && controller.showBookingOptions.value) {
return SizedBox.shrink();
}
if (showCheckIn && (controller.isReserved || !controller.bookingEnabled)) {
return ActionButton(
text: 'check_in',
onPressed: () => controller.checkIn(),
isPhone: isPhone,
cornerRadius: cornerRadius,
);
}
return SizedBox.shrink();
}),
]
);
}
}
================================================
FILE: app/lib/components/admin_actions.dart
================================================
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AdminActions extends StatelessWidget {
final dynamic controller;
final bool isPhone;
const AdminActions({
super.key,
required this.controller,
required this.isPhone,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
// Refresh button
Obx(() {
final iconSize = isPhone ? 20.0 : 28.0;
return Opacity(
opacity: 0.6,
child: SizedBox(
width: 24,
height: iconSize,
child: IconButton(
icon: controller.isRefreshing.value
? SizedBox(
width: iconSize - 8,
height: iconSize - 8,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Icon(Icons.refresh, size: iconSize, color: Colors.white),
onPressed: controller.isRefreshing.value ? null : () {
controller.refreshDisplayData();
},
tooltip: 'refresh_data'.tr,
padding: EdgeInsets.zero,
alignment: Alignment.center,
),
),
);
}),
SizedBox(width: 15),
// Logout/Switch room button
Opacity(
opacity: 0.6,
child: SizedBox(
width: 24,
height: isPhone ? 20 : 28,
child: IconButton(
icon: const Icon(Icons.logout, size: 24, color: Colors.white),
onPressed: () {
controller.switchRoom();
},
tooltip: 'switch_room'.tr,
padding: EdgeInsets.zero,
alignment: Alignment.center,
),
),
),
],
);
}
}
================================================
FILE: app/lib/components/authenticated_background.dart
================================================
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:spacepad/services/auth_service.dart';
import 'package:get/get.dart';
class AuthenticatedBackground extends StatefulWidget {
final String? imageUrl;
final Widget child;
final BorderRadius? borderRadius;
const AuthenticatedBackground({
Key? key,
this.imageUrl,
required this.child,
this.borderRadius,
}) : super(key: key);
@override
State<AuthenticatedBackground> createState() => _AuthenticatedBackgroundState();
}
class _AuthenticatedBackgroundState extends State<AuthenticatedBackground> {
ImageProvider? _imageProvider;
bool _hasError = false;
@override
void initState() {
super.initState();
if (widget.imageUrl != null) {
_loadImage();
}
}
@override
void didUpdateWidget(AuthenticatedBackground oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.imageUrl != widget.imageUrl) {
if (widget.imageUrl != null) {
_loadImage();
} else {
setState(() {
_hasError = false;
_imageProvider = null;
});
}
}
}
Future<void> _loadImage() async {
if (!mounted || widget.imageUrl == null) return;
setState(() {
_hasError = false;
});
try {
// Get authentication headers
final headers = <String, String>{
'Accept': 'application/json',
'Accept-Language': Get.locale?.languageCode ?? 'en',
};
if (AuthService.instance.getAuthToken() != null) {
headers['Authorization'] = 'Bearer ${AuthService.instance.getAuthToken()}';
}
// Make authenticated request with timeout
final response = await http.get(
Uri.parse(widget.imageUrl!),
headers: headers,
).timeout(
const Duration(seconds: 15),
onTimeout: () {
throw TimeoutException('Image load timeout after 15 seconds');
},
);
if (response.statusCode == 200) {
// Create image provider from bytes
_imageProvider = MemoryImage(response.bodyBytes);
if (mounted) {
setState(() {
_hasError = false;
});
}
} else {
throw Exception('Failed to load image: ${response.statusCode}');
}
} on TimeoutException {
if (mounted) {
setState(() {
_hasError = true;
});
}
return;
} catch (e) {
if (mounted) {
setState(() {
_hasError = true;
});
}
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: widget.borderRadius,
color: Colors.black,
image: _imageProvider != null && !_hasError
? DecorationImage(
image: _imageProvider!,
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black.withValues(alpha: 0.3),
BlendMode.srcOver,
),
)
: null,
),
child: widget.child,
);
}
}
================================================
FILE: app/lib/components/authenticated_image.dart
================================================
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:spacepad/services/auth_service.dart';
import 'package:get/get.dart';
class AuthenticatedImage extends StatefulWidget {
final String imageUrl;
final double? width;
final double? height;
final BoxFit fit;
final Widget? placeholder;
final Widget? errorWidget;
const AuthenticatedImage({
Key? key,
required this.imageUrl,
this.width,
this.height,
this.fit = BoxFit.cover,
this.placeholder,
this.errorWidget,
}) : super(key: key);
@override
State<AuthenticatedImage> createState() => _AuthenticatedImageState();
}
class _AuthenticatedImageState extends State<AuthenticatedImage> {
ImageProvider? _imageProvider;
bool _isLoading = true;
bool _hasError = false;
@override
void initState() {
super.initState();
_loadImage();
}
@override
void didUpdateWidget(AuthenticatedImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.imageUrl != widget.imageUrl) {
_loadImage();
}
}
Future<void> _loadImage() async {
if (!mounted) return;
setState(() {
_isLoading = true;
_hasError = false;
});
try {
// Get authentication headers
final headers = <String, String>{
'Accept': 'application/json',
'Accept-Language': Get.locale?.languageCode ?? 'en',
};
if (AuthService.instance.getAuthToken() != null) {
headers['Authorization'] = 'Bearer ${AuthService.instance.getAuthToken()}';
}
// Make authenticated request with timeout
final response = await http.get(
Uri.parse(widget.imageUrl),
headers: headers,
).timeout(
const Duration(seconds: 10),
onTimeout: () {
throw TimeoutException('Image load timeout after 10 seconds', const Duration(seconds: 10));
},
);
if (response.statusCode == 200) {
// Create image provider from bytes
_imageProvider = MemoryImage(response.bodyBytes);
if (mounted) {
setState(() {
_isLoading = false;
_hasError = false;
});
}
} else {
throw Exception('Failed to load image: ${response.statusCode}');
}
} on TimeoutException {
if (mounted) {
setState(() {
_isLoading = false;
_hasError = true;
});
}
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
_hasError = true;
});
}
}
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return widget.placeholder ??
Container(
width: widget.width,
height: widget.height,
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.grey),
),
),
);
}
if (_hasError || _imageProvider == null) {
return widget.errorWidget ?? SizedBox.shrink();
}
return Image(
image: _imageProvider!,
width: widget.width,
height: widget.height,
fit: widget.fit,
);
}
}
================================================
FILE: app/lib/components/calendar_modal.dart
================================================
import 'package:flutter/material.dart';
import 'package:spacepad/models/event_model.dart';
import 'package:spacepad/theme.dart';
import 'package:get/get.dart';
import 'package:spacepad/date_format_helper.dart';
import 'package:tailwind_components/tailwind_components.dart';
import 'package:spacepad/components/frosted_panel.dart';
class CalendarModal extends StatelessWidget {
final List<EventModel> events;
final DateTime selectedDate;
const CalendarModal({
super.key,
required this.events,
required this.selectedDate,
});
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Center(
child: SizedBox(
width: 800, // Make modal narrower
child: FrostedPanel(
borderRadius: 20,
blurIntensity: 18,
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Title and close icon
Padding(
padding: const EdgeInsets.fromLTRB(26, 20, 26, 20),
child: Row(
children: [
Expanded(
child: Text(
'todays_schedule'.tr,
style: TextStyle(
color: AppTheme.platinum,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: Icon(
Icons.close,
color: AppTheme.platinum,
size: 28,
),
splashRadius: 22,
),
],
),
),
// Removed date header
// Events list
Flexible(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: events.isEmpty
? SizedBox(
height: 200,
child: Center(
child: Text(
'no_events_today'.tr,
style: TextStyle(
color: AppTheme.platinum,
fontSize: 16,
),
),
),
)
: ListView.builder(
shrinkWrap: true,
itemCount: events.length,
itemBuilder: (context, index) {
final event = events[index];
return Container(
margin: const EdgeInsets.only(bottom: 18),
decoration: BoxDecoration(
color: TWColors.gray_800,
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: Colors.black
.withAlpha((0.1 * 255).toInt()),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 18, vertical: 16),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.schedule,
color: AppTheme.orange,
size: 18,
),
const SizedBox(width: 8),
Text(
'${formatTime(context, event.start)} - ${formatTime(context, event.end)}',
style: TextStyle(
color: AppTheme.platinum,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 8),
Text(
event.summary,
style: TextStyle(
color: Colors.white,
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
if ((event.location ?? '')
.trim()
.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
event.location!,
style: TextStyle(
color: AppTheme.platinum,
fontSize: 13,
fontWeight: FontWeight.w400,
),
overflow: TextOverflow.ellipsis,
),
],
],
),
),
);
},
),
),
),
],
),
),
),
),
);
}
}
================================================
FILE: app/lib/components/custom_booking_modal.dart
================================================
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:spacepad/components/frosted_panel.dart';
import 'package:spacepad/components/solid_button.dart';
import 'package:spacepad/date_format_helper.dart';
import 'package:spacepad/models/event_model.dart';
import 'package:spacepad/theme.dart';
import 'package:tailwind_components/tailwind_components.dart';
class CustomBookingModal extends StatefulWidget {
final dynamic controller;
final bool isPhone;
final double cornerRadius;
const CustomBookingModal({
super.key,
required this.controller,
required this.isPhone,
required this.cornerRadius,
});
@override
State<CustomBookingModal> createState() => _CustomBookingModalState();
}
class _CustomBookingModalState extends State<CustomBookingModal> {
late TextEditingController _titleController;
late DateTime _startTime;
late DateTime _endTime;
DateTime? _nextMeetingStart;
@override
void initState() {
super.initState();
final now = DateTime.now();
// Find the first available time slot
// If there's a current event, start time should be when it ends
// Otherwise, start from now
final currentEvent = widget.controller.currentEvent;
if (currentEvent != null && currentEvent.end.isAfter(now)) {
_startTime = currentEvent.end;
} else {
_startTime = now;
}
// Calculate default end time: 1 hour from start, or until next meeting if available
final upcomingEvents = widget.controller.upcomingEvents as List<EventModel>;
if (upcomingEvents.isNotEmpty) {
// Find the next meeting that starts after our start time
final nextMeetingAfterStart = upcomingEvents.where((event) => event.start.isAfter(_startTime)).firstOrNull;
if (nextMeetingAfterStart != null) {
_nextMeetingStart = nextMeetingAfterStart.start;
final oneHourFromStart = _startTime.add(const Duration(hours: 1));
_endTime = _nextMeetingStart!.isBefore(oneHourFromStart)
? _nextMeetingStart!
: oneHourFromStart;
} else {
// No meetings after start time, use 1 hour default
_nextMeetingStart = null;
_endTime = _startTime.add(const Duration(hours: 1));
}
} else {
_nextMeetingStart = null;
_endTime = _startTime.add(const Duration(hours: 1));
}
// Ensure end time is strictly after start time
_endTime = _endTime.isBefore(_startTime) || _endTime.isAtSameMomentAs(_startTime)
? _startTime.add(const Duration(minutes: 1))
: _endTime;
_titleController = TextEditingController(text: 'reserved'.tr);
}
@override
void dispose() {
_titleController.dispose();
super.dispose();
}
Future<void> _selectStartTime() async {
final now = DateTime.now();
// Ensure initial time is not in the past
final initialTime = _startTime.isBefore(now)
? TimeOfDay.fromDateTime(now)
: TimeOfDay.fromDateTime(_startTime);
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: initialTime,
);
if (picked != null) {
final selectedDateTime = DateTime(
now.year,
now.month,
now.day,
picked.hour,
picked.minute,
);
// Prevent selecting time in the past
final validStartTime = selectedDateTime.isBefore(now) ? now : selectedDateTime;
setState(() {
_startTime = validStartTime;
// Ensure end time is after start time
if (_endTime.isBefore(_startTime) || _endTime.isAtSameMomentAs(_startTime)) {
_endTime = _startTime.add(const Duration(hours: 1));
}
});
}
}
Future<void> _selectEndTime() async {
final now = DateTime.now();
// Ensure initial time is not in the past and is after start time
final minEndTime = _startTime.add(const Duration(minutes: 1));
final initialEndTime = _endTime.isBefore(minEndTime)
? TimeOfDay.fromDateTime(minEndTime)
: TimeOfDay.fromDateTime(_endTime);
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: initialEndTime,
);
if (picked != null) {
final selectedDateTime = DateTime(
now.year,
now.month,
now.day,
picked.hour,
picked.minute,
);
// Prevent selecting time in the past or before start time
final minValidTime = minEndTime.isAfter(now) ? minEndTime : now.add(const Duration(minutes: 1));
final validEndTime = selectedDateTime.isBefore(minValidTime)
? minValidTime
: (selectedDateTime.isAfter(_startTime) ? selectedDateTime : minValidTime);
setState(() {
_endTime = validEndTime;
});
}
}
void _setStartTimeToNow() {
setState(() {
final now = DateTime.now();
// Clamp start time to now if in the past
_startTime = now;
// Ensure end time is strictly after start time
final oneHourFromStart = _startTime.add(const Duration(hours: 1));
if (_nextMeetingStart != null && _nextMeetingStart!.isBefore(oneHourFromStart)) {
_endTime = _nextMeetingStart!.isAfter(_startTime)
? _nextMeetingStart!
: _startTime.add(const Duration(minutes: 1));
} else {
_endTime = _endTime.isBefore(_startTime) || _endTime.isAtSameMomentAs(_startTime)
? oneHourFromStart
: _endTime;
}
// Final check: ensure end time is strictly after start time
_endTime = _endTime.isBefore(_startTime) || _endTime.isAtSameMomentAs(_startTime)
? _startTime.add(const Duration(minutes: 1))
: _endTime;
});
}
void _setEndTimeToMax() {
if (_nextMeetingStart != null) {
setState(() {
final now = DateTime.now();
// Clamp start time to now if in the past
_startTime = _startTime.isBefore(now) ? now : _startTime;
// Set end time to next meeting start, but ensure it's strictly after start time
final oneHourFromStart = _startTime.add(const Duration(hours: 1));
_endTime = (_nextMeetingStart != null && _nextMeetingStart!.isBefore(oneHourFromStart))
? _nextMeetingStart!
: oneHourFromStart;
// Ensure end time is strictly after start time
_endTime = _endTime.isBefore(_startTime) || _endTime.isAtSameMomentAs(_startTime)
? _startTime.add(const Duration(minutes: 1))
: _endTime;
});
}
}
void _bookCustom() {
if (_titleController.text.trim().isEmpty) {
return;
}
// Clamp and normalize times before sending to backend
final now = DateTime.now();
// Clamp start time to now if in the past
final clampedStartTime = _startTime.isBefore(now) ? now : _startTime;
// Preserve next meeting clipping behavior if applicable
final oneHourFromStart = clampedStartTime.add(const Duration(hours: 1));
final endTimeWithClipping = (_nextMeetingStart != null && _nextMeetingStart!.isBefore(oneHourFromStart))
? _nextMeetingStart!
: oneHourFromStart;
// Ensure end time is strictly after start time (at least 1 minute after)
// Use max of endTimeWithClipping or minimum valid end time
final minValidEndTime = clampedStartTime.add(const Duration(minutes: 1));
final finalEndTime = endTimeWithClipping.isBefore(minValidEndTime) || endTimeWithClipping.isAtSameMomentAs(minValidEndTime)
? minValidEndTime
: endTimeWithClipping;
widget.controller.bookCustom(
_titleController.text.trim(),
clampedStartTime,
finalEndTime,
);
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Center(
child: SizedBox(
width: 800,
child: FrostedPanel(
borderRadius: 20,
blurIntensity: 18,
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Title and close button
Padding(
padding: const EdgeInsets.fromLTRB(26, 20, 26, 20),
child: Row(
children: [
Expanded(
child: Text(
'custom_booking'.tr,
style: TextStyle(
color: AppTheme.platinum,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: Icon(
Icons.close,
color: AppTheme.platinum,
size: 28,
),
splashRadius: 22,
),
],
),
),
// Meeting title input
Padding(
padding: const EdgeInsets.fromLTRB(26, 0, 26, 20),
child: SpaceCol(
spaceBetween: 8,
children: [
Text(
'meeting_title'.tr,
style: TextStyle(
color: AppTheme.platinum,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
TextField(
controller: _titleController,
style: TextStyle(
color: Colors.white,
fontSize: 17,
),
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: TWColors.gray_500),
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: TWColors.gray_800.withOpacity(0.5),
),
),
],
),
),
// Start and End time side by side
Padding(
padding: const EdgeInsets.fromLTRB(26, 0, 26, 20),
child: Row(
children: [
// Start time
Expanded(
child: SpaceCol(
spaceBetween: 8,
children: [
Text(
'start_time'.tr,
style: TextStyle(
color: AppTheme.platinum,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
Row(
children: [
Expanded(
child: GestureDetector(
onTap: _selectStartTime,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
decoration: BoxDecoration(
color: TWColors.gray_800.withOpacity(0.5),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: TWColors.gray_500),
),
child: Text(
formatTime(context, _startTime),
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
),
),
),
SizedBox(width: 8),
SolidButton(
text: 'now',
onPressed: _setStartTimeToNow,
fontSize: 15,
),
],
),
],
),
),
SizedBox(width: 16),
// End time
Expanded(
child: SpaceCol(
spaceBetween: 8,
children: [
Text(
'end_time'.tr,
style: TextStyle(
color: AppTheme.platinum,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
Row(
children: [
Expanded(
child: GestureDetector(
onTap: _selectEndTime,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
decoration: BoxDecoration(
color: TWColors.gray_800.withAlpha(128),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: TWColors.gray_500),
),
child: Text(
formatTime(context, _endTime),
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
),
),
),
SizedBox(width: 8),
SolidButton(
text: 'max',
onPressed: _nextMeetingStart != null ? _setEndTimeToMax : null,
fontSize: 15,
),
],
),
],
),
),
],
),
),
// Action buttons
Padding(
padding: const EdgeInsets.fromLTRB(26, 16, 26, 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SolidButton(
text: 'close',
onPressed: () => Navigator.of(context).pop(),
fontSize: 17,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
SizedBox(width: 12),
SolidButton(
text: 'book',
onPressed: _bookCustom,
fontSize: 17,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
],
),
),
],
),
),
),
),
);
}
}
================================================
FILE: app/lib/components/event_line.dart
================================================
import 'package:get/get.dart';
import 'package:spacepad/models/event_model.dart';
import 'package:flutter/material.dart';
import 'package:spacepad/date_format_helper.dart';
import 'package:tailwind_components/tailwind_components.dart';
class EventLine extends StatelessWidget {
const EventLine({super.key, required this.event});
final EventModel event;
bool _isPhone(BuildContext context) {
final shortestSide = MediaQuery.of(context).size.shortestSide;
return shortestSide < 600;
}
@override
Widget build(BuildContext context) {
final isPhone = _isPhone(context);
return SizedBox(
width: double.infinity,
child: SpaceRow(
spaceBetween: isPhone ? 5 : 10,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${'next'.tr}:',
style: TextStyle(
fontSize: isPhone ? 16 : 18,
fontWeight: FontWeight.bold,
color: Colors.white
)
),
Expanded(
child: Text(
'next_event_title'.trParams({
'start': formatTime(context, event.start),
'end': formatTime(context, event.end),
'summary': event.summary,
}),
style: TextStyle(
fontSize: isPhone ? 16 : 18,
fontWeight: FontWeight.w400,
color: Colors.white
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
);
}
}
================================================
FILE: app/lib/components/frosted_panel.dart
================================================
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:tailwind_components/tailwind_components.dart';
class FrostedPanel extends StatelessWidget {
final Widget child;
final double borderRadius;
final double blurIntensity;
final Color backgroundColor;
final EdgeInsetsGeometry? padding;
const FrostedPanel({
super.key,
required this.child,
this.borderRadius = 20,
this.blurIntensity = 18,
this.backgroundColor = const Color(0x14FFFFFF), // Colors.white.withAlpha((0.08 * 255).toInt())
this.padding,
});
/// Creates a frosted panel with gray background (for use with background images)
factory FrostedPanel.gray({
required Widget child,
double borderRadius = 20,
bool hasBackgroundImage = false,
EdgeInsetsGeometry? padding,
}) {
return FrostedPanel(
borderRadius: borderRadius,
blurIntensity: 0, // No blur for gray panels
backgroundColor: hasBackgroundImage
? TWColors.black.withValues(alpha: 0.8)
: TWColors.black.withValues(alpha: 0.1),
padding: padding,
child: child,
);
}
@override
Widget build(BuildContext context) {
final panel = Container(
decoration: BoxDecoration(
color: Colors.white.withAlpha((0.1 * 255).toInt()),
borderRadius: BorderRadius.circular(borderRadius),
),
padding: padding,
child: child,
);
// Only apply backdrop filter if blur intensity is greater than 0
if (blurIntensity > 0) {
return ClipRRect(
borderRadius: BorderRadius.circular(borderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: blurIntensity, sigmaY: blurIntensity),
child: panel,
),
);
}
return ClipRRect(
borderRadius: BorderRadius.circular(borderRadius),
child: panel,
);
}
}
================================================
FILE: app/lib/components/solid_button.dart
================================================
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tailwind_components/tailwind_components.dart';
class SolidButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
final double? fontSize;
final EdgeInsets? padding;
final double borderRadius;
const SolidButton({
super.key,
required this.text,
this.onPressed,
this.fontSize,
this.padding,
this.borderRadius = 8,
});
@override
Widget build(BuildContext context) {
return Opacity(
opacity: onPressed != null ? 1.0 : 0.5,
child: GestureDetector(
onTap: onPressed,
child: Container(
padding: padding ?? EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: TWColors.gray_800.withAlpha(128),
borderRadius: BorderRadius.circular(borderRadius),
border: Border.all(color: TWColors.gray_500),
),
child: Text(
text.tr,
style: TextStyle(
color: Colors.white,
fontSize: fontSize ?? 15,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
}
================================================
FILE: app/lib/components/spinner.dart
================================================
import 'package:flutter/material.dart';
import 'package:spacepad/theme.dart';
class Spinner extends StatelessWidget {
final double size;
final EdgeInsets? padding;
final double? thickness;
final Color? color;
const Spinner({super.key, required this.size, this.padding, this.thickness, this.color = AppTheme.oxford});
@override
Widget build(BuildContext context) {
return Container(
height: size,
width: size,
margin: padding,
child: CircularProgressIndicator(strokeWidth: thickness ?? 3, color: color),
);
}
}
================================================
FILE: app/lib/components/toast.dart
================================================
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:heroicons/heroicons.dart';
class Toast {
Toast._();
static void showSuccess(String message) {
_showSnackBar(message, HeroIcons.check, Colors.green);
}
static void showError(String message) {
_showSnackBar(message, HeroIcons.exclamationCircle, Colors.red);
}
static void _showSnackBar(String message, HeroIcons icon, Color color) async {
/// Small delay to make sure widget tree is built.
await Future.delayed(const Duration(milliseconds: 100));
Get.showSnackbar(
GetSnackBar(
messageText: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: Padding(
padding: const EdgeInsets.only(top: 3),
child: Text(message, style: const TextStyle(
color: Colors.black,
fontSize: 14,
height: 1.2,
fontWeight: FontWeight.w600,
)),
)),
const SizedBox(width: 2),
IconButton(
onPressed: () => Get.closeCurrentSnackbar(),
icon: const HeroIcon(HeroIcons.xMark, size: 20),
)
],
),
margin: const EdgeInsets.symmetric(horizontal: 35, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 7),
backgroundColor: Colors.white,
boxShadows: const [
BoxShadow(
color: Colors.black12,
spreadRadius: 1,
blurRadius: 2,
offset: Offset(0, 0), // changes position of shadow
),
],
icon: Padding(
padding: const EdgeInsets.only(left: 12, right: 8),
child: HeroIcon(icon, color: color, style: HeroIconStyle.solid, size: 26)
),
borderRadius: 10,
duration: const Duration(seconds: 4),
animationDuration: const Duration(milliseconds: 350),
forwardAnimationCurve: Curves.fastEaseInToSlowEaseOut,
reverseAnimationCurve: Curves.fastOutSlowIn,
borderWidth: 1,
borderColor: Colors.grey[300],
)
);
}
}
================================================
FILE: app/lib/controllers/dashboard_controller.dart
================================================
import 'dart:async';
import 'package:get/get.dart';
import 'package:spacepad/components/toast.dart';
import 'package:spacepad/models/event_model.dart';
import 'package:spacepad/models/display_data_model.dart';
import 'package:spacepad/models/event_status.dart';
import 'package:spacepad/services/display_service.dart';
import 'package:spacepad/services/auth_service.dart';
import 'package:spacepad/pages/display_page.dart';
import 'package:spacepad/models/device_model.dart';
import 'package:spacepad/models/display_model.dart';
import 'package:spacepad/models/display_settings_model.dart';
import 'package:spacepad/services/font_service.dart';
import 'package:flutter/material.dart';
import 'package:spacepad/components/custom_booking_modal.dart';
class DashboardController extends GetxController {
final RxBool loading = RxBool(true);
final RxList<EventModel> events = RxList();
final Rx<DateTime> time = Rx<DateTime>(DateTime.now());
final RxString displayId = RxString('');
// Global variables for device, display, and settings
DeviceModel? globalCurrentDevice;
DisplayModel? globalDisplay;
final Rx<DisplaySettingsModel?> globalSettings = Rx<DisplaySettingsModel?>(null);
// Reactive font family for UI updates
final RxString currentFontFamily = RxString('Inter');
Timer? _clock;
Timer? _dataTimer;
// Track refresh state to prevent spamming
final RxBool isRefreshing = RxBool(false);
DateTime? _lastRefreshTime;
static const int _refreshCooldownSeconds = 3;
@override
void onInit() async {
super.onInit();
updateTime();
// Check if display ID is set, redirect to display page if not
final displayIdResult = AuthService.instance.getCurrentDisplayId();
if (displayIdResult == null) {
Get.offAll(() => const DisplayPage());
return;
} else {
displayId.value = displayIdResult;
}
initializeTimers();
await fetchDisplayData();
// Preload fonts for better performance
await FontService.instance.preloadFonts();
loading.value = false;
}
void initializeTimers() {
final int millisecondsToNextSecond = DateTime.now().millisecond;
// Start a timer that aligns with the next second for data refresh (every 60 seconds)
Future.delayed(Duration(milliseconds: millisecondsToNextSecond), () {
_dataTimer = Timer.periodic(const Duration(seconds: 60), (timer) => fetchDisplayData());
});
_clock = Timer.periodic(const Duration(seconds: 1), (timer) => updateTime());
}
void updateTime() {
time.value = DateTime.now();
}
String get roomName {
return globalDisplay?.name ?? 'meeting_room'.tr;
}
String get title {
if (isReserved) {
return currentEvent!.summary;
}
if (isCheckInActive) {
return globalSettings.value?.textCheckin ?? 'check_in_now'.tr;
}
if (isTransitioning && !isReserved) {
return globalSettings.value?.textTransitioning ?? 'to_be_reserved'.tr;
}
return globalSettings.value?.textAvailable ?? 'available'.tr;
}
/// Returns the start and end DateTime of the current event, or null if not reserved.
Map<String, DateTime>? get meetingInfoTimes {
if (!isReserved) {
return null;
}
return {
'start': currentEvent!.start,
'end': currentEvent!.end,
};
}
String get subtitle {
if (isReserved && !isCheckInActive) {
final currentEventEnd = currentEvent!.end;
final totalMinutesLeft = currentEventEnd.difference(DateTime.now()).inMinutes;
final hoursLeft = (totalMinutesLeft / 60).floor();
final minutesLeft = (totalMinutesLeft - (hoursLeft * 60)).floor() + 1;
return totalMinutesLeft < 60 ?
'x_minutes_left'.trParams({'minutes': minutesLeft.toString()}) :
'x_hours_x_minutes_left'.trParams({'hours': hoursLeft.toString(), 'minutes': minutesLeft.toString()});
}
if (isCheckInActive && currentEvent != null) {
final totalMinutesLeft = currentEvent!.start.add(Duration(minutes: checkInGracePeriod)).difference(DateTime.now()).inMinutes;
final hoursLeft = (totalMinutesLeft / 60).floor();
final minutesLeft = (totalMinutesLeft - (hoursLeft * 60)).floor() + 1;
return 'check_in_within_x_minutes'.trParams({'minutes': minutesLeft.toString()});
}
if (isCheckInActive && upcomingEvents.isNotEmpty) {
final upcomingMeeting = upcomingEvents.first;
final totalMinutesLeft = upcomingMeeting.start.difference(DateTime.now()).inMinutes;
final hoursLeft = (totalMinutesLeft / 60).floor();
final minutesLeft = (totalMinutesLeft - (hoursLeft * 60)).floor() + 1;
return 'x_starts_in_x_minutes'.trParams({'meeting': upcomingMeeting.summary, 'minutes': minutesLeft.toString()});
}
if (upcomingEvents.isNotEmpty) {
final upcomingStart = upcomingEvents.first.start;
final totalMinutesLeft = upcomingStart.difference(DateTime.now()).inMinutes;
final hoursLeft = (totalMinutesLeft / 60).floor();
final minutesLeft = (totalMinutesLeft - (hoursLeft * 60)).floor() + 1;
return totalMinutesLeft < 60 ?
'for_x_minutes'.trParams({'minutes': minutesLeft.toString()}) :
'for_x_hours_x_minutes'.trParams({'hours': hoursLeft.toString(), 'minutes': minutesLeft.toString()});
}
return 'till_end_of_day'.tr;
}
bool get isReserved {
return currentEvent != null;
}
bool get isTransitioning {
if (checkInEnabled) {
return false;
}
if (isReserved) {
final currentEventEnd = currentEvent!.end;
final minutesLeft = currentEventEnd.difference(DateTime.now()).inMinutes;
return minutesLeft < 10;
}
if (upcomingEvents.isNotEmpty) {
final upcomingStart = upcomingEvents.first.start;
final minutesLeft = upcomingStart.difference(DateTime.now()).inMinutes;
return minutesLeft < 10;
}
return false;
}
// Returns true if there is an event with checkInRequired and we are within its check-in window (before/after start)
bool get isCheckInActive {
if (!checkInEnabled) {
return false;
}
return checkInEvent != null;
}
EventModel? get checkInEvent {
final now = DateTime.now();
return events.firstWhereOrNull((e) {
if (e.checkInRequired != true) return false;
final start = e.start;
final windowStart = start.subtract(Duration(minutes: checkInMinutes));
final windowEnd = start.add(Duration(minutes: checkInGracePeriod));
return now.isAfter(windowStart) && now.isBefore(windowEnd);
});
}
EventModel? get currentEvent {
DateTime now = DateTime.now();
return events.where((e) => now.isAfter(e.start) && now.isBefore(e.end)).firstOrNull;
}
List<EventModel> get upcomingEvents {
List<EventModel> nextEvents = events.where((e) => e.start.isAfter(DateTime.now())).toList();
nextEvents.sort((a, b) => a.start.compareTo(b.start));
return nextEvents;
}
Future<void> fetchDisplayData() async {
try {
DisplayDataModel displayData = await DisplayService.instance.getDisplayData(displayId.value);
// Update global device, display, and settings
if (AuthService.instance.currentDevice.value != null) {
globalCurrentDevice = AuthService.instance.currentDevice.value;
globalCurrentDevice!.display = displayData.display;
globalDisplay = globalCurrentDevice!.display;
globalSettings.value = globalDisplay?.settings;
// Update reactive font family to trigger UI rebuild
final newFontFamily = globalSettings.value?.fontFamily ?? 'Inter';
if (currentFontFamily.value != newFontFamily) {
currentFontFamily.value = newFontFamily;
// Reload the font when settings change
await FontService.instance.reloadFont(newFontFamily);
}
AuthService.instance.currentDevice.refresh();
}
// Update events
events.value = displayData.events
.where((e) => e.status != EventStatus.cancelled)
.map((e) {
e.summary = getDisplayableSummary(e);
return e;
})
.toList();
} catch (e) {
Toast.showError('could_not_load_data'.tr);
}
}
void switchRoom() {
_clock?.cancel();
_dataTimer?.cancel();
Get.offAll(() => const DisplayPage());
}
// Manually refresh display data with cooldown to prevent spamming
Future<void> refreshDisplayData() async {
// Check if we're already refreshing
if (isRefreshing.value) {
return;
}
// Check cooldown period
if (_lastRefreshTime != null) {
final secondsSinceLastRefresh = DateTime.now().difference(_lastRefreshTime!).inSeconds;
if (secondsSinceLastRefresh < _refreshCooldownSeconds) {
return;
}
}
isRefreshing.value = true;
_lastRefreshTime = DateTime.now();
try {
await fetchDisplayData();
Toast.showSuccess('display_data_refreshed'.tr);
} finally {
isRefreshing.value = false;
}
}
Future<void> bookRoom(int duration) async {
if (isBooking.value) return; // Prevent multiple simultaneous bookings
try {
isBooking.value = true;
bookingDuration.value = duration; // Track which button was clicked
final summary = 'reserved'.tr;
await DisplayService.instance.book(displayId.value, duration, summary: summary);
await fetchDisplayData();
Toast.showSuccess('room_booked'.tr);
// Cancel the booking options timer since user took action
_bookingOptionsTimer?.cancel();
showBookingOptions.value = false;
} catch (e) {
Toast.showError('could_not_book_room'.tr);
} finally {
isBooking.value = false;
bookingDuration.value = null; // Clear the tracked duration
}
}
void showCustomBookingModal(BuildContext context, bool isPhone, double cornerRadius) {
showDialog(
context: context,
builder: (context) => CustomBookingModal(
controller: this,
isPhone: isPhone,
cornerRadius: cornerRadius,
),
);
}
Future<void> bookCustom(String title, DateTime startTime, DateTime endTime) async {
isBooking.value = true;
try {
await DisplayService.instance.bookCustom(displayId.value, title, startTime, endTime);
await fetchDisplayData();
Toast.showSuccess('room_booked'.tr);
// Cancel the booking options timer since user took action
_bookingOptionsTimer?.cancel();
showBookingOptions.value = false;
} catch (e) {
Toast.showError('could_not_book_room'.tr);
} finally {
isBooking.value = false;
bookingDuration.value = null; // Clear the tracked duration
}
}
Future<void> cancelCurrentEvent() async {
if (isCancelling.value) return; // Prevent multiple simultaneous cancellations
try {
isCancelling.value = true;
if (currentEvent != null) {
await DisplayService.instance.cancelEvent(displayId.value, currentEvent!.id);
await fetchDisplayData();
Toast.showSuccess('event_cancelled'.tr);
}
} catch (e) {
Toast.showError('could_not_cancel_event'.tr);
} finally {
isCancelling.value = false;
}
}
// Check if booking should be displayed based on display settings
bool get bookingEnabled {
return globalSettings.value?.bookingEnabled ?? false;
}
// Check if custom booking is available (server capability)
bool get hasCustomBooking {
return globalSettings.value?.hasCustomBooking ?? false;
}
// Check if current event can be cancelled based on cancel permission setting
bool get canCancelCurrentEvent {
// Early return: cannot cancel if there's no current event
if (currentEvent == null) {
return false;
}
final cancelPermission = globalSettings.value?.cancelPermission ?? 'all';
if (cancelPermission == 'none') {
return false;
}
if (cancelPermission == 'tablet_only') {
// Only allow cancelling if the event was booked via tablet
// currentEvent is guaranteed to be non-null at this point
return currentEvent!.isTabletBooking;
}
// Default: 'all' - allow cancelling any event
// currentEvent is guaranteed to be non-null at this point
return true;
}
// Get border width based on border thickness setting
double getBorderWidth() {
final borderThickness = globalSettings.value?.borderThickness ?? 'medium';
switch (borderThickness) {
case 'small':
return 1.33;
case 'large':
return 2.67;
case 'medium':
default:
return 2.0;
}
}
bool get calendarEnabled {
return globalSettings.value?.calendarEnabled ?? false;
}
// Track if booking options are shown
final RxBool showBookingOptions = RxBool(false);
// Loading states for actions
final RxBool isBooking = RxBool(false);
final Rx<int?> bookingDuration = Rx<int?>(null); // Track which duration button was clicked
final RxBool isCancelling = RxBool(false);
// Timer for booking options timeout
Timer? _bookingOptionsTimer;
// Track if admin actions are temporarily visible
final RxBool showAdminActionsTemporarily = RxBool(false);
// Timer for admin actions timeout
Timer? _adminActionsTimer;
// Timer for long press detection (3 seconds)
Timer? _longPressTimer;
// Show booking options with 30-second timeout
void toggleBookingOptions() {
showBookingOptions.value = true;
// Cancel any existing timer
_bookingOptionsTimer?.cancel();
// Set a 30-second timeout to automatically hide booking options
_bookingOptionsTimer = Timer(const Duration(seconds: 30), () {
showBookingOptions.value = false;
});
}
// Hide booking options
void hideBookingOptions() {
showBookingOptions.value = false;
_bookingOptionsTimer?.cancel();
}
// Start long press timer (3 seconds)
void startLongPressTimer() {
// Cancel any existing timer
_longPressTimer?.cancel();
// Set a 3-second timer to trigger reveal
_longPressTimer = Timer(const Duration(seconds: 3), () {
revealAdminActionsTemporarily();
});
}
// Cancel long press timer
void cancelLongPressTimer() {
_longPressTimer?.cancel();
}
// Show admin actions temporarily (30 seconds)
void revealAdminActionsTemporarily() {
showAdminActionsTemporarily.value = true;
// Show notification with duration
Toast.showSuccess('admin_actions_enabled'.trParams({'seconds': '30'}));
// Cancel any existing timer
_adminActionsTimer?.cancel();
// Set a 30-second timeout to automatically hide admin actions
_adminActionsTimer = Timer(const Duration(seconds: 30), () {
showAdminActionsTemporarily.value = false;
});
}
int get checkInGracePeriod {
return globalSettings.value?.checkInGracePeriod ?? 5;
}
bool get checkInEnabled {
return globalSettings.value?.checkInEnabled ?? false;
}
int get checkInMinutes {
return globalSettings.value?.checkInMinutes ?? 15;
}
void checkIn() async {
try {
await DisplayService.instance.checkInToEvent(displayId.value, checkInEvent!.id);
await fetchDisplayData();
Toast.showSuccess('checked_in'.tr);
} catch (e) {
Toast.showError('could_not_check_in'.tr);
}
}
List<int> get availableBookingDurations {
final base = [15, 30, 60];
if (isCheckInActive) {
return base.where((min) => min <= checkInGracePeriod).toList();
}
if (upcomingEvents.isNotEmpty) {
final nextEvent = upcomingEvents.first;
final minutesUntilNext = nextEvent.start.difference(DateTime.now()).inMinutes;
return base.where((min) => min <= minutesUntilNext).toList();
}
return base;
}
/// Returns the summary to display for an event, respecting showMeetingTitle
String getDisplayableSummary(EventModel event) {
if (globalSettings.value?.showMeetingTitle == false) {
return getReservedText();
}
return event.summary;
}
String getReservedText() {
return globalSettings.value?.textReserved ?? 'reserved'.tr;
}
@override
void dispose() {
_clock?.cancel();
_dataTimer?.cancel();
_bookingOptionsTimer?.cancel();
_adminActionsTimer?.cancel();
_longPressTimer?.cancel();
super.dispose();
}
}
================================================
FILE: app/lib/controllers/display_controller.dart
================================================
import 'package:get/get.dart';
import 'package:spacepad/models/display_model.dart';
import 'package:spacepad/components/toast.dart';
import 'package:spacepad/services/device_service.dart';
import 'package:spacepad/services/display_service.dart';
import 'package:spacepad/services/auth_service.dart';
class DisplayController extends GetxController {
final RxBool loading = RxBool(false);
final RxList<DisplayModel> displays = RxList();
final Rx<DisplayModel?> selectedDisplay = Rx(null);
@override
void onInit() {
super.onInit();
getDisplays();
}
void onSelect(val) {
selectedDisplay.value = val;
}
bool get submitActive {
return selectedDisplay.value != null;
}
Future<void> getDisplays() async {
if (loading.value) return;
loading.value = true;
try {
displays.value = await DisplayService.instance.getDisplays();
} catch (e) {
Toast.showError('could_not_load_displays'.tr);
}
loading.value = false;
}
Future<void> submit() async {
if (loading.value) return;
loading.value = true;
try {
await DeviceService.instance.changeDisplay(selectedDisplay.value!.id);
// Save the selected display ID to local storage
await AuthService.instance.setCurrentDisplayId(selectedDisplay.value!.id);
await AuthService.instance.verify();
} catch (e) {
Toast.showError('check_connection'.tr);
}
loading.value = false;
}
}
================================================
FILE: app/lib/controllers/login_controller.dart
================================================
import 'dart:io';
import 'dart:core';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter_udid/flutter_udid.dart';
import 'package:get/get.dart';
import 'package:spacepad/services/auth_service.dart';
import 'package:spacepad/components/toast.dart';
import 'package:spacepad/services/server_service.dart';
import 'package:spacepad/services/api_service.dart';
class LoginController extends GetxController {
final AuthService _authService = AuthService.instance;
final ServerService _serverService = ServerService();
final RxBool loading = false.obs;
final RxBool isSelfHosted = false.obs;
final RxString url = ''.obs;
final RxString code = ''.obs;
final RxBool submitActive = false.obs;
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
void toggleSelfHosted(bool value) {
isSelfHosted.value = value;
_updateSubmitActive();
}
void urlChanged(String value) {
url.value = value;
_updateSubmitActive();
}
void codeChanged(String value) {
code.value = value;
_updateSubmitActive();
}
void _updateSubmitActive() {
if (isSelfHosted.value) {
submitActive.value = url.value.isNotEmpty && code.value.length == 6;
} else {
submitActive.value = code.value.length == 6;
}
}
bool _isValidUrl(String url) {
try {
final uri = Uri.parse(url);
return uri.isAbsolute && (uri.scheme == 'http' || uri.scheme == 'https');
} catch (e) {
return false;
}
}
Future<String?> getDeviceId() async {
return await FlutterUdid.udid;
}
Future<String?> getDeviceName() async {
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
return androidInfo.model;
}
if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
return iosInfo.utsname.machine;
}
return null;
}
Future<void> submit() async {
if (loading.value) return;
loading.value = true;
try {
if (isSelfHosted.value) {
if (!_isValidUrl(url.value)) {
Toast.showError('invalid_url'.tr);
return;
}
var trimmedUrl = url.value.endsWith('/') ? url.value.substring(0, url.value.length - 1) : url.value;
if (!await _serverService.isServerReachable(trimmedUrl)) {
Toast.showError('server_unreachable'.tr);
return;
}
// Set the custom base URL for the API service
await ApiService.setBaseUrl(trimmedUrl);
} else {
await ApiService.resetToServerBaseUrl();
}
final deviceId = await getDeviceId() ?? 'Unknown device';
final deviceName = await getDeviceName() ?? 'Unknown model';
await _authService.login(code.value, deviceId, deviceName);
} catch (e) {
Toast.showError('login_failed'.tr);
} finally {
loading.value = false;
}
}
}
================================================
FILE: app/lib/date_format_helper.dart
================================================
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
String formatTime(BuildContext context, DateTime time) {
final use24Hour = MediaQuery.of(context).alwaysUse24HourFormat;
return use24Hour
? DateFormat.Hm().format(time) // 24-hour format
: DateFormat.jm().format(time); // 12-hour format
}
================================================
FILE: app/lib/exceptions/api_exception.dart
================================================
import 'dart:convert';
import 'package:http/http.dart';
class ApiException implements Exception {
final int code;
final String? message;
final Map? errors;
ApiException({required this.code, this.message, this.errors});
static ApiException fromResponse(Response response) {
return ApiException(
code: response.statusCode,
message: jsonDecode(response.body)['message'],
errors: _mapErrors(jsonDecode(response.body)['errors'])
);
}
static Map? _mapErrors(Map? errors) {
return errors?.map((key, value) {
return MapEntry(key, value?.isNotEmpty ? value.first : '');
});
}
@override
String toString() => 'ApiException: $code - $message';
}
================================================
FILE: app/lib/main.dart
================================================
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:spacepad/pages/login_page.dart';
import 'package:spacepad/pages/splash_page.dart';
import 'package:spacepad/theme.dart';
import 'package:spacepad/services/auth_service.dart';
import 'package:spacepad/translations/translations.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:timezone/data/latest.dart' as tz;
// Supported locales list
const List<Locale> supportedLocales = [
Locale('en'),
Locale('nl'),
Locale('fr'),
Locale('es'),
Locale('de'),
Locale('sv'),
];
// Helper function to validate if a locale is exactly supported
bool isLocaleSupported(Locale locale) {
return supportedLocales.any((supportedLocale) =>
supportedLocale.languageCode == locale.languageCode &&
supportedLocale.countryCode == locale.countryCode);
}
// Helper function to get the best matching locale
Locale getBestMatchingLocale(Locale? requestedLocale) {
if (requestedLocale == null) {
return const Locale('en');
}
// First try exact match (both language and country code match)
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == requestedLocale.languageCode &&
supportedLocale.countryCode == requestedLocale.countryCode) {
return supportedLocale;
}
}
// Try to find a locale with the same language code (return supported locale, not original)
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == requestedLocale.languageCode) {
return supportedLocale;
}
}
// Fallback to English
return const Locale('en');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
await AuthService.instance.initialise();
tz.initializeTimeZones();
WakelockPlus.enable();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
// Set a valid locale based on device locale or fallback to English
final deviceLocale = Get.deviceLocale;
final validLocale = getBestMatchingLocale(deviceLocale);
// Debug information (remove in production)
if (deviceLocale != null) {
print('Device locale: ${deviceLocale.languageCode}_${deviceLocale.countryCode}');
print('Selected locale: ${validLocale.languageCode}');
print('Is supported: ${isLocaleSupported(deviceLocale)}');
}
Get.updateLocale(validLocale);
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
// Resolve locale consistently using the same logic as in main()
final resolvedLocale = getBestMatchingLocale(Get.locale);
return GetMaterialApp(
themeMode: ThemeMode.light,
theme: AppTheme.data,
initialRoute: '/',
transitionDuration: Duration.zero,
translations: AppTranslations(),
locale: resolvedLocale,
fallbackLocale: const Locale('en'),
supportedLocales: supportedLocales,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
debugShowCheckedModeBanner: false,
getPages: [
GetPage(name: '/', page: () {
if (AuthService.instance.getAuthToken() != null) {
return const SplashPage();
}
return const LoginPage();
})
],
);
}
}
================================================
FILE: app/lib/models/device_model.dart
================================================
import 'package:spacepad/models/user_model.dart';
import 'package:spacepad/models/display_model.dart';
class DeviceModel {
final String id;
final String name;
UserModel? user;
DisplayModel? display;
DeviceModel({required this.id, required this.name, required this.user, this.display});
factory DeviceModel.fromJson(Map data) {
return DeviceModel(
id: data['id'],
name: data['name'],
user: data['user'] != null ? UserModel.fromJson(data['user']) : null,
display: data['display'] != null ? DisplayModel.fromJson(data['display']) : null,
);
}
}
================================================
FILE: app/lib/models/display_data_model.dart
================================================
import 'display_model.dart';
import 'event_model.dart';
class DisplayDataModel {
DisplayModel? display;
List<EventModel> events;
DisplayDataModel({
required this.display,
required this.events,
});
factory DisplayDataModel.fromJson(Map<String, dynamic> data) {
return DisplayDataModel(
display: DisplayModel.fromJson(data['display']),
events: (data['events'] as List)
.map((e) => EventModel.fromJson(e))
.toList(),
);
}
factory DisplayDataModel.fromEventsJson(List data) {
return DisplayDataModel(
display: null,
events: data
.map((e) => EventModel.fromJson(e))
.toList(),
);
}
Map<String, dynamic> toJson() {
return {
'display': display?.toJson(),
'events': events.map((e) => e.toJson()).toList(),
};
}
}
================================================
FILE: app/lib/models/display_model.dart
================================================
import 'display_settings_model.dart';
class DisplayModel {
String id;
String name;
DisplaySettingsModel settings;
DisplayModel({
required this.id,
required this.name,
required this.settings,
});
factory DisplayModel.fromJson(Map data) {
return DisplayModel(
id: data['id'],
name: data['name'],
settings: DisplaySettingsModel.fromJson(data['settings'] ?? {}),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'settings': settings.toJson(),
};
}
}
================================================
FILE: app/lib/models/display_settings_model.dart
================================================
class DisplaySettingsModel {
bool checkInEnabled;
bool bookingEnabled;
bool hasCustomBooking;
int checkInGracePeriod;
int checkInMinutes;
bool calendarEnabled;
bool hideAdminActions;
String? textAvailable;
String? textTransitioning;
String? textReserved;
String? textCheckin;
bool showMeetingTitle;
String? logoUrl;
String? backgroundImageUrl;
String fontFamily;
String cancelPermission;
String borderThickness;
DisplaySettingsModel({
required this.checkInEnabled,
required this.bookingEnabled,
required this.hasCustomBooking,
required this.checkInGracePeriod,
required this.checkInMinutes,
required this.calendarEnabled,
required this.hideAdminActions,
this.textAvailable,
this.textTransitioning,
this.textReserved,
this.textCheckin,
required this.showMeetingTitle,
this.logoUrl,
this.backgroundImageUrl,
required this.fontFamily,
this.cancelPermission = 'all',
this.borderThickness = 'medium',
});
factory DisplaySettingsModel.fromJson(Map data) {
return DisplaySettingsModel(
checkInEnabled: data['check_in_enabled'] ?? false,
bookingEnabled: data['booking_enabled'] ?? false,
hasCustomBooking: data['has_custom_booking'] ?? false,
checkInGracePeriod: data['check_in_grace_period'] ?? 5,
checkInMinutes: data['check_in_minutes'] ?? 15,
calendarEnabled: data['calendar_enabled'] ?? false,
hideAdminActions: data['hide_admin_actions'] ?? false,
textAvailable: data['text_available'],
textTransitioning: data['text_transitioning'],
textReserved: data['text_reserved'],
textCheckin: data['text_checkin'],
showMeetingTitle: data['show_meeting_title'] ?? true,
logoUrl: data['logo_url'],
backgroundImageUrl: data['background_image_url'],
fontFamily: data['font_family'] ?? 'Inter',
cancelPermission: data['cancel_permission'] ?? 'all',
borderThickness: data['border_thickness'] ?? 'medium',
);
}
Map<String, dynamic>? toJson() {
return {
'check_in_enabled': checkInEnabled,
'booking_enabled': bookingEnabled,
'has_custom_booking': hasCustomBooking,
'check_in_grace_period': checkInGracePeriod,
'check_in_minutes': checkInMinutes,
'calendar_enabled': calendarEnabled,
'hide_admin_actions': hideAdminActions,
'text_available': textAvailable,
'text_transitioning': textTransitioning,
'text_reserved': textReserved,
'text_checkin': textCheckin,
'show_meeting_title': showMeetingTitle,
'logo_url': logoUrl,
'background_image_url': backgroundImageUrl,
'font_family': fontFamily,
};
}
}
================================================
FILE: app/lib/models/event_model.dart
================================================
import 'event_status.dart';
class EventModel {
String id;
EventStatus status;
String summary;
String? location;
String? description;
DateTime start;
DateTime end;
String? timezone;
bool isCheckedIn;
bool checkInRequired;
String? source;
bool isTabletBooking;
EventModel({
required this.id,
required this.status,
required this.summary,
this.location,
this.description,
required this.start,
required this.end,
this.timezone,
this.isCheckedIn = false,
this.checkInRequired = false,
this.source,
this.isTabletBooking = false,
});
factory EventModel.fromJson(Map<String, dynamic> data) {
return EventModel(
id: data['id'],
status: eventStatusFromString(data['status']),
summary: data['summary'],
location: data['location'],
description: data['description'],
start: DateTime.parse(data['start']).toLocal(),
end: DateTime.parse(data['end']).toLocal(),
timezone: data['timezone'],
isCheckedIn: data['checkedInAt'] != null,
checkInRequired: data['checkInRequired'] ?? false,
source: data['source'],
isTabletBooking: data['isTabletBooking'] ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'status': eventStatusToString(status),
'summary': summary,
'location': location,
'description': description,
'start': start.toIso8601String(),
'end': end.toIso8601String(),
'timezone': timezone,
'isCheckedIn': isCheckedIn,
'checkInRequired': checkInRequired,
};
}
}
================================================
FILE: app/lib/models/event_status.dart
================================================
enum EventStatus { confirmed, tentative, cancelled }
EventStatus eventStatusFromString(String? value) {
switch (value) {
case 'tentative':
return EventStatus.tentative;
case 'cancelled':
return EventStatus.cancelled;
case 'confirmed':
default:
return EventStatus.confirmed;
}
}
String eventStatusToString(EventStatus status) {
switch (status) {
case EventStatus.tentative:
return 'tentative';
case EventStatus.cancelled:
return 'cancelled';
case EventStatus.confirmed:
default:
return 'confirmed';
}
}
================================================
FILE: app/lib/models/user_model.dart
================================================
class UserModel {
String id;
String name;
String email;
UserModel({
required this.id,
required this.name,
required this.email,
});
factory UserModel.fromJson(Map data) {
return UserModel(
id: data['id'],
name: data['name'],
email: data['email'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
};
}
}
================================================
FILE: app/lib/pages/dashboard_page.dart
================================================
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:spacepad/components/event_line.dart';
import 'package:spacepad/components/spinner.dart';
import 'package:spacepad/controllers/dashboard_controller.dart';
import 'package:spacepad/date_format_helper.dart';
import 'package:spacepad/models/event_model.dart';
import 'package:get/get.dart';
import 'package:spacepad/theme.dart';
import 'package:tailwind_components/tailwind_components.dart';
import 'dart:math' show max;
import 'package:spacepad/components/action_panel.dart';
import 'package:spacepad/components/calendar_modal.dart';
import 'package:spacepad/components/authenticated_image.dart';
import 'package:spacepad/components/authenticated_background.dart';
import 'package:spacepad/services/font_service.dart';
import 'package:spacepad/components/frosted_panel.dart';
import 'package:spacepad/components/admin_actions.dart';
class DashboardPage extends StatefulWidget {
const DashboardPage({super.key});
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
bool _isPhone(BuildContext context) {
final shortestSide = MediaQuery.of(context).size.shortestSide;
// Consider devices with shortestSide < 600 as phones only
return shortestSide < 600;
}
bool _isPortrait(BuildContext context) {
final size = MediaQuery.of(context).size;
return size.height > size.width;
}
double _getCornerRadius(BuildContext context) {
// Get the top padding which includes the notch area
final topPadding = MediaQuery.of(context).padding.top;
// The corner radius is typically around 40-50% of the top padding
// We'll use 45% as a good middle ground
final cornerRadius = max(topPadding * 0.45, 10.0);
return cornerRadius;
}
double _getContainerPadding(BuildContext context, DashboardController controller) {
final size = MediaQuery.of(context).size;
final shortestSide = size.shortestSide;
final isPortrait = size.height > size.width;
// Base padding on shortest side, increase for portrait
final basePadding = shortestSide * 0.02; // 2% of shortest side
final portraitMultiplier = isPortrait ? 1.2 : 1.1;
// Adjust padding based on border thickness setting
// Border thickness affects the visual border created by padding
final borderThickness = controller.getBorderWidth();
final borderMultiplier = borderThickness / 2.0; // Normalize to 2.0 (medium) as baseline
return basePadding * portraitMultiplier * borderMultiplier;
}
EdgeInsets _getInnerPadding(BuildContext context) {
final size = MediaQuery.of(context).size;
final shortestSide = size.shortestSide;
final isPortrait = size.height > size.width;
// Base padding on shortest side, increase for portrait
final horizontalBase = shortestSide * 0.033; // ~3.3% of shortest side
final verticalBase = shortestSide * 0.025; // ~2.5% of shortest side
final portraitMultiplier = isPortrait ? 1.2 : 1.4;
return EdgeInsets.fromLTRB(
horizontalBase * portraitMultiplier,
verticalBase * portraitMultiplier,
horizontalBase * portraitMultiplier,
verticalBase * portraitMultiplier,
);
}
@override
Widget build(BuildContext context) {
DashboardController controller = Get.put(DashboardController());
final isPhone = _isPhone(context);
final isPortrait = _isPortrait(context);
final cornerRadius = _getCornerRadius(context);
if (kDebugMode) print('isPhone: $isPhone');
if (kDebugMode) print('isPortrait: $isPortrait');
if (kDebugMode) print('cornerRadius: $cornerRadius');
return Scaffold(
backgroundColor: AppTheme.black,
body: Obx(() => controller.loading.value ?
Center(
child: Spinner(size: 40, thickness: 4, color: AppTheme.platinum),
) :
Container(
height: double.infinity,
width: double.infinity,
color: controller.isTransitioning || controller.isCheckInActive ?
TWColors.amber_500 :
(controller.isReserved ? TWColors.rose_600 : TWColors.green_600),
padding: EdgeInsets.all(_getContainerPadding(context, controller)),
child: AuthenticatedBackground(
imageUrl: controller.globalSettings.value?.backgroundImageUrl,
borderRadius: BorderRadius.circular(cornerRadius),
child: Padding(
padding: _getInnerPadding(context),
child: Stack(
children: [
Align(
alignment: Alignment.topLeft,
child: Obx(() => Text(
formatTime(context, controller.time.value),
style: FontService.instance.getTextStyle(
fontFamily: controller.currentFontFamily.value,
fontSize: isPhone ? 20 : 28,
fontWeight: FontWeight.w500,
color: TWColors.white,
)
))
),
Align(
alignment: Alignment.topRight,
child: Obx(() {
final hideAdminActions = controller.globalSettings.value?.hideAdminActions ?? false;
final showTemporarily = controller.showAdminActionsTemporarily.value;
final shouldShowAdminActions = !hideAdminActions || showTemporarily;
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Admin actions component (refresh and logout buttons)
if (shouldShowAdminActions) AdminActions(
controller: controller,
isPhone: isPhone,
),
if (shouldShowAdminActions) SizedBox(width: 15),
GestureDetector(
onLongPressStart: (details) {
if (hideAdminActions) {
controller.startLongPressTimer();
}
},
onLongPressEnd: (details) {
if (hideAdminActions) {
controller.cancelLongPressTimer();
}
},
child: Text(
controller.roomName,
style: FontService.instance.getTextStyle(
fontFamily: controller.currentFontFamily.value,
fontSize: isPhone ? 20 : 28,
fontWeight: FontWeight.w500,
color: TWColors.white,
)
),
),
],
);
}),
),
SpaceCol(
spaceBetween: _getContainerPadding(context, controller) * 1.75, // Proportional to container padding
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SpaceCol(
spaceBetween: controller.meetingInfoTimes != null ? (isPhone ? 5 : 10) : 0,
children: [
Obx(() {
final logoUrl = controller.globalSettings.value?.logoUrl;
if (logoUrl != null) {
return Container(
margin: EdgeInsets.only(bottom: isPhone ? 20 : 10),
child: AuthenticatedImage(
imageUrl: logoUrl,
height: isPhone ? 24 : 36,
fit: BoxFit.contain,
placeholder: Container(
height: isPhone ? 24 : 36,
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(TWColors.gray_300),
),
),
),
errorWidget: SizedBox.shrink(), // Hide logo if it fails to load
),
);
}
return SizedBox.shrink();
}),
Obx(() => Text(
controller.title,
style: FontService.instance.getTextStyle(
fontFamily: controller.currentFontFamily.value,
fontSize: isPhone ? 30 : 50,
fontWeight: FontWeight.w700,
color: Colors.white,
)
)),
SpaceRow(
spaceBetween: isPhone ? 10 : 20,
children: [
if (controller.meetingInfoTimes != null) FrostedPanel(
borderRadius: cornerRadius,
blurIntensity: 18,
padding: EdgeInsets.fromLTRB(
isPhone ? 10 : 15,
isPhone ? 5 : 8,
isPhone ? 10 : 15,
isPhone ? 5 : 8,
),
child: Obx(() => Text(
'meeting_info_title'.trParams({
'start': formatTime(context, controller.meetingInfoTimes?['start'] ?? DateTime.now()),
'end': formatTime(context, controller.meetingInfoTimes?['end'] ?? DateTime.now()),
}),
style: FontService.instance.getTextStyle(
fontFamily: controller.currentFontFamily.value,
fontSize: isPhone ? 24 : 32,
fontWeight: FontWeight.w400,
color: TWColors.white,
)
)),
),
Flexible(
child: Obx(() => Text(
controller.subtitle,
style: FontService.instance.getTextStyle(
fontFamily: controller.currentFontFamily.value,
fontSize: isPhone ? 28 : 36,
fontWeight: FontWeight.w400,
color: TWColors.gray_300,
),
softWrap: true,
overflow: TextOverflow.visible,
)),
),
]
),
if (controller.meetingInfoTimes == null) SizedBox(height: isPhone ? 5 : 10),
if (controller.bookingEnabled || controller.checkInEnabled) ActionPanel(
controller: controller,
isPhone: isPhone,
cornerRadius: cornerRadius,
),
],
),
],
),
// Fixed Action Bar at Bottom
Align(
alignment: Alignment.bottomCenter,
child: FrostedPanel(
borderRadius: cornerRadius,
blurIntensity: 18,
padding: EdgeInsets.all(isPhone ? 12 : 20),
child: SpaceRow(
spaceBetween: isPhone ? 10 : 20,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Upcoming Events Section
Expanded(
child: controller.upcomingEvents.isNotEmpty
? SpaceCol(
spaceBetween: isPhone ? 8 : 12,
children: [
for (EventModel event in controller.upcomingEvents.take(1)) EventLine(event: event),
],
)
: Text(
'no_upcoming_events'.tr,
style: TextStyle(
color: TWColors.white,
fontSize: isPhone ? 16 : 18,
fontWeight: FontWeight.w500,
),
),
),
// Action Buttons Section
if (controller.calendarEnabled)
Material(
color: Colors.transparent,
child: InkWell(
hoverColor: Colors.transparent,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
borderRadius: BorderRadius.circular(8),
onTap: () {
showDialog(
context: context,
builder: (context) => CalendarModal(
events: controller.events,
selectedDate: DateTime.now(),
),
);
},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.calendar_today_outlined,
size: 24,
color: Colors.white,
),
SizedBox(width: 12),
Text(
'view_schedule'.tr,
style: TextStyle(
color: Colors.white,
fontSize: isPhone ? 16 : 18,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
),
],
),
),
),
],
),
),
)
),
),
);
}
}
================================================
FILE: app/lib/pages/display_page.dart
================================================
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:spacepad/components/spinner.dart';
import 'package:spacepad/controllers/display_controller.dart';
import 'package:spacepad/models/display_model.dart';
import 'package:spacepad/services/auth_service.dart';
import 'package:spacepad/theme.dart';
import 'package:tailwind_components/tailwind_components.dart';
class DisplayPage extends StatelessWidget {
const DisplayPage({super.key});
@override
Widget build(BuildContext context) {
DisplayController controller = Get.put(DisplayController());
return Scaffold(
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: SafeArea(
child: Stack(
children: [
// Logout button at t
gitextract_qzg9plm7/
├── .github/
│ └── workflows/
│ ├── docker-build.yml
│ └── tests.yml
├── .gitignore
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE.md
├── LICENSE_PRO.md
├── README.md
├── app/
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── android/
│ │ ├── .gitignore
│ │ ├── app/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ ├── debug/
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── kotlin/
│ │ │ │ │ └── com/
│ │ │ │ │ └── magweter/
│ │ │ │ │ └── spacepad/
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── res/
│ │ │ │ ├── drawable/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values/
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night/
│ │ │ │ └── styles.xml
│ │ │ └── profile/
│ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle.kts
│ │ ├── gradle/
│ │ │ └── wrapper/
│ │ │ └── gradle-wrapper.properties
│ │ ├── gradle.properties
│ │ └── settings.gradle.kts
│ ├── ios/
│ │ ├── .gitignore
│ │ ├── Flutter/
│ │ │ ├── AppFrameworkInfo.plist
│ │ │ ├── Debug.xcconfig
│ │ │ └── Release.xcconfig
│ │ ├── Podfile
│ │ ├── Runner/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── LaunchImage.imageset/
│ │ │ │ ├── Contents.json
│ │ │ │ └── README.md
│ │ │ ├── Base.lproj/
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ └── Runner-Bridging-Header.h
│ │ ├── 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
│ │ └── RunnerTests/
│ │ └── RunnerTests.swift
│ ├── lib/
│ │ ├── components/
│ │ │ ├── action_button.dart
│ │ │ ├── action_panel.dart
│ │ │ ├── admin_actions.dart
│ │ │ ├── authenticated_background.dart
│ │ │ ├── authenticated_image.dart
│ │ │ ├── calendar_modal.dart
│ │ │ ├── custom_booking_modal.dart
│ │ │ ├── event_line.dart
│ │ │ ├── frosted_panel.dart
│ │ │ ├── solid_button.dart
│ │ │ ├── spinner.dart
│ │ │ └── toast.dart
│ │ ├── controllers/
│ │ │ ├── dashboard_controller.dart
│ │ │ ├── display_controller.dart
│ │ │ └── login_controller.dart
│ │ ├── date_format_helper.dart
│ │ ├── exceptions/
│ │ │ └── api_exception.dart
│ │ ├── main.dart
│ │ ├── models/
│ │ │ ├── device_model.dart
│ │ │ ├── display_data_model.dart
│ │ │ ├── display_model.dart
│ │ │ ├── display_settings_model.dart
│ │ │ ├── event_model.dart
│ │ │ ├── event_status.dart
│ │ │ └── user_model.dart
│ │ ├── pages/
│ │ │ ├── dashboard_page.dart
│ │ │ ├── display_page.dart
│ │ │ ├── login_page.dart
│ │ │ └── splash_page.dart
│ │ ├── services/
│ │ │ ├── api_service.dart
│ │ │ ├── auth_service.dart
│ │ │ ├── device_service.dart
│ │ │ ├── display_service.dart
│ │ │ ├── font_service.dart
│ │ │ └── server_service.dart
│ │ ├── theme.dart
│ │ └── translations/
│ │ └── translations.dart
│ ├── pubspec.yaml
│ └── test/
│ └── widget_test.dart
├── backend/
│ ├── .editorconfig
│ ├── .gitattributes
│ ├── .gitignore
│ ├── Dockerfile
│ ├── FARO_SETUP.md
│ ├── README.md
│ ├── WORKSPACE_SETUP.md
│ ├── app/
│ │ ├── Console/
│ │ │ └── Commands/
│ │ │ ├── CheckMarketingTriggers.php
│ │ │ ├── CleanupExpiredEvents.php
│ │ │ ├── RenewEventSubscriptions.php
│ │ │ ├── SendHeartbeat.php
│ │ │ ├── TriggerRegistrationWebhookForMissingNames.php
│ │ │ ├── UpdateLemonSqueezySubscriptions.php
│ │ │ └── ValidateLicense.php
│ │ ├── Data/
│ │ │ ├── CalendarWebhookData.php
│ │ │ ├── DisplayWebhookData.php
│ │ │ ├── InstanceData.php
│ │ │ ├── LicenseData.php
│ │ │ ├── OrderWebhookData.php
│ │ │ ├── PermissionResult.php
│ │ │ ├── UserData.php
│ │ │ └── UserWebhookData.php
│ │ ├── Enums/
│ │ │ ├── AccountStatus.php
│ │ │ ├── DisplayStatus.php
│ │ │ ├── EventSource.php
│ │ │ ├── EventStatus.php
│ │ │ ├── GoogleBookingMethod.php
│ │ │ ├── OAuthDriver.php
│ │ │ ├── PermissionType.php
│ │ │ ├── Plan.php
│ │ │ ├── Provider.php
│ │ │ ├── UsageType.php
│ │ │ ├── UserStatus.php
│ │ │ └── WorkspaceRole.php
│ │ ├── Events/
│ │ │ ├── TrialExpiredOrCancelled.php
│ │ │ ├── UserActivatedAfter24h.php
│ │ │ ├── UserInactive.php
│ │ │ ├── UserNotActivatedAfter24h.php
│ │ │ ├── UserOnboarded.php
│ │ │ ├── UserPassive.php
│ │ │ └── UserRegistered.php
│ │ ├── Exceptions/
│ │ │ └── Handler.php
│ │ ├── Helpers/
│ │ │ ├── DisplaySettings.php
│ │ │ └── Settings.php
│ │ ├── Http/
│ │ │ ├── Controllers/
│ │ │ │ ├── API/
│ │ │ │ │ ├── ApiController.php
│ │ │ │ │ ├── Auth/
│ │ │ │ │ │ └── AuthController.php
│ │ │ │ │ ├── Cloud/
│ │ │ │ │ │ └── InstanceController.php
│ │ │ │ │ ├── DeviceController.php
│ │ │ │ │ ├── DisplayController.php
│ │ │ │ │ └── EventController.php
│ │ │ │ ├── AdminController.php
│ │ │ │ ├── Auth/
│ │ │ │ │ ├── AuthController.php
│ │ │ │ │ ├── GoogleController.php
│ │ │ │ │ ├── LoginController.php
│ │ │ │ │ ├── MicrosoftController.php
│ │ │ │ │ ├── RegisterController.php
│ │ │ │ │ └── SocialAuthController.php
│ │ │ │ ├── BoardController.php
│ │ │ │ ├── CalDAVAccountsController.php
│ │ │ │ ├── CalendarController.php
│ │ │ │ ├── Controller.php
│ │ │ │ ├── DashboardController.php
│ │ │ │ ├── DisplayController.php
│ │ │ │ ├── DisplaySettingsController.php
│ │ │ │ ├── GoogleAccountsController.php
│ │ │ │ ├── GoogleWebhookController.php
│ │ │ │ ├── LicenseController.php
│ │ │ │ ├── OnboardingController.php
│ │ │ │ ├── OutlookAccountsController.php
│ │ │ │ ├── OutlookWebhookController.php
│ │ │ │ ├── RoomController.php
│ │ │ │ ├── UsageController.php
│ │ │ │ └── WorkspaceController.php
│ │ │ ├── Middleware/
│ │ │ │ ├── CheckUserActive.php
│ │ │ │ ├── CheckUserOnboarding.php
│ │ │ │ └── UpdateLastActivity.php
│ │ │ ├── Requests/
│ │ │ │ ├── API/
│ │ │ │ │ ├── Auth/
│ │ │ │ │ │ └── LoginRequest.php
│ │ │ │ │ ├── BookEventRequest.php
│ │ │ │ │ ├── ChangeDisplayRequest.php
│ │ │ │ │ ├── InstanceHeartbeatRequest.php
│ │ │ │ │ └── ValidateInstanceRequest.php
│ │ │ │ ├── ActivateLicenseRequest.php
│ │ │ │ ├── Auth/
│ │ │ │ │ ├── LoginRequest.php
│ │ │ │ │ ├── OAuth2TokenRequest.php
│ │ │ │ │ └── RegisterRequest.php
│ │ │ │ ├── CreateBoardRequest.php
│ │ │ │ ├── CreateDisplayRequest.php
│ │ │ │ ├── UpdateBoardRequest.php
│ │ │ │ └── UpdateDisplayCustomizationRequest.php
│ │ │ └── Resources/
│ │ │ └── API/
│ │ │ ├── DeviceResource.php
│ │ │ ├── DisplayDataResource.php
│ │ │ ├── DisplayResource.php
│ │ │ ├── DisplaySettingsResource.php
│ │ │ ├── EventResource.php
│ │ │ └── UserResource.php
│ │ ├── Infrastructure/
│ │ │ └── Cloud/
│ │ │ └── LicenseService.php
│ │ ├── Listeners/
│ │ │ ├── ActivateUser.php
│ │ │ ├── SendOnboardingCompleteNotification.php
│ │ │ ├── SendOrderCreatedNotification.php
│ │ │ ├── SendRegistrationNotification.php
│ │ │ ├── SendTrialExpiredOrCancelledNotification.php
│ │ │ ├── SendUserActivatedAfter24hNotification.php
│ │ │ ├── SendUserInactiveNotification.php
│ │ │ ├── SendUserNotActivatedAfter24hNotification.php
│ │ │ └── SendUserPassiveNotification.php
│ │ ├── Models/
│ │ │ ├── Board.php
│ │ │ ├── CalDAVAccount.php
│ │ │ ├── Calendar.php
│ │ │ ├── Device.php
│ │ │ ├── Display.php
│ │ │ ├── DisplaySetting.php
│ │ │ ├── Event.php
│ │ │ ├── EventSubscription.php
│ │ │ ├── GoogleAccount.php
│ │ │ ├── Instance.php
│ │ │ ├── OutlookAccount.php
│ │ │ ├── PersonalAccessToken.php
│ │ │ ├── Room.php
│ │ │ ├── Setting.php
│ │ │ ├── User.php
│ │ │ ├── Workspace.php
│ │ │ └── WorkspaceMember.php
│ │ ├── Notifications/
│ │ │ └── MagicLoginNotification.php
│ │ ├── Observers/
│ │ │ └── EventObserver.php
│ │ ├── Policies/
│ │ │ ├── BoardPolicy.php
│ │ │ └── DisplayPolicy.php
│ │ ├── Providers/
│ │ │ ├── AppServiceProvider.php
│ │ │ └── AuthServiceProvider.php
│ │ ├── Services/
│ │ │ ├── CalDAVService.php
│ │ │ ├── DisplayService.php
│ │ │ ├── EventService.php
│ │ │ ├── GoogleService.php
│ │ │ ├── ImageService.php
│ │ │ ├── InstanceService.php
│ │ │ └── OutlookService.php
│ │ └── Traits/
│ │ ├── HasLastActivity.php
│ │ ├── HasUlid.php
│ │ └── RespondsWithApiResponse.php
│ ├── artisan
│ ├── bootstrap/
│ │ ├── app.php
│ │ ├── cache/
│ │ │ └── .gitignore
│ │ ├── opentelemetry.php
│ │ └── providers.php
│ ├── composer.json
│ ├── config/
│ │ ├── app.php
│ │ ├── auth.php
│ │ ├── broadcasting.php
│ │ ├── cache.php
│ │ ├── database.php
│ │ ├── faro.php
│ │ ├── filesystems.php
│ │ ├── googletagmanager.php
│ │ ├── lemon-squeezy.php
│ │ ├── logging.php
│ │ ├── magiclink.php
│ │ ├── mail.php
│ │ ├── queue.php
│ │ ├── recaptchav3.php
│ │ ├── sanctum.php
│ │ ├── sentry.php
│ │ ├── services.php
│ │ ├── session.php
│ │ ├── settings.php
│ │ └── wave.php
│ ├── database/
│ │ ├── .gitignore
│ │ ├── factories/
│ │ │ ├── BoardFactory.php
│ │ │ ├── CalDAVAccountFactory.php
│ │ │ ├── CalendarFactory.php
│ │ │ ├── DeviceFactory.php
│ │ │ ├── DisplayFactory.php
│ │ │ ├── EventSubscriptionFactory.php
│ │ │ ├── GoogleAccountFactory.php
│ │ │ ├── InstanceFactory.php
│ │ │ ├── OutlookAccountFactory.php
│ │ │ ├── RoomFactory.php
│ │ │ ├── UserFactory.php
│ │ │ └── WorkspaceFactory.php
│ │ ├── migrations/
│ │ │ ├── 2014_10_12_000000_create_users_table.php
│ │ │ ├── 2014_10_12_100000_create_password_reset_tokens_table.php
│ │ │ ├── 2017_07_06_000000_create_table_magic_links.php
│ │ │ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ │ │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php
│ │ │ ├── 2021_03_06_211907_add_access_code_to_magic_links_table.php
│ │ │ ├── 2024_03_19_000000_add_usage_type_to_users_table.php
│ │ │ ├── 2024_10_08_193424_create_outlook_accounts_table.php
│ │ │ ├── 2024_10_08_193455_create_calendars_table.php
│ │ │ ├── 2024_10_12_203020_create_displays_table.php
│ │ │ ├── 2024_10_17_212003_create_event_subscriptions_table.php
│ │ │ ├── 2025_01_12_122905_create_devices_table.php
│ │ │ ├── 2025_01_12_190259_create_rooms_table.php
│ │ │ ├── 2025_05_04_204354_remove_unique_from_outlook_accounts.php
│ │ │ ├── 2025_05_07_181029_create_sessions_table.php
│ │ │ ├── 2025_05_07_181034_create_cache_table.php
│ │ │ ├── 2025_05_17_130507_create_google_accounts_table.php
│ │ │ ├── 2025_05_17_153857_add_google_account_id_to_calendars_table.php
│ │ │ ├── 2025_05_18_010101_remove_unique_from_google_accounts.php
│ │ │ ├── 2025_05_18_010201_remove_unique_from_calendars.php
│ │ │ ├── 2025_05_18_114502_add_status_to_accounts.php
│ │ │ ├── 2025_05_21_000000_add_google_account_id_to_event_subscriptions.php
│ │ │ ├── 2025_05_23_000000_create_caldav_accounts_table.php
│ │ │ ├── 2025_05_23_000001_add_caldav_account_id_to_calendars_table.php
│ │ │ ├── 2025_05_23_201433_add_google_id_to_users_table.php
│ │ │ ├── 2025_05_27_203928_add_last_activity_at_to_users_table.php
│ │ │ ├── 2025_05_27_204843_add_last_activity_at_to_devices_table.php
│ │ │ ├── 2025_05_28_193657_add_is_billing_exempt_to_users_table.php
│ │ │ ├── 2025_05_28_194845_add_is_unlimited_to_users_table.php
│ │ │ ├── 2025_06_08_000001_add_terms_accepted_at_to_users_table.php
│ │ │ ├── 2025_06_09_115819_drop_is_billing_exempt_from_users_table.php
│ │ │ ├── 2025_06_09_122516_add_hosted_domain_to_google_accounts_table.php
│ │ │ ├── 2025_06_09_122702_add_tenant_id_to_outlook_accounts_table.php
│ │ │ ├── 2025_06_09_125231_add_uid_to_devices_table.php
│ │ │ ├── 2025_06_09_150001_create_instances_table.php
│ │ │ ├── 2025_06_15_000000_create_settings_table.php
│ │ │ ├── 2025_06_15_120000_change_billable_id_to_ulid_on_lemonsqueezy_tables.php
│ │ │ ├── 2025_06_16_000000_create_events_table.php
│ │ │ ├── 2025_07_05_000000_create_display_settings_table.php
│ │ │ ├── 2025_07_05_000001_alter_avatar_column_on_google_accounts_table.php
│ │ │ ├── 2025_07_27_000000_add_is_admin_to_users_table.php
│ │ │ ├── 2025_11_28_000000_add_permission_type_to_outlook_accounts_table.php
│ │ │ ├── 2025_11_28_000001_add_permission_type_to_google_accounts_table.php
│ │ │ ├── 2025_11_28_000002_add_permission_type_to_caldav_accounts_table.php
│ │ │ ├── 2025_12_03_000000_add_service_account_file_path_to_google_accounts_table.php
│ │ │ ├── 2025_12_04_000000_add_booking_method_to_google_accounts_table.php
│ │ │ ├── 2025_12_05_000000_encrypt_existing_tokens_in_google_and_outlook_accounts.php
│ │ │ ├── 2025_12_06_000003_add_first_name_and_last_name_to_users_table.php
│ │ │ ├── 2025_12_30_000000_create_workspaces_table.php
│ │ │ ├── 2025_12_30_000001_create_workspace_members_table.php
│ │ │ ├── 2025_12_30_000002_add_workspace_id_to_tables.php
│ │ │ ├── 2025_12_30_000003_add_workspace_id_to_accounts_tables.php
│ │ │ ├── 2025_12_30_000004_create_workspaces_for_existing_users.php
│ │ │ ├── 2026_02_28_000000_increase_events_description_column_size.php
│ │ │ ├── 2026_02_28_000001_increase_caldav_accounts_password_column_size.php
│ │ │ ├── 2026_02_28_120000_create_boards_table.php
│ │ │ ├── 2026_02_28_120001_create_board_displays_table.php
│ │ │ ├── 2026_02_28_120002_add_theme_to_boards_table.php
│ │ │ ├── 2026_02_28_120003_add_logo_to_boards_table.php
│ │ │ ├── 2026_02_28_120004_add_display_options_to_boards_table.php
│ │ │ ├── 2026_02_28_120005_add_additional_settings_to_boards_table.php
│ │ │ ├── 2026_02_28_120007_add_title_and_subtitle_to_boards_table.php
│ │ │ ├── 2026_02_28_120008_add_view_mode_to_boards_table.php
│ │ │ └── 2026_02_28_140000_add_boards_count_to_instances_table.php
│ │ └── seeders/
│ │ └── DatabaseSeeder.php
│ ├── docs/
│ │ ├── CODING_STANDARDS.md
│ │ └── WORKSPACE_SETUP.md
│ ├── lang/
│ │ ├── de/
│ │ │ └── boards.php
│ │ ├── en/
│ │ │ ├── boards.php
│ │ │ └── validation.php
│ │ ├── es/
│ │ │ └── boards.php
│ │ ├── fr/
│ │ │ └── boards.php
│ │ ├── nl/
│ │ │ └── boards.php
│ │ └── sv/
│ │ └── boards.php
│ ├── package.json
│ ├── phpunit.xml
│ ├── public/
│ │ ├── .htaccess
│ │ ├── images/
│ │ │ └── backgrounds/
│ │ │ └── README.md
│ │ ├── index.php
│ │ ├── robots.txt
│ │ └── site.webmanifest
│ ├── requests/
│ │ ├── .gitignore
│ │ ├── api/
│ │ │ ├── activate.http
│ │ │ ├── auth/
│ │ │ │ └── login.http
│ │ │ ├── book-room.http
│ │ │ ├── cancel-event.http
│ │ │ ├── change-display.http
│ │ │ ├── check-in-event.http
│ │ │ ├── get-display-data.http
│ │ │ ├── get-displays.http
│ │ │ ├── get-events.http
│ │ │ ├── get-me.http
│ │ │ ├── heartbeat.http
│ │ │ └── outlook/
│ │ │ ├── get-outlook-calendars.http
│ │ │ └── outlook-auth.http
│ │ ├── graph/
│ │ │ ├── get-calendar-by-email.http
│ │ │ ├── get-calendars.http
│ │ │ ├── get-events.http
│ │ │ └── get-rooms.http
│ │ └── webhook-tests.http
│ ├── resources/
│ │ ├── css/
│ │ │ └── app.css
│ │ ├── js/
│ │ │ ├── app.js
│ │ │ ├── bootstrap.js
│ │ │ └── echo.js
│ │ └── views/
│ │ ├── .gitkeep
│ │ ├── auth/
│ │ │ ├── login.blade.php
│ │ │ └── register.blade.php
│ │ ├── components/
│ │ │ ├── alerts/
│ │ │ │ └── alert.blade.php
│ │ │ ├── calendars/
│ │ │ │ └── picker.blade.php
│ │ │ ├── cards/
│ │ │ │ └── card.blade.php
│ │ │ ├── displays/
│ │ │ │ └── table-row.blade.php
│ │ │ ├── icons/
│ │ │ │ ├── arrow-left.blade.php
│ │ │ │ ├── brush.blade.php
│ │ │ │ ├── building.blade.php
│ │ │ │ ├── caldav.blade.php
│ │ │ │ ├── calendar.blade.php
│ │ │ │ ├── display.blade.php
│ │ │ │ ├── external.blade.php
│ │ │ │ ├── google.blade.php
│ │ │ │ ├── information.blade.php
│ │ │ │ ├── logout.blade.php
│ │ │ │ ├── microsoft.blade.php
│ │ │ │ ├── pause.blade.php
│ │ │ │ ├── play.blade.php
│ │ │ │ ├── plus.blade.php
│ │ │ │ ├── room.blade.php
│ │ │ │ ├── settings.blade.php
│ │ │ │ ├── trash.blade.php
│ │ │ │ └── users.blade.php
│ │ │ ├── impersonation-banner.blade.php
│ │ │ ├── modals/
│ │ │ │ ├── google-service-account.blade.php
│ │ │ │ ├── license-key.blade.php
│ │ │ │ ├── manage-subscription.blade.php
│ │ │ │ ├── select-google-booking-method.blade.php
│ │ │ │ └── select-permission.blade.php
│ │ │ ├── rooms/
│ │ │ │ └── picker.blade.php
│ │ │ └── scripts/
│ │ │ ├── clarity.blade.php
│ │ │ └── faro.blade.php
│ │ ├── errors/
│ │ │ ├── 403.blade.php
│ │ │ ├── 404.blade.php
│ │ │ ├── 419.blade.php
│ │ │ ├── 429.blade.php
│ │ │ └── 500.blade.php
│ │ ├── layouts/
│ │ │ ├── base.blade.php
│ │ │ ├── blank.blade.php
│ │ │ └── error.blade.php
│ │ ├── pages/
│ │ │ ├── admin/
│ │ │ │ └── user.blade.php
│ │ │ ├── admin.blade.php
│ │ │ ├── boards/
│ │ │ │ ├── form.blade.php
│ │ │ │ ├── index.blade.php
│ │ │ │ └── show.blade.php
│ │ │ ├── caldav-accounts/
│ │ │ │ └── create.blade.php
│ │ │ ├── dashboard.blade.php
│ │ │ ├── displays/
│ │ │ │ ├── create.blade.php
│ │ │ │ ├── customization.blade.php
│ │ │ │ └── settings.blade.php
│ │ │ ├── onboarding.blade.php
│ │ │ └── usage/
│ │ │ └── index.blade.php
│ │ └── vendor/
│ │ ├── googletagmanager/
│ │ │ └── body.blade.php
│ │ └── pagination/
│ │ └── tailwind.blade.php
│ ├── routes/
│ │ ├── api.php
│ │ ├── channels.php
│ │ ├── console.php
│ │ └── web.php
│ ├── storage/
│ │ ├── app/
│ │ │ └── .gitignore
│ │ ├── framework/
│ │ │ ├── .gitignore
│ │ │ ├── cache/
│ │ │ │ └── .gitignore
│ │ │ ├── sessions/
│ │ │ │ └── .gitignore
│ │ │ ├── testing/
│ │ │ │ └── .gitignore
│ │ │ └── views/
│ │ │ └── .gitignore
│ │ └── logs/
│ │ └── .gitignore
│ ├── tailwind.config.js
│ ├── tests/
│ │ ├── Feature/
│ │ │ ├── API/
│ │ │ │ ├── AuthControllerTest.php
│ │ │ │ └── EventControllerTest.php
│ │ │ ├── AdminBoardsTest.php
│ │ │ ├── BoardControllerTest.php
│ │ │ ├── BoardUsageTest.php
│ │ │ ├── DisplaySettingsApiTest.php
│ │ │ ├── InstanceHeartbeatTest.php
│ │ │ └── PageReachabilityTest.php
│ │ ├── Pest.php
│ │ ├── TestCase.php
│ │ └── Unit/
│ │ ├── DisplaySettingsTest.php
│ │ ├── SettingsTest.php
│ │ └── WorkspaceUsageTest.php
│ └── vite.config.js
├── deployment/
│ └── docker-compose.mariadb-redis.yml
├── docker-compose.dev.yml
├── docker-compose.prod.yml
├── docker-compose.yml
├── docs/
│ ├── REVERSE_PROXY.md
│ ├── SETUP.md
│ └── UPGRADE_GUIDE.md
└── k6/
├── README.md
├── load-test.js
└── tags.js
SYMBOL INDEX (973 symbols across 244 files)
FILE: app/lib/components/action_button.dart
class ActionButton (line 6) | class ActionButton extends StatelessWidget {
method build (line 29) | Widget build(BuildContext context)
class _DiagonalStrikethroughPainter (line 107) | class _DiagonalStrikethroughPainter extends CustomPainter {
method paint (line 113) | void paint(Canvas canvas, Size size)
method shouldRepaint (line 126) | bool shouldRepaint(covariant CustomPainter oldDelegate)
FILE: app/lib/components/action_panel.dart
class ActionPanel (line 6) | class ActionPanel extends StatelessWidget {
method build (line 19) | Widget build(BuildContext context)
FILE: app/lib/components/admin_actions.dart
class AdminActions (line 4) | class AdminActions extends StatelessWidget {
method build (line 15) | Widget build(BuildContext context)
FILE: app/lib/components/authenticated_background.dart
class AuthenticatedBackground (line 7) | class AuthenticatedBackground extends StatefulWidget {
method createState (line 20) | State<AuthenticatedBackground> createState()
class _AuthenticatedBackgroundState (line 23) | class _AuthenticatedBackgroundState extends State<AuthenticatedBackgroun...
method initState (line 28) | void initState()
method didUpdateWidget (line 36) | void didUpdateWidget(AuthenticatedBackground oldWidget)
method _loadImage (line 50) | Future<void> _loadImage()
method build (line 108) | Widget build(BuildContext context)
FILE: app/lib/components/authenticated_image.dart
class AuthenticatedImage (line 7) | class AuthenticatedImage extends StatefulWidget {
method createState (line 26) | State<AuthenticatedImage> createState()
class _AuthenticatedImageState (line 29) | class _AuthenticatedImageState extends State<AuthenticatedImage> {
method initState (line 35) | void initState()
method didUpdateWidget (line 41) | void didUpdateWidget(AuthenticatedImage oldWidget)
method _loadImage (line 48) | Future<void> _loadImage()
method build (line 109) | Widget build(BuildContext context)
FILE: app/lib/components/calendar_modal.dart
class CalendarModal (line 9) | class CalendarModal extends StatelessWidget {
method build (line 20) | Widget build(BuildContext context)
FILE: app/lib/components/custom_booking_modal.dart
class CustomBookingModal (line 10) | class CustomBookingModal extends StatefulWidget {
method createState (line 23) | State<CustomBookingModal> createState()
class _CustomBookingModalState (line 26) | class _CustomBookingModalState extends State<CustomBookingModal> {
method initState (line 33) | void initState()
method dispose (line 77) | void dispose()
method _selectStartTime (line 82) | Future<void> _selectStartTime()
method _selectEndTime (line 115) | Future<void> _selectEndTime()
method _setStartTimeToNow (line 148) | void _setStartTimeToNow()
method _setEndTimeToMax (line 173) | void _setEndTimeToMax()
method _bookCustom (line 194) | void _bookCustom()
method build (line 228) | Widget build(BuildContext context)
FILE: app/lib/components/event_line.dart
class EventLine (line 7) | class EventLine extends StatelessWidget {
method _isPhone (line 12) | bool _isPhone(BuildContext context)
method build (line 18) | Widget build(BuildContext context)
FILE: app/lib/components/frosted_panel.dart
class FrostedPanel (line 5) | class FrostedPanel extends StatelessWidget {
method build (line 40) | Widget build(BuildContext context)
FILE: app/lib/components/solid_button.dart
class SolidButton (line 5) | class SolidButton extends StatelessWidget {
method build (line 22) | Widget build(BuildContext context)
FILE: app/lib/components/spinner.dart
class Spinner (line 4) | class Spinner extends StatelessWidget {
method build (line 13) | Widget build(BuildContext context)
FILE: app/lib/components/toast.dart
class Toast (line 5) | class Toast {
method showSuccess (line 8) | void showSuccess(String message)
method showError (line 12) | void showError(String message)
method _showSnackBar (line 16) | void _showSnackBar(String message, HeroIcons icon, Color color)
FILE: app/lib/controllers/dashboard_controller.dart
class DashboardController (line 18) | class DashboardController extends GetxController {
method onInit (line 41) | void onInit()
method initializeTimers (line 64) | void initializeTimers()
method updateTime (line 75) | void updateTime()
method fetchDisplayData (line 211) | Future<void> fetchDisplayData()
method switchRoom (line 247) | void switchRoom()
method refreshDisplayData (line 255) | Future<void> refreshDisplayData()
method bookRoom (line 280) | Future<void> bookRoom(int duration)
method showCustomBookingModal (line 302) | void showCustomBookingModal(BuildContext context, bool isPhone, double...
method bookCustom (line 313) | Future<void> bookCustom(String title, DateTime startTime, DateTime end...
method cancelCurrentEvent (line 332) | Future<void> cancelCurrentEvent()
method getBorderWidth (line 384) | double getBorderWidth()
method toggleBookingOptions (line 422) | void toggleBookingOptions()
method hideBookingOptions (line 435) | void hideBookingOptions()
method startLongPressTimer (line 440) | void startLongPressTimer()
method cancelLongPressTimer (line 451) | void cancelLongPressTimer()
method revealAdminActionsTemporarily (line 456) | void revealAdminActionsTemporarily()
method checkIn (line 483) | void checkIn()
method getDisplayableSummary (line 507) | String getDisplayableSummary(EventModel event)
method getReservedText (line 514) | String getReservedText()
method dispose (line 519) | void dispose()
FILE: app/lib/controllers/display_controller.dart
class DisplayController (line 8) | class DisplayController extends GetxController {
method onInit (line 14) | void onInit()
method onSelect (line 20) | void onSelect(val)
method getDisplays (line 28) | Future<void> getDisplays()
method submit (line 42) | Future<void> submit()
FILE: app/lib/controllers/login_controller.dart
class LoginController (line 12) | class LoginController extends GetxController {
method toggleSelfHosted (line 22) | void toggleSelfHosted(bool value)
method urlChanged (line 27) | void urlChanged(String value)
method codeChanged (line 32) | void codeChanged(String value)
method _updateSubmitActive (line 37) | void _updateSubmitActive()
method _isValidUrl (line 45) | bool _isValidUrl(String url)
method getDeviceId (line 54) | Future<String?> getDeviceId()
method getDeviceName (line 58) | Future<String?> getDeviceName()
method submit (line 72) | Future<void> submit()
FILE: app/lib/date_format_helper.dart
function formatTime (line 4) | String formatTime(BuildContext context, DateTime time)
FILE: app/lib/exceptions/api_exception.dart
class ApiException (line 4) | class ApiException implements Exception {
method fromResponse (line 11) | ApiException fromResponse(Response response)
method _mapErrors (line 19) | Map? _mapErrors(Map? errors)
method toString (line 26) | String toString()
FILE: app/lib/main.dart
function isLocaleSupported (line 26) | bool isLocaleSupported(Locale locale)
function getBestMatchingLocale (line 33) | Locale getBestMatchingLocale(Locale? requestedLocale)
function main (line 57) | Future<void> main()
class App (line 86) | class App extends StatelessWidget {
method build (line 90) | Widget build(BuildContext context)
FILE: app/lib/models/device_model.dart
class DeviceModel (line 4) | class DeviceModel {
FILE: app/lib/models/display_data_model.dart
class DisplayDataModel (line 4) | class DisplayDataModel {
method toJson (line 31) | Map<String, dynamic> toJson()
FILE: app/lib/models/display_model.dart
class DisplayModel (line 3) | class DisplayModel {
method toJson (line 22) | Map<String, dynamic> toJson()
FILE: app/lib/models/display_settings_model.dart
class DisplaySettingsModel (line 1) | class DisplaySettingsModel {
method toJson (line 62) | Map<String, dynamic>? toJson()
FILE: app/lib/models/event_model.dart
class EventModel (line 3) | class EventModel {
method toJson (line 49) | Map<String, dynamic> toJson()
FILE: app/lib/models/event_status.dart
type EventStatus (line 1) | enum EventStatus { confirmed, tentative, cancelled }
function eventStatusFromString (line 3) | EventStatus eventStatusFromString(String? value)
function eventStatusToString (line 15) | String eventStatusToString(EventStatus status)
FILE: app/lib/models/user_model.dart
class UserModel (line 1) | class UserModel {
method toJson (line 20) | Map<String, dynamic> toJson()
FILE: app/lib/pages/dashboard_page.dart
class DashboardPage (line 22) | class DashboardPage extends StatefulWidget {
method createState (line 26) | State<DashboardPage> createState()
class _DashboardPageState (line 29) | class _DashboardPageState extends State<DashboardPage> {
method _isPhone (line 30) | bool _isPhone(BuildContext context)
method _isPortrait (line 36) | bool _isPortrait(BuildContext context)
method _getCornerRadius (line 41) | double _getCornerRadius(BuildContext context)
method _getContainerPadding (line 50) | double _getContainerPadding(BuildContext context, DashboardController ...
method _getInnerPadding (line 67) | EdgeInsets _getInnerPadding(BuildContext context)
method build (line 86) | Widget build(BuildContext context)
FILE: app/lib/pages/display_page.dart
class DisplayPage (line 11) | class DisplayPage extends StatelessWidget {
method build (line 15) | Widget build(BuildContext context)
FILE: app/lib/pages/login_page.dart
class LoginPage (line 9) | class LoginPage extends StatelessWidget {
method build (line 13) | Widget build(BuildContext context)
FILE: app/lib/pages/splash_page.dart
class SplashPage (line 6) | class SplashPage extends StatefulWidget {
method createState (line 10) | State<SplashPage> createState()
class _SplashPageState (line 13) | class _SplashPageState extends State<SplashPage> {
method initState (line 15) | void initState()
method build (line 22) | Widget build(BuildContext context)
FILE: app/lib/services/api_service.dart
class ApiService (line 13) | class ApiService {
method setBaseUrl (line 16) | Future<bool> setBaseUrl(String apiUrl)
method resetToServerBaseUrl (line 21) | Future<bool> resetToServerBaseUrl()
method getBaseUrl (line 26) | Future<String> getBaseUrl()
method get (line 32) | Future get(String endpoint)
method post (line 56) | Future post(String endpoint, Map body)
method put (line 78) | Future put(String endpoint, Map body)
method delete (line 100) | Future delete(String endpoint, [Map? body])
method _getHeaders (line 126) | Map<String, String>? _getHeaders()
FILE: app/lib/services/auth_service.dart
class AuthService (line 10) | class AuthService {
method initialise (line 17) | Future<void> initialise()
method setBaseUrl (line 21) | Future<void> setBaseUrl(String url)
method login (line 25) | Future<void> login(String code, String uid, String name)
method verify (line 40) | Future<void> verify()
method changeDisplay (line 60) | Future<void> changeDisplay(Map body)
method signOut (line 68) | Future<void> signOut()
method getAuthToken (line 77) | String? getAuthToken()
method setAuthToken (line 81) | Future<bool> setAuthToken(String token)
method deleteAuthToken (line 85) | Future<bool> deleteAuthToken()
method getCurrentDisplayId (line 89) | String? getCurrentDisplayId()
method setCurrentDisplayId (line 93) | Future<bool> setCurrentDisplayId(String displayId)
method removeCurrentDisplayId (line 97) | Future<bool> removeCurrentDisplayId()
FILE: app/lib/services/device_service.dart
class DeviceService (line 3) | class DeviceService {
method changeDisplay (line 7) | Future<void> changeDisplay(String displayId)
FILE: app/lib/services/display_service.dart
class DisplayService (line 5) | class DisplayService {
method getDisplays (line 9) | Future<List<DisplayModel>> getDisplays()
method book (line 17) | Future<void> book(String displayId, int duration, {String? summary})
method bookCustom (line 24) | Future<void> bookCustom(String displayId, String title, DateTime start...
method getDisplayData (line 33) | Future<DisplayDataModel> getDisplayData(String displayId)
method _getDisplayDataNew (line 45) | Future<DisplayDataModel> _getDisplayDataNew(String displayId)
method _getDisplayDataOld (line 51) | Future<DisplayDataModel> _getDisplayDataOld(String displayId)
method _isRouteNotFoundError (line 57) | bool _isRouteNotFoundError(dynamic e)
method cancelEvent (line 62) | Future<void> cancelEvent(String displayId, String eventId)
method checkInToEvent (line 66) | Future<void> checkInToEvent(String displayId, String eventId)
FILE: app/lib/services/font_service.dart
class FontService (line 4) | class FontService {
method getTextStyle (line 29) | TextStyle getTextStyle({
method preloadFonts (line 100) | Future<void> preloadFonts()
method reloadFont (line 133) | Future<void> reloadFont(String fontFamily)
method getFontDisplayName (line 165) | String getFontDisplayName(String fontFamily)
method isFontAvailable (line 170) | bool isFontAvailable(String fontFamily)
FILE: app/lib/services/server_service.dart
class ServerService (line 3) | class ServerService {
method isServerReachable (line 10) | Future<bool> isServerReachable(String url)
FILE: app/lib/theme.dart
class AppTheme (line 5) | class AppTheme {
FILE: app/lib/translations/translations.dart
class AppTranslations (line 3) | class AppTranslations extends Translations {
FILE: app/test/widget_test.dart
function main (line 13) | void main()
FILE: backend/app/Console/Commands/CheckMarketingTriggers.php
class CheckMarketingTriggers (line 14) | class CheckMarketingTriggers extends Command
method handle (line 33) | public function handle(): int
method checkUsersNotActivatedAfter24h (line 60) | private function checkUsersNotActivatedAfter24h(): void
method checkUsersActivatedAfter24h (line 81) | private function checkUsersActivatedAfter24h(): void
method checkPassiveUsers (line 104) | private function checkPassiveUsers(): void
method checkInactiveUsers (line 139) | private function checkInactiveUsers(): void
method checkTrialExpiredOrCancelled (line 173) | private function checkTrialExpiredOrCancelled(): void
FILE: backend/app/Console/Commands/CleanupExpiredEvents.php
class CleanupExpiredEvents (line 9) | class CleanupExpiredEvents extends Command
method handle (line 28) | public function handle(): void
method cleanupEventsForDisplay (line 62) | private function cleanupEventsForDisplay(Display $display, bool $dryRu...
FILE: backend/app/Console/Commands/RenewEventSubscriptions.php
class RenewEventSubscriptions (line 16) | class RenewEventSubscriptions extends Command
method handle (line 36) | public function handle(OutlookService $outlookService, GoogleService $...
method renewOutlookEventSubscription (line 91) | private function renewOutlookEventSubscription(OutlookAccount $outlook...
method renewGoogleEventSubscription (line 111) | private function renewGoogleEventSubscription(GoogleAccount $googleAcc...
method createOutlookEventSubscription (line 131) | private function createOutlookEventSubscription(OutlookAccount $outloo...
method createGoogleEventSubscription (line 155) | private function createGoogleEventSubscription(GoogleAccount $googleAc...
FILE: backend/app/Console/Commands/SendHeartbeat.php
class SendHeartbeat (line 10) | class SendHeartbeat extends Command
method handle (line 18) | public function handle(InstanceService $instanceService): int
FILE: backend/app/Console/Commands/TriggerRegistrationWebhookForMissingNames.php
class TriggerRegistrationWebhookForMissingNames (line 9) | class TriggerRegistrationWebhookForMissingNames extends Command
method handle (line 28) | public function handle(): int
FILE: backend/app/Console/Commands/UpdateLemonSqueezySubscriptions.php
class UpdateLemonSqueezySubscriptions (line 11) | class UpdateLemonSqueezySubscriptions extends Command
method handle (line 30) | public function handle(): int
method getActiveDisplayCount (line 87) | private function getActiveDisplayCount(User $user): int
method getTotalUsageCount (line 98) | private function getTotalUsageCount(User $user): int
method updateQuantityBasedBilling (line 112) | private function updateQuantityBasedBilling(User $user, int $displayCo...
method updateUsageBasedBilling (line 202) | private function updateUsageBasedBilling(User $user, int $displayCount...
method getSubscriptionItems (line 300) | private function getSubscriptionItems(array $subscriptionData, string ...
method getSubscriptionItemId (line 341) | private function getSubscriptionItemId(array $subscriptionItem): ?string
FILE: backend/app/Console/Commands/ValidateLicense.php
class ValidateLicense (line 11) | class ValidateLicense extends Command
method handle (line 19) | public function handle(InstanceService $instanceService): int
FILE: backend/app/Data/CalendarWebhookData.php
class CalendarWebhookData (line 7) | class CalendarWebhookData extends Data
method __construct (line 9) | public function __construct(
method excludeProperties (line 28) | public function excludeProperties(): array
FILE: backend/app/Data/DisplayWebhookData.php
class DisplayWebhookData (line 7) | class DisplayWebhookData extends Data
method __construct (line 9) | public function __construct(
FILE: backend/app/Data/InstanceData.php
class InstanceData (line 9) | class InstanceData extends Data
method __construct (line 11) | public function __construct(
FILE: backend/app/Data/LicenseData.php
class LicenseData (line 9) | class LicenseData extends Data
method __construct (line 11) | public function __construct(
method fromModel (line 17) | public static function fromModel(Instance $instance): self
FILE: backend/app/Data/OrderWebhookData.php
class OrderWebhookData (line 7) | class OrderWebhookData extends Data
method __construct (line 9) | public function __construct(
FILE: backend/app/Data/PermissionResult.php
class PermissionResult (line 5) | class PermissionResult
method __construct (line 11) | public function __construct(bool $permitted, ?string $message = null, ...
FILE: backend/app/Data/UserData.php
class UserData (line 9) | class UserData extends Data
method __construct (line 11) | public function __construct(
FILE: backend/app/Data/UserWebhookData.php
class UserWebhookData (line 8) | class UserWebhookData extends Data
method __construct (line 10) | public function __construct(
method excludeProperties (line 38) | public function excludeProperties(): array
FILE: backend/app/Enums/AccountStatus.php
method label (line 10) | public function label(): string
method color (line 18) | public function color(): string
FILE: backend/app/Enums/DisplayStatus.php
method label (line 12) | public function label(): string
method color (line 22) | public function color(): string
FILE: backend/app/Enums/PermissionType.php
method label (line 10) | public function label(): string
method description (line 18) | public function description(): string
FILE: backend/app/Enums/UsageType.php
method label (line 10) | public function label(): string
FILE: backend/app/Enums/WorkspaceRole.php
method label (line 11) | public function label(): string
method canManage (line 23) | public function canManage(): bool
FILE: backend/app/Events/TrialExpiredOrCancelled.php
class TrialExpiredOrCancelled (line 9) | class TrialExpiredOrCancelled
method __construct (line 16) | public function __construct(public User $user)
FILE: backend/app/Events/UserActivatedAfter24h.php
class UserActivatedAfter24h (line 9) | class UserActivatedAfter24h
method __construct (line 16) | public function __construct(public User $user)
FILE: backend/app/Events/UserInactive.php
class UserInactive (line 9) | class UserInactive
method __construct (line 16) | public function __construct(public User $user)
FILE: backend/app/Events/UserNotActivatedAfter24h.php
class UserNotActivatedAfter24h (line 9) | class UserNotActivatedAfter24h
method __construct (line 16) | public function __construct(public User $user)
FILE: backend/app/Events/UserOnboarded.php
class UserOnboarded (line 9) | class UserOnboarded
method __construct (line 16) | public function __construct(public User $user, public Display $display)
FILE: backend/app/Events/UserPassive.php
class UserPassive (line 9) | class UserPassive
method __construct (line 16) | public function __construct(public User $user)
FILE: backend/app/Events/UserRegistered.php
class UserRegistered (line 9) | class UserRegistered
method __construct (line 16) | public function __construct(public User $user)
FILE: backend/app/Exceptions/Handler.php
class Handler (line 14) | class Handler extends ExceptionHandler
method register (line 30) | public function register(): void
method render (line 39) | public function render($request, Throwable $e): Response|JsonResponse|...
FILE: backend/app/Helpers/DisplaySettings.php
class DisplaySettings (line 8) | class DisplaySettings
method getSetting (line 10) | public static function getSetting(Display $display, string $key, mixed...
method setSetting (line 26) | public static function setSetting(Display $display, string $key, mixed...
method deleteSetting (line 46) | public static function deleteSetting(Display $display, string $key): bool
method getAllSettings (line 58) | public static function getAllSettings(Display $display): array
method isCheckInEnabled (line 77) | public static function isCheckInEnabled(Display $display): bool
method setCheckInEnabled (line 82) | public static function setCheckInEnabled(Display $display, bool $enabl...
method isBookingEnabled (line 87) | public static function isBookingEnabled(Display $display): bool
method setBookingEnabled (line 92) | public static function setBookingEnabled(Display $display, bool $enabl...
method getLogo (line 98) | public static function getLogo(Display $display): ?string
method setLogo (line 103) | public static function setLogo(Display $display, string $logoPath): bool
method removeLogo (line 108) | public static function removeLogo(Display $display): bool
method getBackgroundImage (line 114) | public static function getBackgroundImage(Display $display): ?string
method setBackgroundImage (line 119) | public static function setBackgroundImage(Display $display, string $ba...
method removeBackgroundImage (line 124) | public static function removeBackgroundImage(Display $display): bool
method getFontFamily (line 130) | public static function getFontFamily(Display $display): string
method setFontFamily (line 135) | public static function setFontFamily(Display $display, string $fontFam...
method getCheckInMinutes (line 140) | public static function getCheckInMinutes(Display $display): int
method setCheckInMinutes (line 145) | public static function setCheckInMinutes(Display $display, int $minute...
method getCheckInGracePeriod (line 150) | public static function getCheckInGracePeriod(Display $display): int
method setCheckInGracePeriod (line 155) | public static function setCheckInGracePeriod(Display $display, int $mi...
method isCalendarEnabled (line 160) | public static function isCalendarEnabled(Display $display): bool
method setCalendarEnabled (line 165) | public static function setCalendarEnabled(Display $display, bool $enab...
method getAvailableText (line 171) | public static function getAvailableText(Display $display): ?string
method setAvailableText (line 175) | public static function setAvailableText(Display $display, string $text...
method getTransitioningText (line 180) | public static function getTransitioningText(Display $display): ?string
method setTransitioningText (line 184) | public static function setTransitioningText(Display $display, string $...
method getReservedText (line 189) | public static function getReservedText(Display $display): ?string
method setReservedText (line 193) | public static function setReservedText(Display $display, string $text)...
method getCheckInText (line 198) | public static function getCheckInText(Display $display): ?string
method setCheckInText (line 202) | public static function setCheckInText(Display $display, string $text):...
method getShowMeetingTitle (line 208) | public static function getShowMeetingTitle(Display $display): bool
method setShowMeetingTitle (line 212) | public static function setShowMeetingTitle(Display $display, bool $sho...
method isAdminActionsHidden (line 218) | public static function isAdminActionsHidden(Display $display): bool
method setAdminActionsHidden (line 223) | public static function setAdminActionsHidden(Display $display, bool $h...
method getCancelPermission (line 230) | public static function getCancelPermission(Display $display): string
method setCancelPermission (line 235) | public static function setCancelPermission(Display $display, string $p...
method getBorderThickness (line 245) | public static function getBorderThickness(Display $display): string
method setBorderThickness (line 250) | public static function setBorderThickness(Display $display, string $th...
FILE: backend/app/Helpers/Settings.php
class Settings (line 7) | class Settings
method getSetting (line 9) | public static function getSetting(string $key, mixed $default = null):...
method setSetting (line 15) | public static function setSetting(string $key, mixed $value, string $t...
method deleteSetting (line 32) | public static function deleteSetting(string $key): bool
method getAllSettings (line 42) | public static function getAllSettings(): array
FILE: backend/app/Http/Controllers/API/ApiController.php
class ApiController (line 8) | class ApiController extends Controller
method success (line 10) | protected function success(string $message = 'Success', mixed $data = ...
method error (line 19) | protected function error(string $message = 'Error', mixed $errors = nu...
FILE: backend/app/Http/Controllers/API/Auth/AuthController.php
class AuthController (line 16) | class AuthController extends ApiController
method __construct (line 18) | public function __construct(protected OutlookService $outlookService)
method login (line 27) | public function login(LoginRequest $request): JsonResponse
FILE: backend/app/Http/Controllers/API/Cloud/InstanceController.php
class InstanceController (line 16) | class InstanceController extends ApiController
method __construct (line 18) | public function __construct(
method pseudonymizeIp (line 30) | private function pseudonymizeIp(string $ip): string
method heartbeat (line 37) | public function heartbeat(InstanceHeartbeatRequest $request): JsonResp...
method validateInstance (line 95) | public function validateInstance(ValidateInstanceRequest $request): Js...
method activate (line 119) | public function activate(ValidateInstanceRequest $request): JsonResponse
FILE: backend/app/Http/Controllers/API/DeviceController.php
class DeviceController (line 14) | class DeviceController extends ApiController
method me (line 16) | public function me(): JsonResponse
method changeDisplay (line 29) | public function changeDisplay(ChangeDisplayRequest $request): JsonResp...
FILE: backend/app/Http/Controllers/API/DisplayController.php
class DisplayController (line 20) | class DisplayController extends ApiController
method __construct (line 22) | public function __construct(
method index (line 29) | public function index(): JsonResponse
method getData (line 66) | public function getData(string $displayId): JsonResponse
method book (line 120) | public function book(BookEventRequest $request, string $displayId): Js...
method checkIn (line 185) | public function checkIn(string $displayId, string $eventId): JsonResponse
method cancel (line 233) | public function cancel(string $displayId, string $eventId): JsonResponse
method serveImage (line 281) | public function serveImage(string $displayId, string $type)
FILE: backend/app/Http/Controllers/API/EventController.php
class EventController (line 12) | class EventController extends ApiController
method __construct (line 14) | public function __construct(
method index (line 23) | public function index(): JsonResponse
FILE: backend/app/Http/Controllers/AdminController.php
class AdminController (line 19) | class AdminController extends Controller
method checkAdminAccess (line 24) | private function checkAdminAccess(): void
method index (line 39) | public function index()
method getSubscriptionData (line 180) | private function getSubscriptionData(string $subscriptionId, int $disp...
method getSubscriptionPrice (line 239) | private function getSubscriptionPrice(string $subscriptionId, int $dis...
method showUser (line 369) | public function showUser(User $user)
method deleteUser (line 415) | public function deleteUser(Request $request, User $user): RedirectResp...
method impersonate (line 573) | public function impersonate(User $user): RedirectResponse
method stopImpersonating (line 613) | public function stopImpersonating(): RedirectResponse
FILE: backend/app/Http/Controllers/Auth/AuthController.php
class AuthController (line 9) | abstract class AuthController extends Controller
method issueToken (line 11) | protected function issueToken(string $tokenName): string
method createUser (line 19) | protected function createUser(
FILE: backend/app/Http/Controllers/Auth/GoogleController.php
class GoogleController (line 10) | class GoogleController extends SocialAuthController
method token (line 14) | public function token(OAuth2TokenRequest $oauthTokenRequest): Redirect...
method redirect (line 19) | public function redirect(): mixed
method callback (line 24) | public function callback(): RedirectResponse
FILE: backend/app/Http/Controllers/Auth/LoginController.php
class LoginController (line 19) | class LoginController extends Controller
method create (line 24) | public function create(): View
method store (line 35) | public function store(LoginRequest $request): RedirectResponse
method destroy (line 66) | public function destroy(Request $request): RedirectResponse
FILE: backend/app/Http/Controllers/Auth/MicrosoftController.php
class MicrosoftController (line 10) | class MicrosoftController extends SocialAuthController
method token (line 14) | public function token(OAuth2TokenRequest $oauthTokenRequest): Redirect...
method redirect (line 19) | public function redirect(): mixed
method callback (line 24) | public function callback(): RedirectResponse
FILE: backend/app/Http/Controllers/Auth/RegisterController.php
class RegisterController (line 20) | class RegisterController extends Controller
method create (line 25) | public function create(): View
method store (line 36) | public function store(RegisterRequest $request): RedirectResponse
FILE: backend/app/Http/Controllers/Auth/SocialAuthController.php
class SocialAuthController (line 14) | abstract class SocialAuthController extends AuthController
method redirect (line 18) | public function redirect(): mixed
method callback (line 32) | public function callback(): RedirectResponse
method token (line 60) | public function token(OAuth2TokenRequest $oauthTokenRequest): Redirect...
method getSocialUserFromToken (line 76) | private function getSocialUserFromToken(OAuth2TokenRequest $oauthToken...
method validateSocialUser (line 101) | private function validateSocialUser($socialUser): void
method findOrCreateUser (line 119) | protected function findOrCreateUser(mixed $socialUser): User
method authenticateUser (line 145) | protected function authenticateUser(User $user): RedirectResponse
FILE: backend/app/Http/Controllers/BoardController.php
class BoardController (line 21) | class BoardController extends Controller
method __construct (line 23) | public function __construct(
method index (line 32) | public function index(): View|Factory|Application
method create (line 61) | public function create(): View|Factory|Application
method store (line 94) | public function store(CreateBoardRequest $request): RedirectResponse
method show (line 169) | public function show(Board $board): View|Factory|Application
method getTransitioningMinutes (line 196) | private function getTransitioningMinutes($currentEvent, $nextEvent, ?B...
method edit (line 223) | public function edit(Board $board): View|Factory|Application
method update (line 250) | public function update(UpdateBoardRequest $request, Board $board): Red...
method destroy (line 325) | public function destroy(Board $board): RedirectResponse
method serveLogo (line 348) | public function serveLogo(Board $board)
method getDisplayStatusData (line 358) | private function getDisplayStatusData(Collection $displays, ?Board $bo...
method isTransitioning (line 472) | private function isTransitioning($display, $currentEvent, $nextEvent, ...
FILE: backend/app/Http/Controllers/CalDAVAccountsController.php
class CalDAVAccountsController (line 12) | class CalDAVAccountsController extends Controller
method __construct (line 14) | public function __construct(protected CalDAVService $caldavService)
method create (line 18) | public function create(): View
method store (line 23) | public function store(Request $request): RedirectResponse
method delete (line 65) | public function delete(CalDAVAccount $caldavAccount): RedirectResponse
FILE: backend/app/Http/Controllers/CalendarController.php
class CalendarController (line 14) | class CalendarController extends Controller
method __construct (line 16) | public function __construct(
method google (line 23) | public function google(string $id): View|Factory|Application
method outlook (line 62) | public function outlook(string $id): View|Factory|Application
method caldav (line 91) | public function caldav(string $id): View|Factory|Application
FILE: backend/app/Http/Controllers/Controller.php
class Controller (line 9) | class Controller extends BaseController
FILE: backend/app/Http/Controllers/DashboardController.php
class DashboardController (line 17) | class DashboardController extends Controller
method __construct (line 19) | public function __construct(protected OutlookService $outlookService)
method __invoke (line 27) | public function __invoke(): View|Factory|Application
FILE: backend/app/Http/Controllers/DisplayController.php
class DisplayController (line 26) | class DisplayController extends Controller
method __construct (line 28) | public function __construct(
method create (line 35) | public function create(): View
method store (line 65) | public function store(CreateDisplayRequest $request): RedirectResponse
method updateStatus (line 131) | public function updateStatus(Request $request, Display $display): Redi...
method delete (line 146) | public function delete(Display $display): RedirectResponse
method createCalendar (line 167) | private function createCalendar(array $validatedData, $workspace): Cal...
method extractCalendarName (line 224) | private function extractCalendarName(string $value): string
FILE: backend/app/Http/Controllers/DisplaySettingsController.php
class DisplaySettingsController (line 14) | class DisplaySettingsController extends Controller
method __construct (line 16) | public function __construct(
method index (line 20) | public function index(Display $display): View
method update (line 34) | public function update(Request $request, Display $display): RedirectRe...
method customization (line 120) | public function customization(Display $display): View
method updateCustomization (line 134) | public function updateCustomization(UpdateDisplayCustomizationRequest ...
method serveImage (line 235) | public function serveImage(Display $display, string $type)
FILE: backend/app/Http/Controllers/GoogleAccountsController.php
class GoogleAccountsController (line 17) | class GoogleAccountsController extends Controller
method __construct (line 21) | public function __construct(GoogleService $googleService)
method setBookingMethod (line 26) | public function setBookingMethod(Request $request): RedirectResponse
method auth (line 54) | public function auth(Request $request): RedirectResponse
method uploadServiceAccount (line 75) | public function uploadServiceAccount(Request $request): RedirectResponse
method callback (line 145) | public function callback(): RedirectResponse
method delete (line 184) | public function delete(GoogleAccount $googleAccount): RedirectResponse
FILE: backend/app/Http/Controllers/GoogleWebhookController.php
class GoogleWebhookController (line 11) | class GoogleWebhookController extends Controller
method __construct (line 13) | public function __construct(protected GoogleService $googleService)
method handleNotification (line 23) | public function handleNotification(Request $request): Response
FILE: backend/app/Http/Controllers/LicenseController.php
class LicenseController (line 10) | class LicenseController extends Controller
method __construct (line 12) | public function __construct(
method validateLicense (line 16) | public function validateLicense(ActivateLicenseRequest $request)
FILE: backend/app/Http/Controllers/OnboardingController.php
class OnboardingController (line 19) | class OnboardingController extends Controller
method __construct (line 21) | public function __construct(protected OutlookService $outlookService)
method index (line 29) | public function index(): View|RedirectResponse
method updateUsageType (line 47) | public function updateUsageType(Request $request): RedirectResponse
method acceptTerms (line 60) | public function acceptTerms(): RedirectResponse
FILE: backend/app/Http/Controllers/OutlookAccountsController.php
class OutlookAccountsController (line 13) | class OutlookAccountsController extends Controller
method __construct (line 17) | public function __construct(OutlookService $outlookService)
method auth (line 22) | public function auth(Request $request): RedirectResponse
method callback (line 38) | public function callback(): RedirectResponse
method delete (line 55) | public function delete(OutlookAccount $outlookAccount): RedirectResponse
FILE: backend/app/Http/Controllers/OutlookWebhookController.php
class OutlookWebhookController (line 11) | class OutlookWebhookController extends Controller
method __construct (line 13) | public function __construct(protected OutlookService $outlookService)
method handleNotification (line 24) | public function handleNotification(Request $request): Response
FILE: backend/app/Http/Controllers/RoomController.php
class RoomController (line 14) | class RoomController extends Controller
method __construct (line 16) | public function __construct(
method outlook (line 22) | public function outlook(string $id): View|Factory|Application
method google (line 54) | public function google(string $id): View|Factory|Application
FILE: backend/app/Http/Controllers/UsageController.php
class UsageController (line 9) | class UsageController extends Controller
method index (line 14) | public function index(): View|Factory|Application
FILE: backend/app/Http/Controllers/WorkspaceController.php
class WorkspaceController (line 10) | class WorkspaceController extends Controller
method switch (line 19) | public function switch(Request $request): RedirectResponse
FILE: backend/app/Http/Middleware/CheckUserActive.php
class CheckUserActive (line 11) | class CheckUserActive
method handle (line 18) | public function handle(Request $request, Closure $next): Response
FILE: backend/app/Http/Middleware/CheckUserOnboarding.php
class CheckUserOnboarding (line 11) | class CheckUserOnboarding
method handle (line 18) | public function handle(Request $request, Closure $next): Response
FILE: backend/app/Http/Middleware/UpdateLastActivity.php
class UpdateLastActivity (line 11) | class UpdateLastActivity
method handle (line 18) | public function handle(Request $request, Closure $next): Response
FILE: backend/app/Http/Requests/API/Auth/LoginRequest.php
class LoginRequest (line 12) | class LoginRequest extends FormRequest
method authorize (line 19) | public function authorize(): bool
method rules (line 29) | public function rules(): array
method authenticate (line 45) | public function authenticate()
method ensureIsNotRateLimited (line 57) | public function ensureIsNotRateLimited()
method throttleKey (line 80) | public function throttleKey()
FILE: backend/app/Http/Requests/API/BookEventRequest.php
class BookEventRequest (line 7) | class BookEventRequest extends FormRequest
method authorize (line 9) | public function authorize(): bool
method rules (line 14) | public function rules(): array
FILE: backend/app/Http/Requests/API/ChangeDisplayRequest.php
class ChangeDisplayRequest (line 12) | class ChangeDisplayRequest extends FormRequest
method authorize (line 19) | public function authorize(): bool
method rules (line 29) | public function rules(): array
FILE: backend/app/Http/Requests/API/InstanceHeartbeatRequest.php
class InstanceHeartbeatRequest (line 7) | class InstanceHeartbeatRequest extends FormRequest
method authorize (line 9) | public function authorize(): bool
method rules (line 14) | public function rules(): array
method messages (line 34) | public function messages(): array
FILE: backend/app/Http/Requests/API/ValidateInstanceRequest.php
class ValidateInstanceRequest (line 8) | class ValidateInstanceRequest extends FormRequest
method authorize (line 10) | public function authorize(): bool
method rules (line 15) | public function rules(): array
FILE: backend/app/Http/Requests/ActivateLicenseRequest.php
class ActivateLicenseRequest (line 8) | class ActivateLicenseRequest extends FormRequest
method authorize (line 10) | public function authorize(): bool
method rules (line 15) | public function rules(): array
FILE: backend/app/Http/Requests/Auth/LoginRequest.php
class LoginRequest (line 12) | class LoginRequest extends FormRequest
method authorize (line 19) | public function authorize(): bool
method rules (line 29) | public function rules(): array
method authenticate (line 44) | public function authenticate()
method ensureIsNotRateLimited (line 66) | public function ensureIsNotRateLimited()
method throttleKey (line 89) | public function throttleKey()
FILE: backend/app/Http/Requests/Auth/OAuth2TokenRequest.php
class OAuth2TokenRequest (line 7) | class OAuth2TokenRequest extends FormRequest
method authorize (line 12) | public function authorize(): bool
method rules (line 20) | public function rules(): array
method messages (line 31) | public function messages(): array
FILE: backend/app/Http/Requests/Auth/RegisterRequest.php
class RegisterRequest (line 12) | class RegisterRequest extends FormRequest
method authorize (line 19) | public function authorize(): bool
method rules (line 29) | public function rules(): array
FILE: backend/app/Http/Requests/CreateBoardRequest.php
class CreateBoardRequest (line 8) | class CreateBoardRequest extends FormRequest
method authorize (line 15) | public function authorize(): bool
method rules (line 25) | public function rules(): array
method messages (line 61) | public function messages(): array
method prepareForValidation (line 68) | protected function prepareForValidation(): void
FILE: backend/app/Http/Requests/CreateDisplayRequest.php
class CreateDisplayRequest (line 12) | class CreateDisplayRequest extends FormRequest
method authorize (line 19) | public function authorize(): bool
method rules (line 29) | public function rules(): array
FILE: backend/app/Http/Requests/UpdateBoardRequest.php
class UpdateBoardRequest (line 8) | class UpdateBoardRequest extends FormRequest
method authorize (line 15) | public function authorize(): bool
method rules (line 25) | public function rules(): array
method messages (line 64) | public function messages(): array
method prepareForValidation (line 71) | protected function prepareForValidation(): void
FILE: backend/app/Http/Requests/UpdateDisplayCustomizationRequest.php
class UpdateDisplayCustomizationRequest (line 7) | class UpdateDisplayCustomizationRequest extends FormRequest
method authorize (line 9) | public function authorize(): bool
method rules (line 15) | public function rules(): array
FILE: backend/app/Http/Resources/API/DeviceResource.php
class DeviceResource (line 8) | class DeviceResource extends JsonResource
method toArray (line 15) | public function toArray($request): array
FILE: backend/app/Http/Resources/API/DisplayDataResource.php
class DisplayDataResource (line 8) | class DisplayDataResource extends JsonResource
method toArray (line 15) | public function toArray($request): array
FILE: backend/app/Http/Resources/API/DisplayResource.php
class DisplayResource (line 8) | class DisplayResource extends JsonResource
method toArray (line 15) | public function toArray($request): array
FILE: backend/app/Http/Resources/API/DisplaySettingsResource.php
class DisplaySettingsResource (line 8) | class DisplaySettingsResource extends JsonResource
method toArray (line 15) | public function toArray($request): array
FILE: backend/app/Http/Resources/API/EventResource.php
class EventResource (line 10) | class EventResource extends JsonResource
method toArray (line 17) | public function toArray($request): array
FILE: backend/app/Http/Resources/API/UserResource.php
class UserResource (line 8) | class UserResource extends JsonResource
method toArray (line 15) | public function toArray($request): array
FILE: backend/app/Infrastructure/Cloud/LicenseService.php
class LicenseService (line 12) | class LicenseService
method api (line 24) | public static function api(string $method, string $uri, array $payload...
method getLicenseKey (line 44) | public static function getLicenseKey(string $id): array
method activateLicense (line 62) | public static function activateLicense(array $payload = []): array
FILE: backend/app/Listeners/ActivateUser.php
class ActivateUser (line 8) | class ActivateUser
method handle (line 13) | public function handle(UserOnboarded $event): void
FILE: backend/app/Listeners/SendOnboardingCompleteNotification.php
class SendOnboardingCompleteNotification (line 11) | class SendOnboardingCompleteNotification
method handle (line 16) | public function handle(UserOnboarded $event): void
FILE: backend/app/Listeners/SendOrderCreatedNotification.php
class SendOrderCreatedNotification (line 10) | class SendOrderCreatedNotification
method handle (line 15) | public function handle(OrderCreated $event): void
FILE: backend/app/Listeners/SendRegistrationNotification.php
class SendRegistrationNotification (line 9) | class SendRegistrationNotification
method handle (line 14) | public function handle(UserRegistered $event): void
FILE: backend/app/Listeners/SendTrialExpiredOrCancelledNotification.php
class SendTrialExpiredOrCancelledNotification (line 9) | class SendTrialExpiredOrCancelledNotification
method handle (line 14) | public function handle(TrialExpiredOrCancelled $event): void
FILE: backend/app/Listeners/SendUserActivatedAfter24hNotification.php
class SendUserActivatedAfter24hNotification (line 9) | class SendUserActivatedAfter24hNotification
method handle (line 14) | public function handle(UserActivatedAfter24h $event): void
FILE: backend/app/Listeners/SendUserInactiveNotification.php
class SendUserInactiveNotification (line 9) | class SendUserInactiveNotification
method handle (line 14) | public function handle(UserInactive $event): void
FILE: backend/app/Listeners/SendUserNotActivatedAfter24hNotification.php
class SendUserNotActivatedAfter24hNotification (line 9) | class SendUserNotActivatedAfter24hNotification
method handle (line 14) | public function handle(UserNotActivatedAfter24h $event): void
FILE: backend/app/Listeners/SendUserPassiveNotification.php
class SendUserPassiveNotification (line 9) | class SendUserPassiveNotification
method handle (line 14) | public function handle(UserPassive $event): void
FILE: backend/app/Models/Board.php
class Board (line 12) | class Board extends Model
method workspace (line 47) | public function workspace(): BelongsTo
method user (line 52) | public function user(): BelongsTo
method displays (line 57) | public function displays(): BelongsToMany
method getDisplaysToShowQuery (line 68) | public function getDisplaysToShowQuery()
method getDisplaysToShow (line 85) | public function getDisplaysToShow()
method hasDisplay (line 96) | public function hasDisplay(Display $display): bool
method getDisplayCountAttribute (line 111) | public function getDisplayCountAttribute(): int
FILE: backend/app/Models/CalDAVAccount.php
class CalDAVAccount (line 14) | class CalDAVAccount extends Model
method user (line 44) | public function user(): BelongsTo
method calendars (line 49) | public function calendars(): HasMany
method workspace (line 54) | public function workspace(): BelongsTo
FILE: backend/app/Models/Calendar.php
class Calendar (line 13) | class Calendar extends Model
method outlookAccount (line 29) | public function outlookAccount(): ?BelongsTo
method googleAccount (line 34) | public function googleAccount(): ?BelongsTo
method caldavAccount (line 39) | public function caldavAccount(): ?BelongsTo
method room (line 44) | public function room(): HasOne
method displays (line 49) | public function displays(): HasMany
method events (line 54) | public function events(): HasMany
method workspace (line 59) | public function workspace(): BelongsTo
FILE: backend/app/Models/Device.php
class Device (line 14) | class Device extends Model implements Authenticatable
method display (line 35) | public function display(): BelongsTo
method user (line 40) | public function user(): BelongsTo
method workspace (line 45) | public function workspace(): BelongsTo
FILE: backend/app/Models/Display.php
class Display (line 15) | class Display extends Model
method calendar (line 37) | public function calendar(): BelongsTo
method user (line 42) | public function user(): BelongsTo
method workspace (line 47) | public function workspace(): BelongsTo
method eventSubscriptions (line 52) | public function eventSubscriptions(): HasMany
method events (line 57) | public function events(): HasMany
method devices (line 62) | public function devices(): HasMany
method settings (line 67) | public function settings(): HasMany
method boards (line 72) | public function boards(): BelongsToMany
method getStartTime (line 78) | public function getStartTime(): Carbon
method getEndTime (line 83) | public function getEndTime(): Carbon
method getEventsCacheKey (line 88) | public function getEventsCacheKey(): string
method getEventsCacheKeyForDisplay (line 93) | public static function getEventsCacheKeyForDisplay(string $displayId):...
method isDeactivated (line 98) | public function isDeactivated(): bool
method updateLastEventAt (line 103) | public function updateLastEventAt(Carbon|null $date = null): void
method updateLastSyncAt (line 108) | public function updateLastSyncAt(Carbon|null $date = null): void
method isCheckInEnabled (line 114) | public function isCheckInEnabled(): bool
method isBookingEnabled (line 119) | public function isBookingEnabled(): bool
method hasCustomBooking (line 124) | public function hasCustomBooking(): bool
method setCheckInEnabled (line 134) | public function setCheckInEnabled(bool $enabled): bool
method setBookingEnabled (line 139) | public function setBookingEnabled(bool $enabled): bool
method getCheckInMinutes (line 144) | public function getCheckInMinutes(): int
method setCheckInMinutes (line 149) | public function setCheckInMinutes(int $minutes): bool
method getCheckInGracePeriod (line 154) | public function getCheckInGracePeriod(): int
method setCheckInGracePeriod (line 159) | public function setCheckInGracePeriod(int $minutes): bool
method isCalendarEnabled (line 164) | public function isCalendarEnabled(): bool
method setCalendarEnabled (line 169) | public function setCalendarEnabled(bool $enabled): bool
method getAvailableText (line 174) | public function getAvailableText(): ?string
method getTransitioningText (line 179) | public function getTransitioningText(): ?string
method getReservedText (line 184) | public function getReservedText(): ?string
method getCheckInText (line 189) | public function getCheckInText(): ?string
method getLogoUrl (line 194) | public function getLogoUrl(): ?string
method getBackgroundImageUrl (line 199) | public function getBackgroundImageUrl(): ?string
method getShowMeetingTitle (line 204) | public function getShowMeetingTitle(): bool
method getFontFamily (line 209) | public function getFontFamily(): string
method isAdminActionsHidden (line 214) | public function isAdminActionsHidden(): bool
method getCancelPermission (line 219) | public function getCancelPermission(): string
method getBorderThickness (line 224) | public function getBorderThickness(): string
FILE: backend/app/Models/DisplaySetting.php
class DisplaySetting (line 10) | class DisplaySetting extends Model
method display (line 25) | public function display(): BelongsTo
method getValueAttribute (line 30) | public function getValueAttribute($value)
method setValueAttribute (line 48) | public function setValueAttribute($value)
FILE: backend/app/Models/Event.php
class Event (line 11) | class Event extends Model
method display (line 37) | public function display(): BelongsTo
method user (line 42) | public function user(): BelongsTo
method calendar (line 47) | public function calendar(): BelongsTo
method isCustomEvent (line 55) | public function isCustomEvent(): bool
method isTabletBooking (line 65) | public function isTabletBooking(): bool
method isActive (line 80) | public function isActive(): bool
method isUpcoming (line 89) | public function isUpcoming(): bool
method checkIn (line 99) | public function checkIn(): void
method getUniqueKey (line 109) | public function getUniqueKey(): string
method checkInRequired (line 117) | public function checkInRequired(): bool
FILE: backend/app/Models/EventSubscription.php
class EventSubscription (line 12) | class EventSubscription extends Model
method scopeExpired (line 28) | public function scopeExpired(Builder $query)
method outlookAccount (line 33) | public function outlookAccount(): BelongsTo
method googleAccount (line 38) | public function googleAccount(): BelongsTo
method display (line 43) | public function display(): BelongsTo
FILE: backend/app/Models/GoogleAccount.php
class GoogleAccount (line 15) | class GoogleAccount extends Model
method user (line 51) | public function user(): BelongsTo
method calendars (line 56) | public function calendars(): HasMany
method isBusiness (line 61) | public function isBusiness(): bool
method workspace (line 66) | public function workspace(): BelongsTo
FILE: backend/app/Models/Instance.php
class Instance (line 11) | class Instance extends Model
method user (line 39) | public function user(): BelongsTo
FILE: backend/app/Models/OutlookAccount.php
class OutlookAccount (line 14) | class OutlookAccount extends Model
method isBusiness (line 47) | public function isBusiness(): bool
method calendars (line 52) | public function calendars(): HasMany
method workspace (line 57) | public function workspace(): BelongsTo
FILE: backend/app/Models/PersonalAccessToken.php
class PersonalAccessToken (line 8) | class PersonalAccessToken extends SanctumPersonalAccessToken
FILE: backend/app/Models/Room.php
class Room (line 13) | class Room extends Model
method calendar (line 26) | public function calendar(): BelongsTo
method user (line 31) | public function user(): BelongsTo
method workspace (line 36) | public function workspace(): BelongsTo
FILE: backend/app/Models/Setting.php
class Setting (line 9) | class Setting extends Model
method getValueAttribute (line 23) | public function getValueAttribute($value)
method setValueAttribute (line 41) | public function setValueAttribute($value)
FILE: backend/app/Models/User.php
class User (line 20) | class User extends Authenticatable
method boot (line 27) | protected static function boot()
method outlookAccounts (line 96) | public function outlookAccounts(): HasMany
method googleAccounts (line 101) | public function googleAccounts(): HasMany
method caldavAccounts (line 106) | public function caldavAccounts(): HasMany
method displays (line 111) | public function displays(): HasMany
method devices (line 116) | public function devices(): HasMany
method rooms (line 121) | public function rooms(): HasMany
method boards (line 126) | public function boards(): HasMany
method ownedWorkspaces (line 134) | public function ownedWorkspaces()
method workspaces (line 142) | public function workspaces(): BelongsToMany
method primaryWorkspace (line 152) | public function primaryWorkspace(): ?Workspace
method accessibleWorkspaces (line 160) | public function accessibleWorkspaces()
method hasAnyDisplay (line 165) | public function hasAnyDisplay(): bool
method hasAnyAccount (line 170) | public function hasAnyAccount(): bool
method getConnectCode (line 180) | public function getConnectCode(): string
method pullConnectCode (line 203) | public static function pullConnectCode(string $code): ?string
method isOnboarded (line 216) | public function isOnboarded(): bool
method hasPro (line 242) | public function hasPro(): bool
method hasProForCurrentWorkspace (line 255) | public function hasProForCurrentWorkspace(): bool
method hasProForWorkspace (line 275) | public function hasProForWorkspace(Workspace $workspace): bool
method isBusinessUser (line 289) | public function isBusinessUser(): bool
method isPersonalUser (line 297) | public function isPersonalUser(): bool
method shouldUpgrade (line 305) | public function shouldUpgrade(): bool
method shouldUpgradeForCurrentWorkspace (line 320) | public function shouldUpgradeForCurrentWorkspace(): bool
method getCheckoutUrl (line 343) | public function getCheckoutUrl(?string $redirectUrl = null): ?Checkout
method isAllowedLogin (line 361) | public static function isAllowedLogin(string $email): bool
method isAdmin (line 382) | public function isAdmin(): bool
method getSelectedWorkspace (line 393) | public function getSelectedWorkspace(): ?Workspace
FILE: backend/app/Models/Workspace.php
class Workspace (line 14) | class Workspace extends Model
method members (line 25) | public function members(): BelongsToMany
method displays (line 35) | public function displays(): HasMany
method devices (line 43) | public function devices(): HasMany
method calendars (line 51) | public function calendars(): HasMany
method rooms (line 59) | public function rooms(): HasMany
method boards (line 67) | public function boards(): HasMany
method hasMember (line 75) | public function hasMember(User $user): bool
method owners (line 83) | public function owners()
method isOwnedBy (line 91) | public function isOwnedBy(User $user): bool
method canBeManagedBy (line 99) | public function canBeManagedBy(User $user): bool
method getUserRole (line 116) | public function getUserRole(User $user): ?WorkspaceRole
method hasPro (line 130) | public function hasPro(): bool
method getTotalUsageCount (line 151) | public function getTotalUsageCount(): int
method getUsageBreakdown (line 164) | public function getUsageBreakdown(): array
FILE: backend/app/Models/WorkspaceMember.php
class WorkspaceMember (line 11) | class WorkspaceMember extends Model
method workspace (line 30) | public function workspace(): BelongsTo
method user (line 38) | public function user(): BelongsTo
FILE: backend/app/Notifications/MagicLoginNotification.php
class MagicLoginNotification (line 10) | class MagicLoginNotification extends Notification
method __construct (line 17) | public function __construct(public string $loginUrl)
method via (line 27) | public function via(object $notifiable): array
method toMail (line 35) | public function toMail(object $notifiable): MailMessage
method toArray (line 53) | public function toArray(object $notifiable): array
FILE: backend/app/Observers/EventObserver.php
class EventObserver (line 8) | class EventObserver
method created (line 13) | public function created(Event $event): void
method updated (line 21) | public function updated(Event $event): void
method deleted (line 29) | public function deleted(Event $event): void
method clearDisplayCache (line 37) | protected function clearDisplayCache(Event $event): void
FILE: backend/app/Policies/BoardPolicy.php
class BoardPolicy (line 9) | class BoardPolicy
method create (line 16) | public function create(User $user): bool
method view (line 25) | public function view(User $user, Board $board): bool
method update (line 34) | public function update(User $user, Board $board): bool
method delete (line 48) | public function delete(User $user, Board $board): bool
FILE: backend/app/Policies/DisplayPolicy.php
class DisplayPolicy (line 10) | class DisplayPolicy
method create (line 17) | public function create(User $user): bool
method update (line 25) | public function update(User $user, Display $display): bool
method delete (line 38) | public function delete(User $user, Display $display): bool
method view (line 51) | public function view($user, Display $display): bool
FILE: backend/app/Providers/AppServiceProvider.php
class AppServiceProvider (line 15) | class AppServiceProvider extends ServiceProvider
method register (line 20) | public function register(): void
method boot (line 31) | public function boot(): void
FILE: backend/app/Providers/AuthServiceProvider.php
class AuthServiceProvider (line 11) | class AuthServiceProvider extends ServiceProvider
method boot (line 26) | public function boot(): void
FILE: backend/app/Services/CalDAVService.php
class CalDAVService (line 15) | class CalDAVService
method __construct (line 19) | public function __construct()
method configureClient (line 28) | private function configureClient(CalDAVAccount $account): void
method fetchCalendars (line 37) | public function fetchCalendars(CalDAVAccount $account): array
method fetchEvents (line 73) | public function fetchEvents(
method createEvent (line 150) | public function createEvent(
method deleteEvent (line 203) | public function deleteEvent(
method checkConnection (line 234) | public function checkConnection(string $url, string $username, string ...
FILE: backend/app/Services/DisplayService.php
class DisplayService (line 10) | class DisplayService
method getDisplay (line 12) | public function getDisplay(string $displayId)
method validateDisplayPermission (line 25) | public function validateDisplayPermission(?string $displayId, string $...
FILE: backend/app/Services/EventService.php
class EventService (line 21) | class EventService
method __construct (line 23) | public function __construct(
method getEventsForDisplay (line 34) | public function getEventsForDisplay($display): Collection
method bookRoom (line 65) | public function bookRoom(string $displayId, string $userId, string $su...
method cancelEvent (line 232) | public function cancelEvent(string $eventId, string $displayId): void
method getAllEvents (line 351) | private function getAllEvents(Display $display): Collection
method syncAllExternalEventsForDisplay (line 368) | private function syncAllExternalEventsForDisplay(Display $display): void
method fetchOutlookEvents (line 399) | private function fetchOutlookEvents(Calendar $calendar, Display $displ...
method fetchGoogleEvents (line 432) | private function fetchGoogleEvents(Calendar $calendar, Display $displa...
method fetchCalDAVEvents (line 477) | private function fetchCalDAVEvents(Calendar $calendar, Display $displa...
method sanitizeOutlookEvent (line 493) | public function sanitizeOutlookEvent(array $outlookEvent): array
method sanitizeGoogleEvent (line 532) | public function sanitizeGoogleEvent(GoogleEvent $googleEvent): array
method sanitizeCalDAVEvent (line 556) | public function sanitizeCalDAVEvent(array $caldavEvent): array
method cleanSubject (line 570) | private function cleanSubject(?string $subject): string
method cleanBody (line 578) | private function cleanBody(?string $body): string
method truncateDescription (line 592) | private function truncateDescription(?string $description): string
method hasConflictingEvents (line 617) | public function hasConflictingEvents(string $displayId, Carbon $start,...
method syncExternalEvents (line 644) | public function syncExternalEvents(Display $display, string $source, C...
method checkInToEvent (line 707) | public function checkInToEvent(string $eventId, string $displayId): void
method waitForEventInApi (line 736) | private function waitForEventInApi(Calendar $calendar, string $externa...
method processExpiredCheckIns (line 804) | private function processExpiredCheckIns(Display $display): void
FILE: backend/app/Services/GoogleService.php
class GoogleService (line 24) | class GoogleService
method __construct (line 28) | public function __construct()
method authenticateGoogleAccount (line 47) | public function authenticateGoogleAccount(string $authCode, Permission...
method isGoogleBusiness (line 94) | public function isGoogleBusiness(GoogleAccount $account): bool
method getAuthUrl (line 123) | public function getAuthUrl(PermissionType $permissionType = Permission...
method ensureAuthenticated (line 144) | private function ensureAuthenticated(GoogleAccount $account): void
method refreshToken (line 159) | private function refreshToken(GoogleAccount $account): void
method fetchCalendars (line 175) | public function fetchCalendars(GoogleAccount $account): array
method fetchRooms (line 185) | public function fetchRooms(GoogleAccount $account): array
method fetchEvents (line 199) | public function fetchEvents(
method createEvent (line 231) | public function createEvent(
method deleteEvent (line 294) | public function deleteEvent(
method deleteRoomEvent (line 338) | private function deleteRoomEvent(
method createEventSubscription (line 366) | public function createEventSubscription(
method deleteEventSubscription (line 447) | public function deleteEventSubscription(
method getServiceAccountClient (line 485) | private function getServiceAccountClient(GoogleAccount $googleAccount)...
method createRoomEventWithServiceAccount (line 536) | private function createRoomEventWithServiceAccount(
method deleteRoomEventWithServiceAccount (line 564) | private function deleteRoomEventWithServiceAccount(
FILE: backend/app/Services/ImageService.php
class ImageService (line 9) | class ImageService
method getDefaultBackgrounds (line 28) | public function getDefaultBackgrounds(): array
method getLogoUrl (line 41) | public function getLogoUrl(Display $display): ?string
method getBackgroundImageUrl (line 56) | public function getBackgroundImageUrl(Display $display): ?string
method getImageVersion (line 76) | private function getImageVersion(Display $display, string $type): string
method serveImage (line 94) | public function serveImage(Display $display, string $type)
method storeLogoFile (line 122) | public function storeLogoFile($file, Display $display): ?string
method storeBackgroundImageFile (line 136) | public function storeBackgroundImageFile($file, Display $display): ?st...
method removeLogoFile (line 150) | public function removeLogoFile(Display $display): void
method removeBackgroundImageFile (line 161) | public function removeBackgroundImageFile(Display $display): void
method getBoardLogoUrl (line 172) | public function getBoardLogoUrl(\App\Models\Board $board): ?string
method storeBoardLogoFile (line 186) | public function storeBoardLogoFile($file, \App\Models\Board $board): ?...
method removeBoardLogoFile (line 200) | public function removeBoardLogoFile(\App\Models\Board $board): void
method serveBoardLogo (line 210) | public function serveBoardLogo(\App\Models\Board $board)
FILE: backend/app/Services/InstanceService.php
class InstanceService (line 16) | class InstanceService
method hasValidLicense (line 20) | public static function hasValidLicense(): bool
method hasLicense (line 36) | public static function hasLicense(): bool
method updateLicense (line 41) | public static function updateLicense(LicenseData $data): bool
method storeInstanceVariable (line 53) | public static function storeInstanceVariable(string $key, ?string $val...
method getInstanceVariable (line 69) | public static function getInstanceVariable(string $key, mixed $default...
method getInstanceKey (line 79) | private static function getInstanceKey(): string
method generateInstanceKey (line 92) | private static function generateInstanceKey(): string
method getSettingKey (line 97) | private static function getSettingKey(string $key): string
method getInstanceData (line 102) | public static function getInstanceData(): InstanceData
FILE: backend/app/Services/OutlookService.php
class OutlookService (line 16) | class OutlookService
method __construct (line 25) | public function __construct()
method ensureAuthenticated (line 37) | private function ensureAuthenticated(&$outlookAccount): void
method getAuthUrl (line 53) | public function getAuthUrl(PermissionType $permissionType = Permission...
method authenticateOutlookAccount (line 79) | public function authenticateOutlookAccount(string $authCode, string|Pe...
method getTenantId (line 140) | public function getTenantId(string $token): ?string
method refreshToken (line 169) | protected function refreshToken(OutlookAccount &$outlookAccount): void
method fetchEventsByUser (line 209) | public function fetchEventsByUser(
method fetchEventsByCalendar (line 241) | public function fetchEventsByCalendar(
method fetchCalendars (line 270) | public function fetchCalendars(OutlookAccount $outlookAccount): mixed
method fetchRooms (line 289) | public function fetchRooms(OutlookAccount $outlookAccount): mixed
method createEvent (line 312) | public function createEvent(
method deleteEvent (line 367) | public function deleteEvent(
method createEventSubscriptionByUser (line 406) | public function createEventSubscriptionByUser(
method createEventSubscriptionByCalendar (line 438) | public function createEventSubscriptionByCalendar(
method createEventSubscription (line 455) | private function createEventSubscription(
method deleteEventSubscription (line 539) | public function deleteEventSubscription(
FILE: backend/app/Traits/HasLastActivity.php
type HasLastActivity (line 5) | trait HasLastActivity
method updateLastActivity (line 10) | public function updateLastActivity(): void
FILE: backend/app/Traits/HasUlid.php
type HasUlid (line 7) | trait HasUlid
method bootHasUlid (line 9) | public static function bootHasUlid(): void
method getIncrementing (line 16) | public function getIncrementing(): bool
method getKeyType (line 21) | public function getKeyType(): string
method getCasts (line 26) | public function getCasts(): array
FILE: backend/app/Traits/RespondsWithApiResponse.php
type RespondsWithApiResponse (line 8) | trait RespondsWithApiResponse
method respond (line 10) | protected function respond(ApiResponse $response): JsonResponse
FILE: backend/database/factories/BoardFactory.php
class BoardFactory (line 13) | class BoardFactory extends Factory
method definition (line 27) | public function definition(): array
FILE: backend/database/factories/CalDAVAccountFactory.php
class CalDAVAccountFactory (line 13) | class CalDAVAccountFactory extends Factory
method definition (line 27) | public function definition(): array
FILE: backend/database/factories/CalendarFactory.php
class CalendarFactory (line 12) | class CalendarFactory extends Factory
method definition (line 26) | public function definition(): array
method primary (line 39) | public function primary(): static
method outlook (line 49) | public function outlook(): static
method google (line 59) | public function google(): static
method caldav (line 69) | public function caldav(): static
FILE: backend/database/factories/DeviceFactory.php
class DeviceFactory (line 13) | class DeviceFactory extends Factory
method definition (line 27) | public function definition(): array
FILE: backend/database/factories/DisplayFactory.php
class DisplayFactory (line 14) | class DisplayFactory extends Factory
method definition (line 28) | public function definition(): array
method active (line 42) | public function active(): static
method deactivated (line 52) | public function deactivated(): static
FILE: backend/database/factories/EventSubscriptionFactory.php
class EventSubscriptionFactory (line 11) | class EventSubscriptionFactory extends Factory
method definition (line 15) | public function definition(): array
method outlook (line 28) | public function outlook(OutlookAccount $account): self
method google (line 38) | public function google(GoogleAccount $account): self
FILE: backend/database/factories/GoogleAccountFactory.php
class GoogleAccountFactory (line 13) | class GoogleAccountFactory extends Factory
method definition (line 27) | public function definition(): array
method business (line 46) | public function business(): static
FILE: backend/database/factories/InstanceFactory.php
class InstanceFactory (line 11) | class InstanceFactory extends Factory
method definition (line 25) | public function definition(): array
FILE: backend/database/factories/OutlookAccountFactory.php
class OutlookAccountFactory (line 13) | class OutlookAccountFactory extends Factory
method definition (line 27) | public function definition(): array
method business (line 46) | public function business(): static
FILE: backend/database/factories/RoomFactory.php
class RoomFactory (line 12) | class RoomFactory extends Factory
method definition (line 26) | public function definition(): array
FILE: backend/database/factories/UserFactory.php
class UserFactory (line 15) | class UserFactory extends Factory
method definition (line 27) | public function definition(): array
method unverified (line 44) | public function unverified(): static
method active (line 54) | public function active(): static
FILE: backend/database/factories/WorkspaceFactory.php
class WorkspaceFactory (line 11) | class WorkspaceFactory extends Factory
method definition (line 25) | public function definition(): array
FILE: backend/database/migrations/2014_10_12_000000_create_users_table.php
method up (line 12) | public function up(): void
method down (line 31) | public function down(): void
FILE: backend/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php
method up (line 12) | public function up(): void
method down (line 24) | public function down(): void
FILE: backend/database/migrations/2017_07_06_000000_create_table_magic_links.php
class CreateTableMagicLinks (line 7) | class CreateTableMagicLinks extends Migration
method up (line 14) | public function up()
method down (line 32) | public function down()
FILE: backend/database/migrations/2019_08_19_000000_create_failed_jobs_table.php
method up (line 12) | public function up(): void
method down (line 28) | public function down(): void
FILE: backend/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
method up (line 12) | public function up(): void
method down (line 29) | public function down(): void
FILE: backend/database/migrations/2021_03_06_211907_add_access_code_to_magic_links_table.php
class AddAccessCodeToMagicLinksTable (line 7) | class AddAccessCodeToMagicLinksTable extends Migration
method up (line 14) | public function up()
method down (line 26) | public function down()
FILE: backend/database/migrations/2024_03_19_000000_add_usage_type_to_users_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2024_10_08_193424_create_outlook_accounts_table.php
method up (line 12) | public function up(): void
method down (line 31) | public function down(): void
FILE: backend/database/migrations/2024_10_08_193455_create_calendars_table.php
method up (line 12) | public function up(): void
method down (line 28) | public function down(): void
FILE: backend/database/migrations/2024_10_12_203020_create_displays_table.php
method up (line 12) | public function up(): void
method down (line 30) | public function down(): void
FILE: backend/database/migrations/2024_10_17_212003_create_event_subscriptions_table.php
method up (line 12) | public function up(): void
method down (line 29) | public function down(): void
FILE: backend/database/migrations/2025_01_12_122905_create_devices_table.php
method up (line 12) | public function up(): void
method down (line 26) | public function down(): void
FILE: backend/database/migrations/2025_01_12_190259_create_rooms_table.php
method up (line 12) | public function up(): void
method down (line 27) | public function down(): void
FILE: backend/database/migrations/2025_05_04_204354_remove_unique_from_outlook_accounts.php
method up (line 12) | public function up(): void
method down (line 23) | public function down(): void
FILE: backend/database/migrations/2025_05_07_181029_create_sessions_table.php
method up (line 12) | public function up(): void
method down (line 27) | public function down(): void
FILE: backend/database/migrations/2025_05_07_181034_create_cache_table.php
method up (line 12) | public function up(): void
method down (line 30) | public function down(): void
FILE: backend/database/migrations/2025_05_17_130507_create_google_accounts_table.php
method up (line 9) | public function up(): void
method down (line 25) | public function down(): void
FILE: backend/database/migrations/2025_05_17_153857_add_google_account_id_to_calendars_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_05_18_010101_remove_unique_from_google_accounts.php
method up (line 12) | public function up(): void
method down (line 23) | public function down(): void
FILE: backend/database/migrations/2025_05_18_010201_remove_unique_from_calendars.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_05_18_114502_add_status_to_accounts.php
method up (line 13) | public function up(): void
method down (line 27) | public function down(): void
FILE: backend/database/migrations/2025_05_21_000000_add_google_account_id_to_event_subscriptions.php
method up (line 12) | public function up(): void
method down (line 26) | public function down(): void
FILE: backend/database/migrations/2025_05_23_000000_create_caldav_accounts_table.php
method up (line 13) | public function up(): void
method down (line 32) | public function down(): void
FILE: backend/database/migrations/2025_05_23_000001_add_caldav_account_id_to_calendars_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_05_23_201433_add_google_id_to_users_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_05_27_203928_add_last_activity_at_to_users_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_05_27_204843_add_last_activity_at_to_devices_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_05_28_193657_add_is_billing_exempt_to_users_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_05_28_194845_add_is_unlimited_to_users_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_06_08_000001_add_terms_accepted_at_to_users_table.php
method up (line 9) | public function up(): void
method down (line 16) | public function down(): void
FILE: backend/database/migrations/2025_06_09_115819_drop_is_billing_exempt_from_users_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_06_09_122516_add_hosted_domain_to_google_accounts_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_06_09_122702_add_tenant_id_to_outlook_accounts_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_06_09_125231_add_uid_to_devices_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_06_09_150001_create_instances_table.php
method up (line 9) | public function up(): void
method down (line 28) | public function down(): void
FILE: backend/database/migrations/2025_06_15_000000_create_settings_table.php
method up (line 9) | public function up(): void
method down (line 20) | public function down(): void
FILE: backend/database/migrations/2025_06_15_120000_change_billable_id_to_ulid_on_lemonsqueezy_tables.php
method up (line 11) | public function up(): void
method down (line 66) | public function down(): void
FILE: backend/database/migrations/2025_06_16_000000_create_events_table.php
method up (line 9) | public function up(): void
method down (line 39) | public function down(): void
FILE: backend/database/migrations/2025_07_05_000000_create_display_settings_table.php
method up (line 12) | public function up(): void
method down (line 30) | public function down(): void
FILE: backend/database/migrations/2025_07_05_000001_alter_avatar_column_on_google_accounts_table.php
method up (line 8) | public function up(): void
method down (line 15) | public function down(): void
FILE: backend/database/migrations/2025_07_27_000000_add_is_admin_to_users_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_11_28_000000_add_permission_type_to_outlook_accounts_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_11_28_000001_add_permission_type_to_google_accounts_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_11_28_000002_add_permission_type_to_caldav_accounts_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2025_12_03_000000_add_service_account_file_path_to_google_accounts_table.php
method up (line 11) | public function up(): void
method down (line 21) | public function down(): void
FILE: backend/database/migrations/2025_12_04_000000_add_booking_method_to_google_accounts_table.php
method up (line 11) | public function up(): void
method down (line 21) | public function down(): void
FILE: backend/database/migrations/2025_12_05_000000_encrypt_existing_tokens_in_google_and_outlook_accounts.php
method isEncrypted (line 12) | private function isEncrypted(string $value): bool
method up (line 34) | public function up(): void
method down (line 91) | public function down(): void
FILE: backend/database/migrations/2025_12_06_000003_add_first_name_and_last_name_to_users_table.php
method up (line 12) | public function up(): void
method down (line 26) | public function down(): void
FILE: backend/database/migrations/2025_12_30_000000_create_workspaces_table.php
method up (line 12) | public function up(): void
method down (line 24) | public function down(): void
FILE: backend/database/migrations/2025_12_30_000001_create_workspace_members_table.php
method up (line 12) | public function up(): void
method down (line 29) | public function down(): void
FILE: backend/database/migrations/2025_12_30_000002_add_workspace_id_to_tables.php
method up (line 12) | public function up(): void
method down (line 38) | public function down(): void
FILE: backend/database/migrations/2025_12_30_000003_add_workspace_id_to_accounts_tables.php
method up (line 12) | public function up(): void
method down (line 33) | public function down(): void
FILE: backend/database/migrations/2025_12_30_000004_create_workspaces_for_existing_users.php
method up (line 24) | public function up(): void
method down (line 82) | public function down(): void
FILE: backend/database/migrations/2026_02_28_000000_increase_events_description_column_size.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2026_02_28_000001_increase_caldav_accounts_password_column_size.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2026_02_28_120000_create_boards_table.php
method up (line 12) | public function up(): void
method down (line 29) | public function down(): void
FILE: backend/database/migrations/2026_02_28_120001_create_board_displays_table.php
method up (line 12) | public function up(): void
method down (line 29) | public function down(): void
FILE: backend/database/migrations/2026_02_28_120002_add_theme_to_boards_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2026_02_28_120003_add_logo_to_boards_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2026_02_28_120004_add_display_options_to_boards_table.php
method up (line 12) | public function up(): void
method down (line 24) | public function down(): void
FILE: backend/database/migrations/2026_02_28_120005_add_additional_settings_to_boards_table.php
method up (line 12) | public function up(): void
method down (line 26) | public function down(): void
FILE: backend/database/migrations/2026_02_28_120007_add_title_and_subtitle_to_boards_table.php
method up (line 12) | public function up(): void
method down (line 23) | public function down(): void
FILE: backend/database/migrations/2026_02_28_120008_add_view_mode_to_boards_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/migrations/2026_02_28_140000_add_boards_count_to_instances_table.php
method up (line 12) | public function up(): void
method down (line 22) | public function down(): void
FILE: backend/database/seeders/DatabaseSeeder.php
class DatabaseSeeder (line 9) | class DatabaseSeeder extends Seeder
method run (line 14) | public function run(): void
FILE: backend/tests/Feature/DisplaySettingsApiTest.php
class DisplaySettingsApiTest (line 13) | class DisplaySettingsApiTest extends TestCase
method test_display_api_includes_settings (line 17) | public function test_display_api_includes_settings()
method test_display_api_includes_default_settings_when_none_set (line 60) | public function test_display_api_includes_default_settings_when_none_s...
method test_display_settings_are_encrypted_in_database (line 87) | public function test_display_settings_are_encrypted_in_database()
FILE: backend/tests/Pest.php
function something (line 43) | function something()
FILE: backend/tests/TestCase.php
class TestCase (line 7) | abstract class TestCase extends BaseTestCase
method setUp (line 9) | protected function setUp(): void
FILE: k6/load-test.js
constant CONTINUOUS_MODE (line 22) | const CONTINUOUS_MODE = __ENV.CONTINUOUS === 'true' || __ENV.CONTINUOUS ...
constant BASE_URL (line 23) | const BASE_URL = __ENV.BACKEND_URL || 'http://localhost:8000';
constant CONNECT_CODE (line 24) | const CONNECT_CODE = __ENV.CONNECT_CODE || '100001';
function setup (line 64) | function setup() {
function teardown (line 330) | function teardown(data) {
FILE: k6/tags.js
function tags (line 6) | function tags(data) {
Condensed preview — 461 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,355K chars).
[
{
"path": ".github/workflows/docker-build.yml",
"chars": 2284,
"preview": "name: Build and Push Docker Image\n\non:\n push:\n branches:\n - main\n - dev\n paths:\n - 'backend/**'\n "
},
{
"path": ".github/workflows/tests.yml",
"chars": 2048,
"preview": "name: Tests\n\non:\n push:\n branches:\n - main\n - dev\n paths:\n - 'backend/**'\n - '.github/workflo"
},
{
"path": ".gitignore",
"chars": 29,
"preview": ".env\ndatabase.sqlite\n.claude/"
},
{
"path": "CLAUDE.md",
"chars": 5332,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "CONTRIBUTING.md",
"chars": 2640,
"preview": "# Contributing to Spacepad\n\nThank you for your interest in contributing to Spacepad! This document provides guidelines a"
},
{
"path": "LICENSE.md",
"chars": 2065,
"preview": "Spacepad Community License (Sustainable Use License)\n----------------------------------------------------\n\nCopyright (c)"
},
{
"path": "LICENSE_PRO.md",
"chars": 1429,
"preview": "Spacepad Pro License (Commercial Use)\n--------------------------------------\n\nCopyright (c) 2025 Spacepad.io\n\nThis licen"
},
{
"path": "README.md",
"chars": 5685,
"preview": "<p align=\"center\" style=\"margin-top: 120px\">\n <h1 align=\"center\">Spacepad</h3>\n\n <p align=\"center\">Simple room display"
},
{
"path": "app/.gitignore",
"chars": 740,
"preview": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.build/\n.buildlog/\n.history\n.svn/\n.swiftpm/\nmigrate_working_d"
},
{
"path": "app/.metadata",
"chars": 1114,
"preview": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrade"
},
{
"path": "app/README.md",
"chars": 577,
"preview": "# spacepad\n\nA simple and fun meeting room occupancy display.\n\n## Getting Started\n\nThis project is a starting point for a"
},
{
"path": "app/analysis_options.yaml",
"chars": 1420,
"preview": "# This file configures the analyzer, which statically analyzes Dart code to\n# check for errors, warnings, and lints.\n#\n#"
},
{
"path": "app/android/.gitignore",
"chars": 247,
"preview": "gradle-wrapper.jar\n/.gradle\n/captures/\n/gradlew\n/gradlew.bat\n/local.properties\nGeneratedPluginRegistrant.java\n\n# Remembe"
},
{
"path": "app/android/app/.gitignore",
"chars": 5,
"preview": ".cxx/"
},
{
"path": "app/android/app/build.gradle.kts",
"chars": 1851,
"preview": "import java.util.Properties\nimport java.io.FileInputStream\n\nplugins {\n id(\"com.android.application\")\n id(\"kotlin-a"
},
{
"path": "app/android/app/src/debug/AndroidManifest.xml",
"chars": 378,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <!-- The INTERNET permission is required for d"
},
{
"path": "app/android/app/src/main/AndroidManifest.xml",
"chars": 2304,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <applic"
},
{
"path": "app/android/app/src/main/kotlin/com/magweter/spacepad/MainActivity.kt",
"chars": 123,
"preview": "package com.magweter.spacepad\n\nimport io.flutter.embedding.android.FlutterActivity\n\nclass MainActivity : FlutterActivity"
},
{
"path": "app/android/app/src/main/res/drawable/launch_background.xml",
"chars": 434,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmln"
},
{
"path": "app/android/app/src/main/res/drawable-v21/launch_background.xml",
"chars": 438,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmln"
},
{
"path": "app/android/app/src/main/res/values/styles.xml",
"chars": 996,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- Theme applied to the Android Window while the process is sta"
},
{
"path": "app/android/app/src/main/res/values-night/styles.xml",
"chars": 995,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!-- Theme applied to the Android Window while the process is sta"
},
{
"path": "app/android/app/src/profile/AndroidManifest.xml",
"chars": 378,
"preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <!-- The INTERNET permission is required for d"
},
{
"path": "app/android/build.gradle.kts",
"chars": 785,
"preview": "allprojects {\n repositories {\n google()\n mavenCentral()\n }\n}\n\nval newBuildDir: Directory = rootProje"
},
{
"path": "app/android/gradle/wrapper/gradle-wrapper.properties",
"chars": 203,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dist"
},
{
"path": "app/android/gradle.properties",
"chars": 166,
"preview": "org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError\nandroid"
},
{
"path": "app/android/settings.gradle.kts",
"chars": 739,
"preview": "pluginManagement {\n val flutterSdkPath = run {\n val properties = java.util.Properties()\n file(\"local.pr"
},
{
"path": "app/ios/.gitignore",
"chars": 569,
"preview": "**/dgph\n*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/De"
},
{
"path": "app/ios/Flutter/AppFrameworkInfo.plist",
"chars": 774,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "app/ios/Flutter/Debug.xcconfig",
"chars": 107,
"preview": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"Generated.xcconfig\"\n"
},
{
"path": "app/ios/Flutter/Release.xcconfig",
"chars": 109,
"preview": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"Generated.xcconfig\"\n"
},
{
"path": "app/ios/Podfile",
"chars": 1391,
"preview": "# Uncomment this line to define a global platform for your project\n# platform :ios, '12.0'\n\n# CocoaPods analytics sends "
},
{
"path": "app/ios/Runner/AppDelegate.swift",
"chars": 391,
"preview": "import Flutter\nimport UIKit\n\n@main\n@objc class AppDelegate: FlutterAppDelegate {\n override func application(\n _ appl"
},
{
"path": "app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 2097,
"preview": "{\"images\":[{\"size\":\"20x20\",\"idiom\":\"iphone\",\"filename\":\"Icon-App-20x20@2x.png\",\"scale\":\"2x\"},{\"size\":\"20x20\",\"idiom\":\"ip"
},
{
"path": "app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
"chars": 391,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"universal\",\n \"filename\" : \"LaunchImage.png\",\n \"scale\" : \"1x\"\n },\n "
},
{
"path": "app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
"chars": 336,
"preview": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in"
},
{
"path": "app/ios/Runner/Base.lproj/LaunchScreen.storyboard",
"chars": 2377,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "app/ios/Runner/Base.lproj/Main.storyboard",
"chars": 1605,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "app/ios/Runner/Info.plist",
"chars": 1643,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "app/ios/Runner/Runner-Bridging-Header.h",
"chars": 38,
"preview": "#import \"GeneratedPluginRegistrant.h\"\n"
},
{
"path": "app/ios/Runner.xcodeproj/project.pbxproj",
"chars": 31075,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
"chars": 226,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
"chars": 3683,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1510\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "app/ios/Runner.xcworkspace/contents.xcworkspacedata",
"chars": 224,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"group:Runner.xcodepr"
},
{
"path": "app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
"chars": 226,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "app/ios/RunnerTests/RunnerTests.swift",
"chars": 285,
"preview": "import Flutter\nimport UIKit\nimport XCTest\n\nclass RunnerTests: XCTestCase {\n\n func testExample() {\n // If you add cod"
},
{
"path": "app/lib/components/action_button.dart",
"chars": 4423,
"preview": "import 'package:flutter/material.dart';\nimport 'package:get/get.dart';\nimport 'package:tailwind_components/tailwind_comp"
},
{
"path": "app/lib/components/action_panel.dart",
"chars": 9478,
"preview": "import 'package:flutter/material.dart';\nimport 'package:get/get.dart';\nimport 'package:spacepad/components/action_button"
},
{
"path": "app/lib/components/admin_actions.dart",
"chars": 2053,
"preview": "import 'package:flutter/material.dart';\nimport 'package:get/get.dart';\n\nclass AdminActions extends StatelessWidget {\n f"
},
{
"path": "app/lib/components/authenticated_background.dart",
"chars": 3164,
"preview": "import 'dart:async';\nimport 'package:flutter/material.dart';\nimport 'package:http/http.dart' as http;\nimport 'package:sp"
},
{
"path": "app/lib/components/authenticated_image.dart",
"chars": 3269,
"preview": "import 'dart:async';\nimport 'package:flutter/material.dart';\nimport 'package:http/http.dart' as http;\nimport 'package:sp"
},
{
"path": "app/lib/components/calendar_modal.dart",
"chars": 7493,
"preview": "import 'package:flutter/material.dart';\nimport 'package:spacepad/models/event_model.dart';\nimport 'package:spacepad/them"
},
{
"path": "app/lib/components/custom_booking_modal.dart",
"chars": 17085,
"preview": "import 'package:flutter/material.dart';\nimport 'package:get/get.dart';\nimport 'package:spacepad/components/frosted_panel"
},
{
"path": "app/lib/components/event_line.dart",
"chars": 1618,
"preview": "import 'package:get/get.dart';\nimport 'package:spacepad/models/event_model.dart';\nimport 'package:flutter/material.dart'"
},
{
"path": "app/lib/components/frosted_panel.dart",
"chars": 1872,
"preview": "import 'dart:ui';\nimport 'package:flutter/material.dart';\nimport 'package:tailwind_components/tailwind_components.dart';"
},
{
"path": "app/lib/components/solid_button.dart",
"chars": 1229,
"preview": "import 'package:flutter/material.dart';\nimport 'package:get/get.dart';\nimport 'package:tailwind_components/tailwind_comp"
},
{
"path": "app/lib/components/spinner.dart",
"chars": 561,
"preview": "import 'package:flutter/material.dart';\nimport 'package:spacepad/theme.dart';\n\nclass Spinner extends StatelessWidget {\n "
},
{
"path": "app/lib/components/toast.dart",
"chars": 2277,
"preview": "import 'package:flutter/material.dart';\nimport 'package:get/get.dart';\nimport 'package:heroicons/heroicons.dart';\n\nclass"
},
{
"path": "app/lib/controllers/dashboard_controller.dart",
"chars": 16422,
"preview": "import 'dart:async';\n\nimport 'package:get/get.dart';\nimport 'package:spacepad/components/toast.dart';\nimport 'package:sp"
},
{
"path": "app/lib/controllers/display_controller.dart",
"chars": 1450,
"preview": "import 'package:get/get.dart';\nimport 'package:spacepad/models/display_model.dart';\nimport 'package:spacepad/components/"
},
{
"path": "app/lib/controllers/login_controller.dart",
"chars": 2906,
"preview": "import 'dart:io';\nimport 'dart:core';\n\nimport 'package:device_info_plus/device_info_plus.dart';\nimport 'package:flutter_"
},
{
"path": "app/lib/date_format_helper.dart",
"chars": 332,
"preview": "import 'package:flutter/material.dart';\nimport 'package:intl/intl.dart';\n\nString formatTime(BuildContext context, DateTi"
},
{
"path": "app/lib/exceptions/api_exception.dart",
"chars": 704,
"preview": "import 'dart:convert';\nimport 'package:http/http.dart';\n\nclass ApiException implements Exception {\n final int code;\n f"
},
{
"path": "app/lib/main.dart",
"chars": 3675,
"preview": "import 'dart:async';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flu"
},
{
"path": "app/lib/models/device_model.dart",
"chars": 598,
"preview": "import 'package:spacepad/models/user_model.dart';\nimport 'package:spacepad/models/display_model.dart';\n\nclass DeviceMode"
},
{
"path": "app/lib/models/display_data_model.dart",
"chars": 837,
"preview": "import 'display_model.dart';\nimport 'event_model.dart';\n\nclass DisplayDataModel {\n DisplayModel? display;\n List<EventM"
},
{
"path": "app/lib/models/display_model.dart",
"chars": 552,
"preview": "import 'display_settings_model.dart';\n\nclass DisplayModel {\n String id;\n String name;\n DisplaySettingsModel settings;"
},
{
"path": "app/lib/models/display_settings_model.dart",
"chars": 2702,
"preview": "class DisplaySettingsModel {\n bool checkInEnabled;\n bool bookingEnabled;\n bool hasCustomBooking;\n int checkInGracePe"
},
{
"path": "app/lib/models/event_model.dart",
"chars": 1599,
"preview": "import 'event_status.dart';\n\nclass EventModel {\n String id;\n EventStatus status;\n String summary;\n String? location;"
},
{
"path": "app/lib/models/event_status.dart",
"chars": 580,
"preview": "enum EventStatus { confirmed, tentative, cancelled }\n\nEventStatus eventStatusFromString(String? value) {\n switch (value"
},
{
"path": "app/lib/models/user_model.dart",
"chars": 422,
"preview": "class UserModel {\n String id;\n String name;\n String email;\n\n UserModel({\n required this.id,\n required this.nam"
},
{
"path": "app/lib/pages/dashboard_page.dart",
"chars": 17285,
"preview": "import 'package:flutter/foundation.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/cupertino.dart"
},
{
"path": "app/lib/pages/display_page.dart",
"chars": 6886,
"preview": "import 'package:dropdown_button2/dropdown_button2.dart';\nimport 'package:flutter/material.dart';\nimport 'package:get/get"
},
{
"path": "app/lib/pages/login_page.dart",
"chars": 9311,
"preview": "import 'package:flutter/material.dart';\nimport 'package:get/get.dart';\nimport 'package:spacepad/components/spinner.dart'"
},
{
"path": "app/lib/pages/splash_page.dart",
"chars": 673,
"preview": "import 'package:flutter/material.dart';\nimport 'package:spacepad/components/spinner.dart';\nimport 'package:spacepad/serv"
},
{
"path": "app/lib/services/api_service.dart",
"chars": 3839,
"preview": "import 'dart:convert';\nimport 'package:flutter/foundation.dart';\nimport 'package:flutter_dotenv/flutter_dotenv.dart';\nim"
},
{
"path": "app/lib/services/auth_service.dart",
"chars": 2522,
"preview": "import 'package:flutter/foundation.dart';\nimport 'package:get/get.dart';\nimport 'package:shared_preferences/shared_prefe"
},
{
"path": "app/lib/services/device_service.dart",
"chars": 303,
"preview": "import 'package:spacepad/services/api_service.dart';\n\nclass DeviceService {\n DeviceService._();\n static final DeviceSe"
},
{
"path": "app/lib/services/display_service.dart",
"chars": 2231,
"preview": "import 'package:spacepad/models/display_model.dart';\nimport 'package:spacepad/models/display_data_model.dart';\nimport 'p"
},
{
"path": "app/lib/services/font_service.dart",
"chars": 4861,
"preview": "import 'package:google_fonts/google_fonts.dart';\nimport 'package:flutter/material.dart';\n\nclass FontService {\n FontServ"
},
{
"path": "app/lib/services/server_service.dart",
"chars": 697,
"preview": "import 'package:http/http.dart' as http;\n\nclass ServerService {\n static final ServerService _instance = ServerService._"
},
{
"path": "app/lib/theme.dart",
"chars": 4169,
"preview": "import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:google_fonts/google_font"
},
{
"path": "app/lib/translations/translations.dart",
"chars": 23359,
"preview": "import 'package:get/get.dart';\n\nclass AppTranslations extends Translations {\n @override\n Map<String, Map<String, Strin"
},
{
"path": "app/pubspec.yaml",
"chars": 3435,
"preview": "name: spacepad\ndescription: \"A simple and privacy-focused meeting room display.\"\n# The following line prevents the packa"
},
{
"path": "app/test/widget_test.dart",
"chars": 1057,
"preview": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester"
},
{
"path": "backend/.editorconfig",
"chars": 258,
"preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_"
},
{
"path": "backend/.gitattributes",
"chars": 186,
"preview": "* text=auto eol=lf\n\n*.blade.php diff=html\n*.css diff=css\n*.html diff=html\n*.md diff=markdown\n*.php diff=php\n\n/.github ex"
},
{
"path": "backend/.gitignore",
"chars": 285,
"preview": "/.phpunit.cache\n/node_modules\n/public/build\n/public/hot\n/public/storage\n/storage/*.key\n/storage/pail\n/vendor\n.env\n.env.b"
},
{
"path": "backend/Dockerfile",
"chars": 1231,
"preview": "FROM composer:lts AS php_builder\n\nWORKDIR /app\n\nCOPY composer.json composer.lock ./\n\nCOPY ./ /app\n\nRUN composer install "
},
{
"path": "backend/FARO_SETUP.md",
"chars": 5676,
"preview": "# Grafana Faro Frontend Monitoring Setup\n\nThis document explains how to configure Grafana Faro Real User Monitoring (RUM"
},
{
"path": "backend/README.md",
"chars": 4109,
"preview": "<p align=\"center\"><a href=\"https://laravel.com\" target=\"_blank\"><img src=\"https://raw.githubusercontent.com/laravel/art/"
},
{
"path": "backend/WORKSPACE_SETUP.md",
"chars": 3270,
"preview": "# Workspace System Documentation\n\n## Overview\n\nThe workspace system allows multiple users to collaborate on managing dis"
},
{
"path": "backend/app/Console/Commands/CheckMarketingTriggers.php",
"chars": 7408,
"preview": "<?php\n\nnamespace App\\Console\\Commands;\n\nuse App\\Events\\TrialExpiredOrCancelled;\nuse App\\Events\\UserActivatedAfter24h;\nus"
},
{
"path": "backend/app/Console/Commands/CleanupExpiredEvents.php",
"chars": 2401,
"preview": "<?php\n\nnamespace App\\Console\\Commands;\n\nuse App\\Models\\Display;\nuse App\\Models\\Event;\nuse Illuminate\\Console\\Command;\n\nc"
},
{
"path": "backend/app/Console/Commands/RenewEventSubscriptions.php",
"chars": 6856,
"preview": "<?php\n\nnamespace App\\Console\\Commands;\n\nuse App\\Enums\\DisplayStatus;\nuse App\\Models\\Display;\nuse App\\Models\\EventSubscri"
},
{
"path": "backend/app/Console/Commands/SendHeartbeat.php",
"chars": 899,
"preview": "<?php\n\nnamespace App\\Console\\Commands;\n\nuse App\\Services\\InstanceService;\nuse Illuminate\\Console\\Command;\nuse Illuminate"
},
{
"path": "backend/app/Console/Commands/TriggerRegistrationWebhookForMissingNames.php",
"chars": 1320,
"preview": "<?php\n\nnamespace App\\Console\\Commands;\n\nuse App\\Events\\UserRegistered;\nuse App\\Models\\User;\nuse Illuminate\\Console\\Comma"
},
{
"path": "backend/app/Console/Commands/UpdateLemonSqueezySubscriptions.php",
"chars": 13369,
"preview": "<?php\n\nnamespace App\\Console\\Commands;\n\nuse App\\Enums\\DisplayStatus;\nuse App\\Models\\User;\nuse Illuminate\\Console\\Command"
},
{
"path": "backend/app/Console/Commands/ValidateLicense.php",
"chars": 1056,
"preview": "<?php\n\nnamespace App\\Console\\Commands;\n\nuse App\\Data\\LicenseData;\nuse App\\Services\\InstanceService;\nuse Illuminate\\Conso"
},
{
"path": "backend/app/Data/CalendarWebhookData.php",
"chars": 787,
"preview": "<?php\n\nnamespace App\\Data;\n\nuse Spatie\\LaravelData\\Data;\n\nclass CalendarWebhookData extends Data\n{\n public function _"
},
{
"path": "backend/app/Data/DisplayWebhookData.php",
"chars": 203,
"preview": "<?php\n\nnamespace App\\Data;\n\nuse Spatie\\LaravelData\\Data;\n\nclass DisplayWebhookData extends Data\n{\n public function __"
},
{
"path": "backend/app/Data/InstanceData.php",
"chars": 954,
"preview": "<?php\n\nnamespace App\\Data;\n\nuse Illuminate\\Support\\Carbon;\nuse Spatie\\LaravelData\\Data;\nuse Spatie\\LaravelData\\Attribute"
},
{
"path": "backend/app/Data/LicenseData.php",
"chars": 553,
"preview": "<?php\n\nnamespace App\\Data;\n\nuse App\\Models\\Instance;\nuse Spatie\\LaravelData\\Data;\nuse Carbon\\Carbon;\n\nclass LicenseData "
},
{
"path": "backend/app/Data/OrderWebhookData.php",
"chars": 233,
"preview": "<?php\n\nnamespace App\\Data;\n\nuse Spatie\\LaravelData\\Data;\n\nclass OrderWebhookData extends Data\n{\n public function __co"
},
{
"path": "backend/app/Data/PermissionResult.php",
"chars": 371,
"preview": "<?php\n\nnamespace App\\Data;\n\nclass PermissionResult\n{\n public readonly bool $permitted;\n public readonly ?string $m"
},
{
"path": "backend/app/Data/UserData.php",
"chars": 442,
"preview": "<?php\n\nnamespace App\\Data;\n\nuse Spatie\\LaravelData\\Data;\nuse Spatie\\LaravelData\\Attributes\\MapName;\nuse Carbon\\Carbon;\n\n"
},
{
"path": "backend/app/Data/UserWebhookData.php",
"chars": 1060,
"preview": "<?php\n\nnamespace App\\Data;\n\nuse Illuminate\\Support\\Carbon;\nuse Spatie\\LaravelData\\Data;\n\nclass UserWebhookData extends D"
},
{
"path": "backend/app/Enums/AccountStatus.php",
"chars": 480,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum AccountStatus: string\n{\n case CONNECTED = 'connected';\n case ERROR = 'error';\n\n "
},
{
"path": "backend/app/Enums/DisplayStatus.php",
"chars": 680,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum DisplayStatus: string\n{\n case READY = 'ready';\n case ACTIVE = 'active';\n case"
},
{
"path": "backend/app/Enums/EventSource.php",
"chars": 168,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum EventSource\n{\n const GOOGLE = 'google';\n const OUTLOOK = 'outlook';\n const CA"
},
{
"path": "backend/app/Enums/EventStatus.php",
"chars": 160,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum EventStatus: string\n{\n case CONFIRMED = 'confirmed';\n case TENTATIVE = 'tentativ"
},
{
"path": "backend/app/Enums/GoogleBookingMethod.php",
"chars": 153,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum GoogleBookingMethod: string\n{\n case SERVICE_ACCOUNT = 'service_account';\n case U"
},
{
"path": "backend/app/Enums/OAuthDriver.php",
"chars": 114,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum OAuthDriver\n{\n const MICROSOFT = 'microsoft';\n const GOOGLE = 'google';\n}\n"
},
{
"path": "backend/app/Enums/PermissionType.php",
"chars": 651,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum PermissionType: string\n{\n case READ = 'read';\n case WRITE = 'write';\n\n public"
},
{
"path": "backend/app/Enums/Plan.php",
"chars": 161,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum Plan\n{\n const WAITLIST = 'waitlist';\n const FREE = 'free';\n const FAIR = 'fai"
},
{
"path": "backend/app/Enums/Provider.php",
"chars": 136,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum Provider\n{\n const GOOGLE = 'google';\n const OUTLOOK = 'outlook';\n const CALDA"
},
{
"path": "backend/app/Enums/UsageType.php",
"chars": 306,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum UsageType: string\n{\n case BUSINESS = 'business';\n case PERSONAL = 'personal';\n\n "
},
{
"path": "backend/app/Enums/UserStatus.php",
"chars": 146,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum UserStatus\n{\n const ONBOARDING = 'onboarding';\n const ACTIVE = 'active';\n con"
},
{
"path": "backend/app/Enums/WorkspaceRole.php",
"chars": 519,
"preview": "<?php\n\nnamespace App\\Enums;\n\nenum WorkspaceRole: string\n{\n case OWNER = 'owner';\n case ADMIN = 'admin';\n case M"
},
{
"path": "backend/app/Events/TrialExpiredOrCancelled.php",
"chars": 337,
"preview": "<?php\n\nnamespace App\\Events;\n\nuse App\\Models\\User;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\Seri"
},
{
"path": "backend/app/Events/UserActivatedAfter24h.php",
"chars": 335,
"preview": "<?php\n\nnamespace App\\Events;\n\nuse App\\Models\\User;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\Seri"
},
{
"path": "backend/app/Events/UserInactive.php",
"chars": 326,
"preview": "<?php\n\nnamespace App\\Events;\n\nuse App\\Models\\User;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\Seri"
},
{
"path": "backend/app/Events/UserNotActivatedAfter24h.php",
"chars": 338,
"preview": "<?php\n\nnamespace App\\Events;\n\nuse App\\Models\\User;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\Seri"
},
{
"path": "backend/app/Events/UserOnboarded.php",
"chars": 317,
"preview": "<?php\n\nnamespace App\\Events;\n\nuse App\\Models\\Display;\nuse App\\Models\\User;\nuse Illuminate\\Queue\\SerializesModels;\n\nclass"
},
{
"path": "backend/app/Events/UserPassive.php",
"chars": 325,
"preview": "<?php\n\nnamespace App\\Events;\n\nuse App\\Models\\User;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\Seri"
},
{
"path": "backend/app/Events/UserRegistered.php",
"chars": 327,
"preview": "<?php\n\nnamespace App\\Events;\n\nuse App\\Models\\User;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\Seri"
},
{
"path": "backend/app/Exceptions/Handler.php",
"chars": 3241,
"preview": "<?php\n\nnamespace App\\Exceptions;\n\nuse Illuminate\\Foundation\\Exceptions\\Handler as ExceptionHandler;\nuse Illuminate\\Http\\"
},
{
"path": "backend/app/Helpers/DisplaySettings.php",
"chars": 8527,
"preview": "<?php\n\nnamespace App\\Helpers;\n\nuse App\\Models\\Display;\nuse App\\Models\\DisplaySetting;\n\nclass DisplaySettings\n{\n publi"
},
{
"path": "backend/app/Helpers/Settings.php",
"chars": 1159,
"preview": "<?php\n\nnamespace App\\Helpers;\n\nuse App\\Models\\Setting;\n\nclass Settings\n{\n public static function getSetting(string $k"
},
{
"path": "backend/app/Http/Controllers/API/ApiController.php",
"chars": 1280,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\API;\n\nuse App\\Http\\Controllers\\Controller;\nuse Illuminate\\Http\\JsonResponse;\n\nclas"
},
{
"path": "backend/app/Http/Controllers/API/Auth/AuthController.php",
"chars": 3754,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\API\\Auth;\n\nuse App\\Http\\Controllers\\API\\ApiController;\nuse App\\Http\\Requests\\API\\A"
},
{
"path": "backend/app/Http/Controllers/API/Cloud/InstanceController.php",
"chars": 6151,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\API\\Cloud;\n\nuse App\\Data\\LicenseData;\nuse App\\Http\\Controllers\\API\\ApiController;\n"
},
{
"path": "backend/app/Http/Controllers/API/DeviceController.php",
"chars": 2260,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\API;\n\nuse App\\Enums\\DisplayStatus;\nuse App\\Http\\Requests\\API\\ChangeDisplayRequest;"
},
{
"path": "backend/app/Http/Controllers/API/DisplayController.php",
"chars": 11129,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\API;\n\nuse App\\Enums\\DisplayStatus;\nuse App\\Http\\Requests\\API\\BookEventRequest;\nuse"
},
{
"path": "backend/app/Http/Controllers/API/EventController.php",
"chars": 1117,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\API;\n\nuse App\\Http\\Resources\\API\\EventResource;\nuse App\\Models\\Device;\nuse App\\Ser"
},
{
"path": "backend/app/Http/Controllers/AdminController.php",
"chars": 25993,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Enums\\DisplayStatus;\nuse App\\Models\\Board;\nuse App\\Models\\Display;\nuse A"
},
{
"path": "backend/app/Http/Controllers/Auth/AuthController.php",
"chars": 758,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Http\\Controllers\\Controller;\nuse App\\Models\\User;\nuse Illuminate\\Su"
},
{
"path": "backend/app/Http/Controllers/Auth/GoogleController.php",
"chars": 629,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Enums\\OAuthDriver;\nuse App\\Http\\Requests\\Auth\\OAuth2TokenRequest;\nu"
},
{
"path": "backend/app/Http/Controllers/Auth/LoginController.php",
"chars": 2094,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Events\\UserRegistered;\nuse App\\Http\\Controllers\\Controller;\nuse App"
},
{
"path": "backend/app/Http/Controllers/Auth/MicrosoftController.php",
"chars": 635,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Enums\\OAuthDriver;\nuse App\\Http\\Requests\\Auth\\OAuth2TokenRequest;\nu"
},
{
"path": "backend/app/Http/Controllers/Auth/RegisterController.php",
"chars": 1993,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Events\\UserRegistered;\nuse App\\Http\\Controllers\\Controller;\nuse App"
},
{
"path": "backend/app/Http/Controllers/Auth/SocialAuthController.php",
"chars": 5340,
"preview": "<?php\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Events\\UserRegistered;\nuse App\\Http\\Requests\\Auth\\OAuth2TokenReques"
},
{
"path": "backend/app/Http/Controllers/BoardController.php",
"chars": 19421,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Enums\\DisplayStatus;\nuse App\\Enums\\EventStatus;\nuse App\\Helpers\\DisplayS"
},
{
"path": "backend/app/Http/Controllers/CalDAVAccountsController.php",
"chars": 2208,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\CalDAVAccount;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illumina"
},
{
"path": "backend/app/Http/Controllers/CalendarController.php",
"chars": 4539,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Services\\GoogleService;\nuse App\\Services\\OutlookService;\nuse App\\Service"
},
{
"path": "backend/app/Http/Controllers/Controller.php",
"chars": 299,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Foundation\\Auth\\Access\\AuthorizesRequests;\nuse Illuminate\\Foundat"
},
{
"path": "backend/app/Http/Controllers/DashboardController.php",
"chars": 4089,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Services\\OutlookService;\nuse Illuminate\\Contracts\\Foundation\\Application"
},
{
"path": "backend/app/Http/Controllers/DisplayController.php",
"chars": 8377,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Enums\\Provider;\nuse App\\Enums\\DisplayStatus;\nuse App\\Events\\UserOnboarde"
},
{
"path": "backend/app/Http/Controllers/DisplaySettingsController.php",
"chars": 9884,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Helpers\\DisplaySettings;\nuse App\\Models\\Display;\nuse App\\Services\\ImageS"
},
{
"path": "backend/app/Http/Controllers/GoogleAccountsController.php",
"chars": 8234,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Enums\\PermissionType;\nuse App\\Enums\\GoogleBookingMethod;\nuse App\\Models\\"
},
{
"path": "backend/app/Http/Controllers/GoogleWebhookController.php",
"chars": 2085,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Suppor"
},
{
"path": "backend/app/Http/Controllers/LicenseController.php",
"chars": 2178,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Data\\LicenseData;\nuse App\\Http\\Requests\\ActivateLicenseRequest;\nuse App\\"
},
{
"path": "backend/app/Http/Controllers/OnboardingController.php",
"chars": 1930,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Enums\\Plan;\nuse App\\Enums\\DisplayStatus;\nuse App\\Events\\UserRegistered;\n"
},
{
"path": "backend/app/Http/Controllers/OutlookAccountsController.php",
"chars": 2234,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Enums\\PermissionType;\nuse App\\Models\\OutlookAccount;\nuse App\\Services\\Ou"
},
{
"path": "backend/app/Http/Controllers/OutlookWebhookController.php",
"chars": 2966,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse Illuminate\\Suppor"
},
{
"path": "backend/app/Http/Controllers/RoomController.php",
"chars": 3793,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Enums\\Provider;\nuse App\\Services\\OutlookService;\nuse App\\Services\\Google"
},
{
"path": "backend/app/Http/Controllers/UsageController.php",
"chars": 740,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse Illuminate\\Contracts\\View\\Factory;\nuse Illuminate\\Contracts\\View\\View;\nuse I"
},
{
"path": "backend/app/Http/Controllers/WorkspaceController.php",
"chars": 1762,
"preview": "<?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\Workspace;\nuse Illuminate\\Http\\RedirectResponse;\nuse Illuminate\\H"
},
{
"path": "backend/app/Http/Middleware/CheckUserActive.php",
"chars": 603,
"preview": "<?php\n\nnamespace App\\Http\\Middleware;\n\nuse App\\Enums\\UserStatus;\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminat"
},
{
"path": "backend/app/Http/Middleware/CheckUserOnboarding.php",
"chars": 604,
"preview": "<?php\n\nnamespace App\\Http\\Middleware;\n\nuse App\\Enums\\UserStatus;\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminat"
},
{
"path": "backend/app/Http/Middleware/UpdateLastActivity.php",
"chars": 638,
"preview": "<?php\n\nnamespace App\\Http\\Middleware;\n\nuse Closure;\nuse App\\Models\\Device;\nuse App\\Models\\User;\nuse Illuminate\\Http\\Requ"
},
{
"path": "backend/app/Http/Requests/API/Auth/LoginRequest.php",
"chars": 1870,
"preview": "<?php\n\nnamespace App\\Http\\Requests\\API\\Auth;\n\nuse Illuminate\\Auth\\Events\\Lockout;\nuse Illuminate\\Foundation\\Http\\FormReq"
},
{
"path": "backend/app/Http/Requests/API/BookEventRequest.php",
"chars": 517,
"preview": "<?php\n\nnamespace App\\Http\\Requests\\API;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\n\nclass BookEventRequest extends For"
},
{
"path": "backend/app/Http/Requests/API/ChangeDisplayRequest.php",
"chars": 753,
"preview": "<?php\n\nnamespace App\\Http\\Requests\\API;\n\nuse Illuminate\\Auth\\Events\\Lockout;\nuse Illuminate\\Foundation\\Http\\FormRequest;"
},
{
"path": "backend/app/Http/Requests/API/InstanceHeartbeatRequest.php",
"chars": 2580,
"preview": "<?php\n\nnamespace App\\Http\\Requests\\API;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\n\nclass InstanceHeartbeatRequest ext"
},
{
"path": "backend/app/Http/Requests/API/ValidateInstanceRequest.php",
"chars": 418,
"preview": "<?php\n\nnamespace App\\Http\\Requests\\API;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse App\\Enums\\Provider;\n\nclass Vali"
},
{
"path": "backend/app/Http/Requests/ActivateLicenseRequest.php",
"chars": 359,
"preview": "<?php\n\nnamespace App\\Http\\Requests;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse App\\Enums\\Provider;\n\nclass Activate"
},
{
"path": "backend/app/Http/Requests/Auth/LoginRequest.php",
"chars": 2234,
"preview": "<?php\n\nnamespace App\\Http\\Requests\\Auth;\n\nuse Illuminate\\Auth\\Events\\Lockout;\nuse Illuminate\\Foundation\\Http\\FormRequest"
},
{
"path": "backend/app/Http/Requests/Auth/OAuth2TokenRequest.php",
"chars": 851,
"preview": "<?php\n\nnamespace App\\Http\\Requests\\Auth;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\n\nclass OAuth2TokenRequest extends "
},
{
"path": "backend/app/Http/Requests/Auth/RegisterRequest.php",
"chars": 891,
"preview": "<?php\n\nnamespace App\\Http\\Requests\\Auth;\n\nuse Illuminate\\Auth\\Events\\Lockout;\nuse Illuminate\\Foundation\\Http\\FormRequest"
},
{
"path": "backend/app/Http/Requests/CreateBoardRequest.php",
"chars": 3870,
"preview": "<?php\n\nnamespace App\\Http\\Requests;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\n\nclass "
},
{
"path": "backend/app/Http/Requests/CreateDisplayRequest.php",
"chars": 1083,
"preview": "<?php\n\nnamespace App\\Http\\Requests;\n\nuse Illuminate\\Auth\\Events\\Lockout;\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse"
},
{
"path": "backend/app/Http/Requests/UpdateBoardRequest.php",
"chars": 4152,
"preview": "<?php\n\nnamespace App\\Http\\Requests;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\nuse Illuminate\\Validation\\Rule;\n\nclass "
},
{
"path": "backend/app/Http/Requests/UpdateDisplayCustomizationRequest.php",
"chars": 1111,
"preview": "<?php\n\nnamespace App\\Http\\Requests;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\n\nclass UpdateDisplayCustomizationReques"
},
{
"path": "backend/app/Http/Resources/API/DeviceResource.php",
"chars": 537,
"preview": "<?php\n\nnamespace App\\Http\\Resources\\API;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Resources\\Json\\JsonResource;\n"
},
{
"path": "backend/app/Http/Resources/API/DisplayDataResource.php",
"chars": 492,
"preview": "<?php\n\nnamespace App\\Http\\Resources\\API;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Resources\\Json\\JsonResource;\n"
},
{
"path": "backend/app/Http/Resources/API/DisplayResource.php",
"chars": 492,
"preview": "<?php\n\nnamespace App\\Http\\Resources\\API;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Resources\\Json\\JsonResource;\n"
},
{
"path": "backend/app/Http/Resources/API/DisplaySettingsResource.php",
"chars": 1440,
"preview": "<?php\n\nnamespace App\\Http\\Resources\\API;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Resources\\Json\\JsonResource;\n"
},
{
"path": "backend/app/Http/Resources/API/EventResource.php",
"chars": 1287,
"preview": "<?php\n\nnamespace App\\Http\\Resources\\API;\n\nuse App\\Models\\Event;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Resourc"
},
{
"path": "backend/app/Http/Resources/API/UserResource.php",
"chars": 454,
"preview": "<?php\n\nnamespace App\\Http\\Resources\\API;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Resources\\Json\\JsonResource;\n"
},
{
"path": "backend/app/Infrastructure/Cloud/LicenseService.php",
"chars": 2161,
"preview": "<?php\n\nnamespace App\\Infrastructure\\Cloud;\n\nuse Exception;\nuse Illuminate\\Http\\Client\\Response;\nuse Illuminate\\Support\\F"
},
{
"path": "backend/app/Listeners/ActivateUser.php",
"chars": 285,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Enums\\UserStatus;\nuse App\\Events\\UserOnboarded;\n\nclass ActivateUser\n{\n /**\n "
},
{
"path": "backend/app/Listeners/SendOnboardingCompleteNotification.php",
"chars": 783,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Data\\CalendarWebhookData;\nuse App\\Data\\DisplayWebhookData;\nuse App\\Data\\UserWeb"
},
{
"path": "backend/app/Listeners/SendOrderCreatedNotification.php",
"chars": 662,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Data\\OrderWebhookData;\nuse App\\Data\\UserWebhookData;\nuse Illuminate\\Support\\Fac"
},
{
"path": "backend/app/Listeners/SendRegistrationNotification.php",
"chars": 550,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Data\\UserWebhookData;\nuse App\\Events\\UserRegistered;\nuse Illuminate\\Support\\Fac"
},
{
"path": "backend/app/Listeners/SendTrialExpiredOrCancelledNotification.php",
"chars": 608,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Data\\UserWebhookData;\nuse App\\Events\\TrialExpiredOrCancelled;\nuse Illuminate\\Su"
},
{
"path": "backend/app/Listeners/SendUserActivatedAfter24hNotification.php",
"chars": 598,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Data\\UserWebhookData;\nuse App\\Events\\UserActivatedAfter24h;\nuse Illuminate\\Supp"
},
{
"path": "backend/app/Listeners/SendUserInactiveNotification.php",
"chars": 549,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Data\\UserWebhookData;\nuse App\\Events\\UserInactive;\nuse Illuminate\\Support\\Facad"
},
{
"path": "backend/app/Listeners/SendUserNotActivatedAfter24hNotification.php",
"chars": 615,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Data\\UserWebhookData;\nuse App\\Events\\UserNotActivatedAfter24h;\nuse Illuminate\\S"
},
{
"path": "backend/app/Listeners/SendUserPassiveNotification.php",
"chars": 544,
"preview": "<?php\n\nnamespace App\\Listeners;\n\nuse App\\Data\\UserWebhookData;\nuse App\\Events\\UserPassive;\nuse Illuminate\\Support\\Facade"
},
{
"path": "backend/app/Models/Board.php",
"chars": 3220,
"preview": "<?php\n\nnamespace App\\Models;\n\nuse App\\Traits\\HasUlid;\nuse App\\Enums\\DisplayStatus;\nuse Illuminate\\Database\\Eloquent\\Fact"
},
{
"path": "backend/app/Models/CalDAVAccount.php",
"chars": 1238,
"preview": "<?php\n\nnamespace App\\Models;\n\nuse App\\Enums\\AccountStatus;\nuse App\\Enums\\PermissionType;\nuse App\\Traits\\HasUlid;\nuse Ill"
},
{
"path": "backend/app/Models/Calendar.php",
"chars": 1453,
"preview": "<?php\n\nnamespace App\\Models;\n\nuse App\\Enums\\Provider;\nuse App\\Traits\\HasUlid;\nuse Illuminate\\Database\\Eloquent\\Factories"
},
{
"path": "backend/app/Models/Device.php",
"chars": 1132,
"preview": "<?php\n\nnamespace App\\Models;\n\nuse App\\Traits\\HasUlid;\nuse App\\Traits\\HasLastActivity;\nuse Illuminate\\Database\\Eloquent\\F"
},
{
"path": "backend/app/Models/Display.php",
"chars": 5523,
"preview": "<?php\n\nnamespace App\\Models;\n\nuse App\\Traits\\HasUlid;\nuse Illuminate\\Support\\Carbon;\nuse Illuminate\\Database\\Eloquent\\Fa"
},
{
"path": "backend/app/Models/DisplaySetting.php",
"chars": 1361,
"preview": "<?php\n\nnamespace App\\Models;\n\nuse App\\Traits\\HasUlid;\nuse Illuminate\\Database\\Eloquent\\Model;\nuse Illuminate\\Database\\El"
},
{
"path": "backend/app/Models/Event.php",
"chars": 3058,
"preview": "<?php\n\nnamespace App\\Models;\n\nuse App\\Enums\\EventSource;\nuse App\\Traits\\HasUlid;\nuse Illuminate\\Database\\Eloquent\\Factor"
}
]
// ... and 261 more files (download for full content)
About this extraction
This page contains the full source code of the magweter/spacepad GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 461 files (1.2 MB), approximately 301.2k tokens, and a symbol index with 973 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.