Full Code of magweter/spacepad for AI

main 215510b0ea9c cached
461 files
1.2 MB
301.2k tokens
973 symbols
1 requests
Download .txt
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>

![Product Overview](art/overview.png)

## 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
Download .txt
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
Download .txt
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.

Copied to clipboard!