From bba6df52c03a6c512d14480ff92655c78cb2d48a Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Tue, 26 May 2026 17:45:37 +0200 Subject: [PATCH] Initial commit: monorepo Match Live TV. Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy. Co-authored-by: Cursor --- .gitignore | 33 + README.md | 70 ++ backend/.dockerignore | 42 + backend/.gitattributes | 9 + backend/.github/dependabot.yml | 12 + backend/.github/workflows/ci.yml | 39 + backend/.gitignore | 33 + backend/.rubocop.yml | 8 + backend/.ruby-version | 1 + backend/Dockerfile | 19 + backend/Gemfile | 33 + backend/Gemfile.lock | 443 +++++++++++ backend/README.md | 24 + backend/Rakefile | 6 + .../app/channels/application_cable/channel.rb | 4 + .../channels/application_cable/connection.rb | 17 + .../channels/concerns/cable_broadcastable.rb | 9 + backend/app/channels/session_channel.rb | 83 ++ .../app/controllers/admin/base_controller.rb | 6 + .../controllers/admin/dashboard_controller.rb | 8 + .../controllers/admin/sessions_controller.rb | 12 + .../app/controllers/admin/teams_controller.rb | 12 + .../app/controllers/api/v1/auth_controller.rb | 65 ++ .../app/controllers/api/v1/base_controller.rb | 17 + .../controllers/api/v1/matches_controller.rb | 85 ++ .../api/v1/stream_sessions_controller.rb | 146 ++++ .../controllers/api/v1/teams_controller.rb | 122 +++ .../controllers/api/v1/youtube_controller.rb | 26 + .../app/controllers/application_controller.rb | 25 + backend/app/controllers/concerns/.keep | 0 .../app/controllers/hls_proxy_controller.rb | 44 ++ .../public/invitations_controller.rb | 28 + .../app/controllers/public/live_controller.rb | 47 ++ .../controllers/public/pages_controller.rb | 19 + .../public/registrations_controller.rb | 33 + .../controllers/public/replay_controller.rb | 15 + .../controllers/public/sessions_controller.rb | 24 + .../public/site_base_controller.rb | 19 + .../controllers/public/teams_controller.rb | 119 +++ .../controllers/public/web_base_controller.rb | 18 + .../webhooks/mediamtx_controller.rb | 53 ++ .../controllers/webhooks/stripe_controller.rb | 32 + backend/app/helpers/public/live_helper.rb | 35 + backend/app/jobs/application_job.rb | 7 + .../app/jobs/cleanup_expired_sessions_job.rb | 12 + backend/app/jobs/disconnection_timeout_job.rb | 22 + backend/app/jobs/notify_stream_health_job.rb | 17 + backend/app/jobs/stream_analytics_job.rb | 16 + backend/app/mailers/application_mailer.rb | 4 + backend/app/models/application_record.rb | 3 + backend/app/models/concerns/.keep | 0 backend/app/models/device_state.rb | 19 + backend/app/models/match.rb | 7 + backend/app/models/plan.rb | 86 +++ backend/app/models/recording.rb | 21 + backend/app/models/score_state.rb | 17 + backend/app/models/stream_event.rb | 13 + backend/app/models/stream_session.rb | 150 ++++ backend/app/models/subscription.rb | 28 + backend/app/models/team.rb | 19 + backend/app/models/team_invitation.rb | 28 + backend/app/models/user.rb | 17 + backend/app/models/user_team.rb | 10 + backend/app/models/youtube_credential.rb | 22 + backend/app/services/billing/assign_plan.rb | 12 + .../billing/stripe/checkout_session.rb | 79 ++ .../billing/stripe/webhook_handler.rb | 131 ++++ backend/app/services/mediamtx/client.rb | 101 +++ .../app/services/recordings/index_session.rb | 25 + backend/app/services/sessions/create.rb | 61 ++ backend/app/services/sessions/pause.rb | 20 + backend/app/services/sessions/start.rb | 21 + backend/app/services/sessions/stop.rb | 30 + .../app/services/teams/entitlement_error.rb | 11 + backend/app/services/teams/entitlements.rb | 239 ++++++ .../app/services/webhooks/mediamtx_handler.rb | 94 +++ backend/app/services/webhooks/signature.rb | 14 + .../app/services/youtube/broadcast_service.rb | 89 +++ .../app/services/youtube/oauth_exchange.rb | 25 + backend/app/services/youtube/oauth_refresh.rb | 31 + backend/app/services/youtube/oauth_url.rb | 22 + .../app/views/admin/dashboard/index.html.erb | 25 + .../app/views/admin/sessions/index.html.erb | 14 + .../app/views/admin/sessions/show.html.erb | 21 + backend/app/views/admin/teams/index.html.erb | 13 + backend/app/views/admin/teams/show.html.erb | 10 + backend/app/views/layouts/admin.html.erb | 32 + backend/app/views/layouts/mailer.html.erb | 13 + backend/app/views/layouts/mailer.text.erb | 1 + backend/app/views/layouts/marketing.html.erb | 20 + .../app/views/layouts/marketing_live.html.erb | 20 + backend/app/views/layouts/public.html.erb | 56 ++ .../views/public/invitations/show.html.erb | 12 + backend/app/views/public/live/index.html.erb | 76 ++ backend/app/views/public/live/show.html.erb | 227 ++++++ .../app/views/public/pages/features.html.erb | 63 ++ backend/app/views/public/pages/home.html.erb | 88 +++ .../app/views/public/pages/pricing.html.erb | 72 ++ .../app/views/public/pages/privacy.html.erb | 9 + backend/app/views/public/pages/terms.html.erb | 9 + .../views/public/registrations/new.html.erb | 20 + backend/app/views/public/replay/show.html.erb | 37 + .../app/views/public/sessions/new.html.erb | 15 + .../app/views/public/teams/billing.html.erb | 49 ++ .../app/views/public/teams/dashboard.html.erb | 82 ++ .../app/views/public/teams/invite.html.erb | 28 + backend/app/views/public/teams/new.html.erb | 21 + .../views/shared/_marketing_footer.html.erb | 13 + .../app/views/shared/_marketing_nav.html.erb | 76 ++ backend/app/views/shared/_meta_tags.html.erb | 7 + backend/bin/brakeman | 7 + backend/bin/bundle | 109 +++ backend/bin/docker-entrypoint | 13 + backend/bin/rails | 4 + backend/bin/rake | 4 + backend/bin/rubocop | 8 + backend/bin/setup | 37 + backend/config.ru | 6 + backend/config/application.rb | 30 + backend/config/boot.rb | 4 + backend/config/cable.yml | 10 + backend/config/credentials.yml.enc | 1 + backend/config/database.yml | 83 ++ backend/config/deploy.yml | 29 + backend/config/environment.rb | 5 + backend/config/environments/development.rb | 75 ++ backend/config/environments/production.rb | 49 ++ backend/config/environments/test.rb | 67 ++ backend/config/initializers/attr_encrypted.rb | 1 + backend/config/initializers/cors.rb | 9 + .../initializers/filter_parameter_logging.rb | 4 + backend/config/initializers/inflections.rb | 16 + backend/config/initializers/match_live_tv.rb | 55 ++ backend/config/initializers/rack_attack.rb | 11 + backend/config/initializers/sidekiq.rb | 9 + backend/config/initializers/sidekiq_cron.rb | 2 + backend/config/initializers/stripe.rb | 3 + backend/config/locales/en.yml | 31 + backend/config/puma.rb | 34 + backend/config/routes.rb | 96 +++ backend/config/sidekiq.yml | 4 + backend/config/storage.yml | 34 + ...60525000001_create_match_live_tv_schema.rb | 113 +++ ...120000_add_set_partials_to_score_states.rb | 5 + ...0525140000_add_scoring_rules_to_matches.rb | 5 + ...260525160000_add_billing_and_recordings.rb | 47 ++ .../20260526120000_update_plans_three_tier.rb | 9 + .../migrate/20260527120000_add_staff_kind.rb | 8 + backend/db/schema.rb | 212 +++++ backend/db/seeds.rb | 34 + backend/db/seeds/plans.rb | 56 ++ backend/lib/json_web_token.rb | 15 + backend/lib/tasks/.keep | 0 backend/lib/tasks/stream_sessions.rake | 50 ++ backend/public/favicon.svg | 1 + backend/public/hero-devices.png | Bin 0 -> 107111 bytes backend/public/home-piani-ecosistema.png | Bin 0 -> 92102 bytes backend/public/home-step-invita-trasmette.png | Bin 0 -> 56201 bytes backend/public/home-step-registra-squadra.png | Bin 0 -> 49579 bytes backend/public/home-step-tutti-guardano.png | Bin 0 -> 110827 bytes backend/public/home-step-vai-in-diretta.png | Bin 0 -> 87442 bytes backend/public/live.css | 213 +++++ backend/public/marketing.css | 510 ++++++++++++ backend/public/robots.txt | 1 + backend/spec/rails_helper.rb | 9 + backend/spec/requests/auth_spec.rb | 16 + .../spec/services/teams/entitlements_spec.rb | 49 ++ .../webhooks/mediamtx_handler_spec.rb | 24 + backend/spec/spec_helper.rb | 5 + backend/test_write | 0 backend/vendor/.keep | 0 docs/E2E.md | 34 + docs/PRODUCT_PREMIUM.md | 35 + docs/infrastructure/SERVER_DEPLOYMENT.md | 253 ++++++ infra/.env.example | 7 + infra/.env.production.example | 30 + infra/docker-compose.prod.yml | 117 +++ infra/docker-compose.yml | 119 +++ infra/mediamtx.yml | 45 ++ infra/nginx.conf | 34 + infra/scripts/generate_slate.sh | 12 + infra/slates/README.md | 11 + infra/slates/offline.mp4 | Bin 0 -> 555574 bytes mobile/.gitignore | 45 ++ mobile/.metadata | 45 ++ mobile/README.md | 17 + mobile/analysis_options.yaml | 28 + mobile/android/.gitignore | 14 + mobile/android/app/build.gradle.kts | 59 ++ mobile/android/app/proguard-rules.pro | 2 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 54 ++ .../matchlivetv/match_live_tv/MainActivity.kt | 21 + .../match_live_tv/StreamPreviewHolder.kt | 9 + .../match_live_tv/StreamingEngine.kt | 476 ++++++++++++ .../match_live_tv/StreamingEngineHolder.kt | 22 + .../StreamingForegroundService.kt | 243 ++++++ .../match_live_tv/StreamingPlugin.kt | 286 +++++++ .../StreamingPreviewPlatformView.kt | 42 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/strings.xml | 12 + .../app/src/main/res/values/styles.xml | 18 + .../main/res/xml/network_security_config.xml | 9 + .../app/src/profile/AndroidManifest.xml | 7 + mobile/android/build.gradle.kts | 25 + mobile/android/gradle.properties | 6 + .../gradle/wrapper/gradle-wrapper.properties | 5 + mobile/android/settings.gradle.kts | 35 + mobile/ios/.gitignore | 34 + mobile/ios/Flutter/AppFrameworkInfo.plist | 24 + mobile/ios/Flutter/Debug.xcconfig | 1 + mobile/ios/Flutter/Release.xcconfig | 1 + mobile/ios/Runner.xcodeproj/project.pbxproj | 644 ++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 119 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + mobile/ios/Runner/AppDelegate.swift | 16 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + mobile/ios/Runner/Base.lproj/Main.storyboard | 26 + mobile/ios/Runner/Info.plist | 70 ++ mobile/ios/Runner/Runner-Bridging-Header.h | 1 + mobile/ios/Runner/SceneDelegate.swift | 6 + mobile/ios/RunnerTests/RunnerTests.swift | 12 + mobile/lib/app/router.dart | 90 +++ mobile/lib/app/theme.dart | 93 +++ mobile/lib/core/auth_storage.dart | 64 ++ mobile/lib/core/config.dart | 24 + .../lib/features/archive/archive_screen.dart | 113 +++ mobile/lib/features/auth/login_screen.dart | 157 ++++ mobile/lib/features/auth/splash_screen.dart | 55 ++ .../features/camera_mode/camera_screen.dart | 652 ++++++++++++++++ .../camera_mode/camera_screen_landscape.dart | 134 ++++ .../camera_mode/camera_screen_portrait.dart | 158 ++++ .../controller_mode/controller_landscape.dart | 188 +++++ .../controller_mode/controller_portrait.dart | 187 +++++ .../controller_mode/controller_screen.dart | 424 ++++++++++ .../lib/features/matches/matches_screen.dart | 401 ++++++++++ .../features/matches/no_team_onboarding.dart | 56 ++ .../matches/resume_session_sheet.dart | 97 +++ .../lib/features/matches/team_providers.dart | 34 + .../lib/features/setup_wizard/step_match.dart | 284 +++++++ .../setup_wizard/step_network_test.dart | 295 +++++++ .../lib/features/setup_wizard/step_roles.dart | 238 ++++++ .../setup_wizard/step_transmission.dart | 331 ++++++++ .../features/setup_wizard/wizard_shell.dart | 173 +++++ .../features/shared/live_score_actions.dart | 94 +++ mobile/lib/main.dart | 38 + mobile/lib/platform/streaming_channel.dart | 53 ++ mobile/lib/platform/streaming_preview.dart | 103 +++ mobile/lib/providers/auth_provider.dart | 90 +++ .../lib/providers/match_rules_provider.dart | 12 + mobile/lib/providers/score_provider.dart | 77 ++ mobile/lib/providers/session_provider.dart | 165 ++++ mobile/lib/shared/api_client.dart | 249 ++++++ mobile/lib/shared/api_error.dart | 32 + mobile/lib/shared/models/device_state.dart | 37 + mobile/lib/shared/models/match.dart | 81 ++ mobile/lib/shared/models/recording_item.dart | 35 + mobile/lib/shared/models/score_state.dart | 100 +++ mobile/lib/shared/models/stream_session.dart | 129 ++++ mobile/lib/shared/models/team.dart | 96 +++ mobile/lib/shared/models/user.dart | 46 ++ mobile/lib/shared/premium_dialog.dart | 37 + .../shared/scoring/match_scoring_rules.dart | 70 ++ mobile/lib/shared/websocket_service.dart | 158 ++++ .../widgets/camera_compact_metrics.dart | 39 + .../shared/widgets/camera_score_controls.dart | 303 ++++++++ .../lib/shared/widgets/connected_badge.dart | 42 + .../lib/shared/widgets/destructive_cta.dart | 65 ++ .../lib/shared/widgets/end_stream_dialog.dart | 31 + mobile/lib/shared/widgets/live_badge.dart | 59 ++ .../shared/widgets/match_live_wordmark.dart | 74 ++ mobile/lib/shared/widgets/metric_card.dart | 65 ++ mobile/lib/shared/widgets/primary_cta.dart | 66 ++ .../shared/widgets/score_outcome_dialogs.dart | 96 +++ .../lib/shared/widgets/score_overlay_bar.dart | 133 ++++ mobile/lib/shared/widgets/screen_insets.dart | 84 ++ mobile/linux/.gitignore | 1 + mobile/linux/CMakeLists.txt | 128 +++ mobile/linux/flutter/CMakeLists.txt | 88 +++ .../flutter/generated_plugin_registrant.cc | 15 + .../flutter/generated_plugin_registrant.h | 15 + mobile/linux/flutter/generated_plugins.cmake | 25 + mobile/linux/runner/CMakeLists.txt | 26 + mobile/linux/runner/main.cc | 6 + mobile/linux/runner/my_application.cc | 148 ++++ mobile/linux/runner/my_application.h | 21 + mobile/macos/.gitignore | 7 + mobile/macos/Flutter/Flutter-Debug.xcconfig | 1 + mobile/macos/Flutter/Flutter-Release.xcconfig | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 24 + mobile/macos/Runner.xcodeproj/project.pbxproj | 729 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 117 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + mobile/macos/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes mobile/macos/Runner/Base.lproj/MainMenu.xib | 343 ++++++++ mobile/macos/Runner/Configs/AppInfo.xcconfig | 14 + mobile/macos/Runner/Configs/Debug.xcconfig | 2 + mobile/macos/Runner/Configs/Release.xcconfig | 2 + mobile/macos/Runner/Configs/Warnings.xcconfig | 13 + mobile/macos/Runner/DebugProfile.entitlements | 12 + mobile/macos/Runner/Info.plist | 32 + mobile/macos/Runner/MainFlutterWindow.swift | 15 + mobile/macos/Runner/Release.entitlements | 8 + mobile/macos/RunnerTests/RunnerTests.swift | 12 + mobile/pubspec.yaml | 34 + mobile/test/widget_test.dart | 23 + mobile/web/favicon.png | Bin 0 -> 917 bytes mobile/web/icons/Icon-192.png | Bin 0 -> 5292 bytes mobile/web/icons/Icon-512.png | Bin 0 -> 8252 bytes mobile/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes mobile/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes mobile/web/index.html | 46 ++ mobile/web/manifest.json | 35 + mobile/windows/.gitignore | 17 + mobile/windows/CMakeLists.txt | 108 +++ mobile/windows/flutter/CMakeLists.txt | 109 +++ .../flutter/generated_plugin_registrant.cc | 23 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 28 + mobile/windows/runner/CMakeLists.txt | 40 + mobile/windows/runner/Runner.rc | 121 +++ mobile/windows/runner/flutter_window.cpp | 71 ++ mobile/windows/runner/flutter_window.h | 33 + mobile/windows/runner/main.cpp | 43 ++ mobile/windows/runner/resource.h | 16 + mobile/windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes mobile/windows/runner/runner.exe.manifest | 14 + mobile/windows/runner/utils.cpp | 69 ++ mobile/windows/runner/utils.h | 19 + mobile/windows/runner/win32_window.cpp | 288 +++++++ mobile/windows/runner/win32_window.h | 102 +++ scripts/deploy/post_bootstrap_deploy.sh | 24 + scripts/deploy/server_bootstrap.sh | 59 ++ scripts/deploy/sync_to_server.sh | 33 + scripts/e2e_api.sh | 72 ++ scripts/run_mobile_android.sh | 14 + scripts/run_mobile_android_prod.sh | 28 + scripts/run_mobile_linux.sh | 7 + 381 files changed, 20599 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 backend/.dockerignore create mode 100644 backend/.gitattributes create mode 100644 backend/.github/dependabot.yml create mode 100644 backend/.github/workflows/ci.yml create mode 100644 backend/.gitignore create mode 100644 backend/.rubocop.yml create mode 100644 backend/.ruby-version create mode 100644 backend/Dockerfile create mode 100644 backend/Gemfile create mode 100644 backend/Gemfile.lock create mode 100644 backend/README.md create mode 100644 backend/Rakefile create mode 100644 backend/app/channels/application_cable/channel.rb create mode 100644 backend/app/channels/application_cable/connection.rb create mode 100644 backend/app/channels/concerns/cable_broadcastable.rb create mode 100644 backend/app/channels/session_channel.rb create mode 100644 backend/app/controllers/admin/base_controller.rb create mode 100644 backend/app/controllers/admin/dashboard_controller.rb create mode 100644 backend/app/controllers/admin/sessions_controller.rb create mode 100644 backend/app/controllers/admin/teams_controller.rb create mode 100644 backend/app/controllers/api/v1/auth_controller.rb create mode 100644 backend/app/controllers/api/v1/base_controller.rb create mode 100644 backend/app/controllers/api/v1/matches_controller.rb create mode 100644 backend/app/controllers/api/v1/stream_sessions_controller.rb create mode 100644 backend/app/controllers/api/v1/teams_controller.rb create mode 100644 backend/app/controllers/api/v1/youtube_controller.rb create mode 100644 backend/app/controllers/application_controller.rb create mode 100644 backend/app/controllers/concerns/.keep create mode 100644 backend/app/controllers/hls_proxy_controller.rb create mode 100644 backend/app/controllers/public/invitations_controller.rb create mode 100644 backend/app/controllers/public/live_controller.rb create mode 100644 backend/app/controllers/public/pages_controller.rb create mode 100644 backend/app/controllers/public/registrations_controller.rb create mode 100644 backend/app/controllers/public/replay_controller.rb create mode 100644 backend/app/controllers/public/sessions_controller.rb create mode 100644 backend/app/controllers/public/site_base_controller.rb create mode 100644 backend/app/controllers/public/teams_controller.rb create mode 100644 backend/app/controllers/public/web_base_controller.rb create mode 100644 backend/app/controllers/webhooks/mediamtx_controller.rb create mode 100644 backend/app/controllers/webhooks/stripe_controller.rb create mode 100644 backend/app/helpers/public/live_helper.rb create mode 100644 backend/app/jobs/application_job.rb create mode 100644 backend/app/jobs/cleanup_expired_sessions_job.rb create mode 100644 backend/app/jobs/disconnection_timeout_job.rb create mode 100644 backend/app/jobs/notify_stream_health_job.rb create mode 100644 backend/app/jobs/stream_analytics_job.rb create mode 100644 backend/app/mailers/application_mailer.rb create mode 100644 backend/app/models/application_record.rb create mode 100644 backend/app/models/concerns/.keep create mode 100644 backend/app/models/device_state.rb create mode 100644 backend/app/models/match.rb create mode 100644 backend/app/models/plan.rb create mode 100644 backend/app/models/recording.rb create mode 100644 backend/app/models/score_state.rb create mode 100644 backend/app/models/stream_event.rb create mode 100644 backend/app/models/stream_session.rb create mode 100644 backend/app/models/subscription.rb create mode 100644 backend/app/models/team.rb create mode 100644 backend/app/models/team_invitation.rb create mode 100644 backend/app/models/user.rb create mode 100644 backend/app/models/user_team.rb create mode 100644 backend/app/models/youtube_credential.rb create mode 100644 backend/app/services/billing/assign_plan.rb create mode 100644 backend/app/services/billing/stripe/checkout_session.rb create mode 100644 backend/app/services/billing/stripe/webhook_handler.rb create mode 100644 backend/app/services/mediamtx/client.rb create mode 100644 backend/app/services/recordings/index_session.rb create mode 100644 backend/app/services/sessions/create.rb create mode 100644 backend/app/services/sessions/pause.rb create mode 100644 backend/app/services/sessions/start.rb create mode 100644 backend/app/services/sessions/stop.rb create mode 100644 backend/app/services/teams/entitlement_error.rb create mode 100644 backend/app/services/teams/entitlements.rb create mode 100644 backend/app/services/webhooks/mediamtx_handler.rb create mode 100644 backend/app/services/webhooks/signature.rb create mode 100644 backend/app/services/youtube/broadcast_service.rb create mode 100644 backend/app/services/youtube/oauth_exchange.rb create mode 100644 backend/app/services/youtube/oauth_refresh.rb create mode 100644 backend/app/services/youtube/oauth_url.rb create mode 100644 backend/app/views/admin/dashboard/index.html.erb create mode 100644 backend/app/views/admin/sessions/index.html.erb create mode 100644 backend/app/views/admin/sessions/show.html.erb create mode 100644 backend/app/views/admin/teams/index.html.erb create mode 100644 backend/app/views/admin/teams/show.html.erb create mode 100644 backend/app/views/layouts/admin.html.erb create mode 100644 backend/app/views/layouts/mailer.html.erb create mode 100644 backend/app/views/layouts/mailer.text.erb create mode 100644 backend/app/views/layouts/marketing.html.erb create mode 100644 backend/app/views/layouts/marketing_live.html.erb create mode 100644 backend/app/views/layouts/public.html.erb create mode 100644 backend/app/views/public/invitations/show.html.erb create mode 100644 backend/app/views/public/live/index.html.erb create mode 100644 backend/app/views/public/live/show.html.erb create mode 100644 backend/app/views/public/pages/features.html.erb create mode 100644 backend/app/views/public/pages/home.html.erb create mode 100644 backend/app/views/public/pages/pricing.html.erb create mode 100644 backend/app/views/public/pages/privacy.html.erb create mode 100644 backend/app/views/public/pages/terms.html.erb create mode 100644 backend/app/views/public/registrations/new.html.erb create mode 100644 backend/app/views/public/replay/show.html.erb create mode 100644 backend/app/views/public/sessions/new.html.erb create mode 100644 backend/app/views/public/teams/billing.html.erb create mode 100644 backend/app/views/public/teams/dashboard.html.erb create mode 100644 backend/app/views/public/teams/invite.html.erb create mode 100644 backend/app/views/public/teams/new.html.erb create mode 100644 backend/app/views/shared/_marketing_footer.html.erb create mode 100644 backend/app/views/shared/_marketing_nav.html.erb create mode 100644 backend/app/views/shared/_meta_tags.html.erb create mode 100755 backend/bin/brakeman create mode 100755 backend/bin/bundle create mode 100755 backend/bin/docker-entrypoint create mode 100755 backend/bin/rails create mode 100755 backend/bin/rake create mode 100755 backend/bin/rubocop create mode 100755 backend/bin/setup create mode 100644 backend/config.ru create mode 100644 backend/config/application.rb create mode 100644 backend/config/boot.rb create mode 100644 backend/config/cable.yml create mode 100644 backend/config/credentials.yml.enc create mode 100644 backend/config/database.yml create mode 100644 backend/config/deploy.yml create mode 100644 backend/config/environment.rb create mode 100644 backend/config/environments/development.rb create mode 100644 backend/config/environments/production.rb create mode 100644 backend/config/environments/test.rb create mode 100644 backend/config/initializers/attr_encrypted.rb create mode 100644 backend/config/initializers/cors.rb create mode 100644 backend/config/initializers/filter_parameter_logging.rb create mode 100644 backend/config/initializers/inflections.rb create mode 100644 backend/config/initializers/match_live_tv.rb create mode 100644 backend/config/initializers/rack_attack.rb create mode 100644 backend/config/initializers/sidekiq.rb create mode 100644 backend/config/initializers/sidekiq_cron.rb create mode 100644 backend/config/initializers/stripe.rb create mode 100644 backend/config/locales/en.yml create mode 100644 backend/config/puma.rb create mode 100644 backend/config/routes.rb create mode 100644 backend/config/sidekiq.yml create mode 100644 backend/config/storage.yml create mode 100644 backend/db/migrate/20260525000001_create_match_live_tv_schema.rb create mode 100644 backend/db/migrate/20260525120000_add_set_partials_to_score_states.rb create mode 100644 backend/db/migrate/20260525140000_add_scoring_rules_to_matches.rb create mode 100644 backend/db/migrate/20260525160000_add_billing_and_recordings.rb create mode 100644 backend/db/migrate/20260526120000_update_plans_three_tier.rb create mode 100644 backend/db/migrate/20260527120000_add_staff_kind.rb create mode 100644 backend/db/schema.rb create mode 100644 backend/db/seeds.rb create mode 100644 backend/db/seeds/plans.rb create mode 100644 backend/lib/json_web_token.rb create mode 100644 backend/lib/tasks/.keep create mode 100644 backend/lib/tasks/stream_sessions.rake create mode 100644 backend/public/favicon.svg create mode 100644 backend/public/hero-devices.png create mode 100644 backend/public/home-piani-ecosistema.png create mode 100644 backend/public/home-step-invita-trasmette.png create mode 100644 backend/public/home-step-registra-squadra.png create mode 100644 backend/public/home-step-tutti-guardano.png create mode 100644 backend/public/home-step-vai-in-diretta.png create mode 100644 backend/public/live.css create mode 100644 backend/public/marketing.css create mode 100644 backend/public/robots.txt create mode 100644 backend/spec/rails_helper.rb create mode 100644 backend/spec/requests/auth_spec.rb create mode 100644 backend/spec/services/teams/entitlements_spec.rb create mode 100644 backend/spec/services/webhooks/mediamtx_handler_spec.rb create mode 100644 backend/spec/spec_helper.rb create mode 100644 backend/test_write create mode 100644 backend/vendor/.keep create mode 100644 docs/E2E.md create mode 100644 docs/PRODUCT_PREMIUM.md create mode 100644 docs/infrastructure/SERVER_DEPLOYMENT.md create mode 100644 infra/.env.example create mode 100644 infra/.env.production.example create mode 100644 infra/docker-compose.prod.yml create mode 100644 infra/docker-compose.yml create mode 100644 infra/mediamtx.yml create mode 100644 infra/nginx.conf create mode 100755 infra/scripts/generate_slate.sh create mode 100644 infra/slates/README.md create mode 100644 infra/slates/offline.mp4 create mode 100644 mobile/.gitignore create mode 100644 mobile/.metadata create mode 100644 mobile/README.md create mode 100644 mobile/analysis_options.yaml create mode 100644 mobile/android/.gitignore create mode 100644 mobile/android/app/build.gradle.kts create mode 100644 mobile/android/app/proguard-rules.pro create mode 100644 mobile/android/app/src/debug/AndroidManifest.xml create mode 100644 mobile/android/app/src/main/AndroidManifest.xml create mode 100644 mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/MainActivity.kt create mode 100644 mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamPreviewHolder.kt create mode 100644 mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingEngine.kt create mode 100644 mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingEngineHolder.kt create mode 100644 mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingForegroundService.kt create mode 100644 mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingPlugin.kt create mode 100644 mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingPreviewPlatformView.kt create mode 100644 mobile/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 mobile/android/app/src/main/res/drawable/launch_background.xml create mode 100644 mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 mobile/android/app/src/main/res/values-night/styles.xml create mode 100644 mobile/android/app/src/main/res/values/strings.xml create mode 100644 mobile/android/app/src/main/res/values/styles.xml create mode 100644 mobile/android/app/src/main/res/xml/network_security_config.xml create mode 100644 mobile/android/app/src/profile/AndroidManifest.xml create mode 100644 mobile/android/build.gradle.kts create mode 100644 mobile/android/gradle.properties create mode 100644 mobile/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 mobile/android/settings.gradle.kts create mode 100644 mobile/ios/.gitignore create mode 100644 mobile/ios/Flutter/AppFrameworkInfo.plist create mode 100644 mobile/ios/Flutter/Debug.xcconfig create mode 100644 mobile/ios/Flutter/Release.xcconfig create mode 100644 mobile/ios/Runner.xcodeproj/project.pbxproj create mode 100644 mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 mobile/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 mobile/ios/Runner/AppDelegate.swift create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 mobile/ios/Runner/Base.lproj/Main.storyboard create mode 100644 mobile/ios/Runner/Info.plist create mode 100644 mobile/ios/Runner/Runner-Bridging-Header.h create mode 100644 mobile/ios/Runner/SceneDelegate.swift create mode 100644 mobile/ios/RunnerTests/RunnerTests.swift create mode 100644 mobile/lib/app/router.dart create mode 100644 mobile/lib/app/theme.dart create mode 100644 mobile/lib/core/auth_storage.dart create mode 100644 mobile/lib/core/config.dart create mode 100644 mobile/lib/features/archive/archive_screen.dart create mode 100644 mobile/lib/features/auth/login_screen.dart create mode 100644 mobile/lib/features/auth/splash_screen.dart create mode 100644 mobile/lib/features/camera_mode/camera_screen.dart create mode 100644 mobile/lib/features/camera_mode/camera_screen_landscape.dart create mode 100644 mobile/lib/features/camera_mode/camera_screen_portrait.dart create mode 100644 mobile/lib/features/controller_mode/controller_landscape.dart create mode 100644 mobile/lib/features/controller_mode/controller_portrait.dart create mode 100644 mobile/lib/features/controller_mode/controller_screen.dart create mode 100644 mobile/lib/features/matches/matches_screen.dart create mode 100644 mobile/lib/features/matches/no_team_onboarding.dart create mode 100644 mobile/lib/features/matches/resume_session_sheet.dart create mode 100644 mobile/lib/features/matches/team_providers.dart create mode 100644 mobile/lib/features/setup_wizard/step_match.dart create mode 100644 mobile/lib/features/setup_wizard/step_network_test.dart create mode 100644 mobile/lib/features/setup_wizard/step_roles.dart create mode 100644 mobile/lib/features/setup_wizard/step_transmission.dart create mode 100644 mobile/lib/features/setup_wizard/wizard_shell.dart create mode 100644 mobile/lib/features/shared/live_score_actions.dart create mode 100644 mobile/lib/main.dart create mode 100644 mobile/lib/platform/streaming_channel.dart create mode 100644 mobile/lib/platform/streaming_preview.dart create mode 100644 mobile/lib/providers/auth_provider.dart create mode 100644 mobile/lib/providers/match_rules_provider.dart create mode 100644 mobile/lib/providers/score_provider.dart create mode 100644 mobile/lib/providers/session_provider.dart create mode 100644 mobile/lib/shared/api_client.dart create mode 100644 mobile/lib/shared/api_error.dart create mode 100644 mobile/lib/shared/models/device_state.dart create mode 100644 mobile/lib/shared/models/match.dart create mode 100644 mobile/lib/shared/models/recording_item.dart create mode 100644 mobile/lib/shared/models/score_state.dart create mode 100644 mobile/lib/shared/models/stream_session.dart create mode 100644 mobile/lib/shared/models/team.dart create mode 100644 mobile/lib/shared/models/user.dart create mode 100644 mobile/lib/shared/premium_dialog.dart create mode 100644 mobile/lib/shared/scoring/match_scoring_rules.dart create mode 100644 mobile/lib/shared/websocket_service.dart create mode 100644 mobile/lib/shared/widgets/camera_compact_metrics.dart create mode 100644 mobile/lib/shared/widgets/camera_score_controls.dart create mode 100644 mobile/lib/shared/widgets/connected_badge.dart create mode 100644 mobile/lib/shared/widgets/destructive_cta.dart create mode 100644 mobile/lib/shared/widgets/end_stream_dialog.dart create mode 100644 mobile/lib/shared/widgets/live_badge.dart create mode 100644 mobile/lib/shared/widgets/match_live_wordmark.dart create mode 100644 mobile/lib/shared/widgets/metric_card.dart create mode 100644 mobile/lib/shared/widgets/primary_cta.dart create mode 100644 mobile/lib/shared/widgets/score_outcome_dialogs.dart create mode 100644 mobile/lib/shared/widgets/score_overlay_bar.dart create mode 100644 mobile/lib/shared/widgets/screen_insets.dart create mode 100644 mobile/linux/.gitignore create mode 100644 mobile/linux/CMakeLists.txt create mode 100644 mobile/linux/flutter/CMakeLists.txt create mode 100644 mobile/linux/flutter/generated_plugin_registrant.cc create mode 100644 mobile/linux/flutter/generated_plugin_registrant.h create mode 100644 mobile/linux/flutter/generated_plugins.cmake create mode 100644 mobile/linux/runner/CMakeLists.txt create mode 100644 mobile/linux/runner/main.cc create mode 100644 mobile/linux/runner/my_application.cc create mode 100644 mobile/linux/runner/my_application.h create mode 100644 mobile/macos/.gitignore create mode 100644 mobile/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 mobile/macos/Flutter/Flutter-Release.xcconfig create mode 100644 mobile/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 mobile/macos/Runner.xcodeproj/project.pbxproj create mode 100644 mobile/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 mobile/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 mobile/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 mobile/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 mobile/macos/Runner/AppDelegate.swift create mode 100644 mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 mobile/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 mobile/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 mobile/macos/Runner/Configs/Debug.xcconfig create mode 100644 mobile/macos/Runner/Configs/Release.xcconfig create mode 100644 mobile/macos/Runner/Configs/Warnings.xcconfig create mode 100644 mobile/macos/Runner/DebugProfile.entitlements create mode 100644 mobile/macos/Runner/Info.plist create mode 100644 mobile/macos/Runner/MainFlutterWindow.swift create mode 100644 mobile/macos/Runner/Release.entitlements create mode 100644 mobile/macos/RunnerTests/RunnerTests.swift create mode 100644 mobile/pubspec.yaml create mode 100644 mobile/test/widget_test.dart create mode 100644 mobile/web/favicon.png create mode 100644 mobile/web/icons/Icon-192.png create mode 100644 mobile/web/icons/Icon-512.png create mode 100644 mobile/web/icons/Icon-maskable-192.png create mode 100644 mobile/web/icons/Icon-maskable-512.png create mode 100644 mobile/web/index.html create mode 100644 mobile/web/manifest.json create mode 100644 mobile/windows/.gitignore create mode 100644 mobile/windows/CMakeLists.txt create mode 100644 mobile/windows/flutter/CMakeLists.txt create mode 100644 mobile/windows/flutter/generated_plugin_registrant.cc create mode 100644 mobile/windows/flutter/generated_plugin_registrant.h create mode 100644 mobile/windows/flutter/generated_plugins.cmake create mode 100644 mobile/windows/runner/CMakeLists.txt create mode 100644 mobile/windows/runner/Runner.rc create mode 100644 mobile/windows/runner/flutter_window.cpp create mode 100644 mobile/windows/runner/flutter_window.h create mode 100644 mobile/windows/runner/main.cpp create mode 100644 mobile/windows/runner/resource.h create mode 100644 mobile/windows/runner/resources/app_icon.ico create mode 100644 mobile/windows/runner/runner.exe.manifest create mode 100644 mobile/windows/runner/utils.cpp create mode 100644 mobile/windows/runner/utils.h create mode 100644 mobile/windows/runner/win32_window.cpp create mode 100644 mobile/windows/runner/win32_window.h create mode 100644 scripts/deploy/post_bootstrap_deploy.sh create mode 100755 scripts/deploy/server_bootstrap.sh create mode 100755 scripts/deploy/sync_to_server.sh create mode 100755 scripts/e2e_api.sh create mode 100755 scripts/run_mobile_android.sh create mode 100755 scripts/run_mobile_android_prod.sh create mode 100755 scripts/run_mobile_linux.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac7cba2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Ruby +backend/.bundle +backend/vendor/bundle +backend/tmp/ +backend/log/ +backend/storage/ +backend/.env +backend/.env.* + +# Flutter +mobile/.dart_tool/ +mobile/.flutter-plugins +mobile/.flutter-plugins-dependencies +mobile/build/ +mobile/.packages +mobile/pubspec.lock + +# Infra +infra/certs/ +infra/recordings/ +infra/.env +infra/.env.production.generated + +# OS +.DS_Store +*.swp +.idea/ +.vscode/ + +# Secrets +.env +*.pem +credentials.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..d902b54 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# Match Live TV + +Piattaforma mobile-first per streaming di partite sportive giovanili — MVP su **Match Live TV** (HLS sul nostro sito); YouTube/Facebook/Twitch come integrazioni premium. + +**Slogan:** *Lo streaming che non muore* + +## Struttura monorepo + +``` +├── backend/ # Rails 7.2 API + Action Cable + Sidekiq +├── mobile/ # Flutter app (Camera + Regia) +├── infra/ # Docker Compose, MediaMTX, Nginx, Kamal +└── docs/ # Documentazione +``` + +## Deploy produzione (server 192.168.1.146) + +Vedi [docs/infrastructure/SERVER_DEPLOYMENT.md](docs/infrastructure/SERVER_DEPLOYMENT.md) per porte router, NPM e procedura completa. + +**Porte da aprire sul router:** `1935/TCP` (RTMP). Opzionale `8888/TCP` (HLS). HTTPS via Nginx Proxy Manager verso `http://192.168.1.146:3000`. + +**Sul server (una tantum, con sudo):** +```bash +sudo bash ~/matchlivetv/scripts/deploy/server_bootstrap.sh +bash ~/matchlivetv/scripts/deploy/post_bootstrap_deploy.sh +``` + +## Avvio rapido (locale) + +```bash +cd infra +cp .env.example .env +docker compose up -d --build +``` + +- API Rails: http://localhost:3000 +- MediaMTX RTMP: rtmp://localhost:1935 +- MediaMTX API: http://localhost:9997 +- Admin: http://localhost:3000/admin + +### Seed utente demo + +``` +email: coach@matchlivetv.test +password: password123 +``` + +## Mobile + +```bash +cd mobile +flutter pub get +flutter run +``` + +Configura `API_BASE_URL` in `lib/core/config.dart` (default `http://10.0.2.2:3000` per emulatore Android). + +## Architettura + +- Video (MVP): Phone → MediaMTX (RTMP) → HLS su `matchlivetv.*` (pagina `/live/:session_id`) +- Video (premium): stesso ingest + relay ffmpeg verso YouTube +- Controllo: Phone ↔ Action Cable ↔ Rails ↔ PostgreSQL +- Disconnessioni: MediaMTX `alwaysAvailable` slate + timeout 5 min (Sidekiq) + +### Mobile produzione + +```bash +./scripts/run_mobile_android_prod.sh +# API: https://matchlivetv.eminux.it +``` diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..7f5a804 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,42 @@ +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. + +# Ignore git directory. +/.git/ +/.gitignore + +# Ignore bundler config. +/.bundle + +# Ignore all environment files (except templates). +/.env* +!/.env*.erb + +# Ignore all default key files. +/config/master.key +/config/credentials/*.key + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/.keep + +# Ignore CI service files. +/.github + +# Ignore development files +/.devcontainer + +# Ignore Docker-related files +/.dockerignore +/Dockerfile* diff --git a/backend/.gitattributes b/backend/.gitattributes new file mode 100644 index 0000000..8dc4323 --- /dev/null +++ b/backend/.gitattributes @@ -0,0 +1,9 @@ +# See https://git-scm.com/docs/gitattributes for more about git attribute files. + +# Mark the database schema as having been generated. +db/schema.rb linguist-generated + +# Mark any vendored files as having been vendored. +vendor/* linguist-vendored +config/credentials/*.yml.enc diff=rails_credentials +config/credentials.yml.enc diff=rails_credentials diff --git a/backend/.github/dependabot.yml b/backend/.github/dependabot.yml new file mode 100644 index 0000000..f0527e6 --- /dev/null +++ b/backend/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/backend/.github/workflows/ci.yml b/backend/.github/workflows/ci.yml new file mode 100644 index 0000000..fb0f4ea --- /dev/null +++ b/backend/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + pull_request: + push: + branches: [ main ] + +jobs: + scan_ruby: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Scan for common Rails security vulnerabilities using static analysis + run: bin/brakeman --no-pager + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Lint code for consistent style + run: bin/rubocop -f github + diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..5ba3862 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,33 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# Temporary files generated by your text editor or operating system +# belong in git's global ignore instead: +# `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` + +# Ignore bundler config. +/.bundle + +# Ignore all environment files (except templates). +/.env* +!/.env*.erb + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/ +!/tmp/storage/.keep + +# Ignore master key for decrypting credentials and more. +/config/master.key diff --git a/backend/.rubocop.yml b/backend/.rubocop.yml new file mode 100644 index 0000000..f9d86d4 --- /dev/null +++ b/backend/.rubocop.yml @@ -0,0 +1,8 @@ +# Omakase Ruby styling for Rails +inherit_gem: { rubocop-rails-omakase: rubocop.yml } + +# Overwrite or add rules to create your own house style +# +# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` +# Layout/SpaceInsideArrayLiteralBrackets: +# Enabled: false diff --git a/backend/.ruby-version b/backend/.ruby-version new file mode 100644 index 0000000..e41c6bf --- /dev/null +++ b/backend/.ruby-version @@ -0,0 +1 @@ +ruby-3.3.11 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..bb6d341 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,19 @@ +FROM ruby:3.3-bookworm + +RUN apt-get update -qq && \ + apt-get install -y --no-install-recommends build-essential libpq-dev curl ffmpeg && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +ENV BUNDLE_WITHOUT="development:test" +ENV BUNDLE_DEPLOYMENT="1" + +COPY Gemfile Gemfile.lock ./ +RUN bundle install + +COPY . . + +EXPOSE 3000 + +CMD ["bundle", "exec", "puma", "-C", "config/puma.rb", "-b", "tcp://0.0.0.0:3000"] diff --git a/backend/Gemfile b/backend/Gemfile new file mode 100644 index 0000000..fa5a506 --- /dev/null +++ b/backend/Gemfile @@ -0,0 +1,33 @@ +source "https://rubygems.org" + +gem "rails", "~> 7.2.2" +gem "pg", "~> 1.1" +gem "puma", ">= 5.0" +gem "bootsnap", require: false +gem "tzinfo-data", platforms: %i[ windows jruby ] + +gem "redis", ">= 4.0.1" +gem "bcrypt", "~> 3.1.7" +gem "rack-cors" +gem "jwt" +gem "sidekiq" +gem "rack-attack" +gem "aasm" +gem "attr_encrypted" +gem "google-apis-youtube_v3" +gem "faraday" +gem "stripe" +gem "kamal", require: false + +group :development, :test do + gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" + gem "brakeman", require: false + gem "rubocop-rails-omakase", require: false + gem "rspec-rails" + gem "factory_bot_rails" + gem "faker" +end + +group :development do + gem "letter_opener" +end diff --git a/backend/Gemfile.lock b/backend/Gemfile.lock new file mode 100644 index 0000000..2864ced --- /dev/null +++ b/backend/Gemfile.lock @@ -0,0 +1,443 @@ +GEM + remote: https://rubygems.org/ + specs: + aasm (5.5.2) + concurrent-ruby (~> 1.0) + actioncable (7.2.3.1) + actionpack (= 7.2.3.1) + activesupport (= 7.2.3.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (7.2.3.1) + actionpack (= 7.2.3.1) + activejob (= 7.2.3.1) + activerecord (= 7.2.3.1) + activestorage (= 7.2.3.1) + activesupport (= 7.2.3.1) + mail (>= 2.8.0) + actionmailer (7.2.3.1) + actionpack (= 7.2.3.1) + actionview (= 7.2.3.1) + activejob (= 7.2.3.1) + activesupport (= 7.2.3.1) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (7.2.3.1) + actionview (= 7.2.3.1) + activesupport (= 7.2.3.1) + cgi + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4, < 3.3) + rack-session (>= 1.0.1) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (7.2.3.1) + actionpack (= 7.2.3.1) + activerecord (= 7.2.3.1) + activestorage (= 7.2.3.1) + activesupport (= 7.2.3.1) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.2.3.1) + activesupport (= 7.2.3.1) + builder (~> 3.1) + cgi + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.2.3.1) + activesupport (= 7.2.3.1) + globalid (>= 0.3.6) + activemodel (7.2.3.1) + activesupport (= 7.2.3.1) + activerecord (7.2.3.1) + activemodel (= 7.2.3.1) + activesupport (= 7.2.3.1) + timeout (>= 0.4.0) + activestorage (7.2.3.1) + actionpack (= 7.2.3.1) + activejob (= 7.2.3.1) + activerecord (= 7.2.3.1) + activesupport (= 7.2.3.1) + marcel (~> 1.0) + activesupport (7.2.3.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1, < 6) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) + ast (2.4.3) + attr_encrypted (4.2.0) + encryptor (~> 3.0.0) + base64 (0.3.0) + bcrypt (3.1.22) + bcrypt_pbkdf (1.1.2) + bcrypt_pbkdf (1.1.2-arm64-darwin) + bcrypt_pbkdf (1.1.2-x86_64-darwin) + benchmark (0.5.0) + bigdecimal (4.1.2) + bootsnap (1.24.5) + msgpack (~> 1.2) + brakeman (8.0.4) + racc + builder (3.3.0) + cgi (0.5.1) + childprocess (5.1.0) + logger (~> 1.5) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + crass (1.0.6) + date (3.5.1) + debug (1.11.1) + irb (~> 1.10) + reline (>= 0.3.8) + declarative (0.0.20) + diff-lcs (1.6.2) + dotenv (3.2.0) + drb (2.2.3) + ed25519 (1.4.0) + encryptor (3.0.0) + erb (6.0.4) + erubi (1.13.1) + factory_bot (6.6.0) + activesupport (>= 6.1.0) + factory_bot_rails (6.5.1) + factory_bot (~> 6.5) + railties (>= 6.1.0) + faker (3.8.0) + i18n (>= 1.8.11, < 2) + faraday (2.14.2) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-follow_redirects (0.5.0) + faraday (>= 1, < 3) + faraday-net_http (3.4.3) + net-http (~> 0.5) + globalid (1.3.0) + activesupport (>= 6.1) + google-apis-core (1.0.2) + addressable (~> 2.8, >= 2.8.7) + faraday (~> 2.13) + faraday-follow_redirects (~> 0.3) + googleauth (~> 1.14) + mini_mime (~> 1.1) + representable (~> 3.0) + retriable (~> 3.1) + google-apis-youtube_v3 (0.64.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-env (2.3.1) + base64 (~> 0.2) + faraday (>= 1.0, < 3.a) + google-logging-utils (0.2.0) + googleauth (1.16.2) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) + jwt (>= 1.4, < 4.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + i18n (1.14.8) + concurrent-ruby (~> 1.0) + io-console (0.8.2) + irb (1.18.0) + pp (>= 0.6.0) + prism (>= 1.3.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.19.5) + jwt (3.2.0) + base64 + kamal (2.11.0) + activesupport (>= 7.0) + base64 (~> 0.2) + bcrypt_pbkdf (~> 1.0) + concurrent-ruby (~> 1.2) + dotenv (~> 3.1) + ed25519 (~> 1.4) + net-ssh (~> 7.3) + sshkit (>= 1.23.0, < 2.0) + thor (~> 1.3) + zeitwerk (>= 2.6.18, < 3.0) + language_server-protocol (3.17.0.5) + launchy (3.1.1) + addressable (~> 2.8) + childprocess (~> 5.0) + logger (~> 1.6) + letter_opener (1.10.0) + launchy (>= 2.2, < 4) + lint_roller (1.1.0) + logger (1.7.0) + loofah (2.25.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.2.1) + mini_mime (1.1.5) + minitest (5.27.0) + msgpack (1.8.0) + multi_json (1.21.1) + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.6.4) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-scp (4.1.0) + net-ssh (>= 2.6.5, < 8.0.0) + net-sftp (4.0.0) + net-ssh (>= 5.0.0, < 8.0.0) + net-smtp (0.5.1) + net-protocol + net-ssh (7.3.2) + nio4r (2.7.5) + nokogiri (1.19.3-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.3-aarch64-linux-musl) + racc (~> 1.4) + nokogiri (1.19.3-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.3-arm-linux-musl) + racc (~> 1.4) + nokogiri (1.19.3-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-linux-musl) + racc (~> 1.4) + os (1.1.4) + ostruct (0.6.3) + parallel (2.1.0) + parser (3.3.11.1) + ast (~> 2.4.1) + racc + pg (1.6.3) + pg (1.6.3-aarch64-linux) + pg (1.6.3-aarch64-linux-musl) + pg (1.6.3-arm64-darwin) + pg (1.6.3-x86_64-darwin) + pg (1.6.3-x86_64-linux) + pg (1.6.3-x86_64-linux-musl) + pp (0.6.3) + prettyprint + prettyprint (0.2.0) + prism (1.9.0) + psych (5.3.1) + date + stringio + public_suffix (7.0.5) + puma (8.0.1) + nio4r (~> 2.0) + racc (1.8.1) + rack (3.2.6) + rack-attack (6.8.0) + rack (>= 1.0, < 4) + rack-cors (3.0.0) + logger + rack (>= 3.0.14) + rack-session (2.1.2) + base64 (>= 0.1.0) + rack (>= 3.0.0) + rack-test (2.2.0) + rack (>= 1.3) + rackup (2.3.1) + rack (>= 3) + rails (7.2.3.1) + actioncable (= 7.2.3.1) + actionmailbox (= 7.2.3.1) + actionmailer (= 7.2.3.1) + actionpack (= 7.2.3.1) + actiontext (= 7.2.3.1) + actionview (= 7.2.3.1) + activejob (= 7.2.3.1) + activemodel (= 7.2.3.1) + activerecord (= 7.2.3.1) + activestorage (= 7.2.3.1) + activesupport (= 7.2.3.1) + bundler (>= 1.15.0) + railties (= 7.2.3.1) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (7.2.3.1) + actionpack (= 7.2.3.1) + activesupport (= 7.2.3.1) + cgi + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) + rainbow (3.1.1) + rake (13.4.2) + rdoc (7.2.0) + erb + psych (>= 4.0.0) + tsort + redis (5.4.1) + redis-client (>= 0.22.0) + redis-client (0.29.0) + connection_pool + regexp_parser (2.12.0) + reline (0.6.3) + io-console (~> 0.5) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.5.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.8) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) + rspec-support (3.13.7) + rubocop (1.86.2) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (>= 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.49.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.1) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.35.2) + activesupport (>= 4.2.0) + lint_roller (~> 1.1) + rack (>= 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rails-omakase (1.1.0) + rubocop (>= 1.72) + rubocop-performance (>= 1.24) + rubocop-rails (>= 2.30) + ruby-progressbar (1.13.0) + securerandom (0.4.1) + sidekiq (8.1.5) + connection_pool (>= 3.0.0) + json (>= 2.16.0) + logger (>= 1.7.0) + rack (>= 3.2.0) + redis-client (>= 0.29.0) + signet (0.21.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + multi_json (~> 1.10) + sshkit (1.25.0) + base64 + logger + net-scp (>= 1.1.2) + net-sftp (>= 2.1.2) + net-ssh (>= 2.8.0) + ostruct + stringio (3.2.0) + stripe (19.1.0) + bigdecimal + logger + thor (1.5.0) + timeout (0.6.1) + trailblazer-option (0.1.2) + tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + uber (0.1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + useragent (0.16.11) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + zeitwerk (2.8.2) + +PLATFORMS + aarch64-linux + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86_64-darwin + x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + aasm + attr_encrypted + bcrypt (~> 3.1.7) + bootsnap + brakeman + debug + factory_bot_rails + faker + faraday + google-apis-youtube_v3 + jwt + kamal + letter_opener + pg (~> 1.1) + puma (>= 5.0) + rack-attack + rack-cors + rails (~> 7.2.2) + redis (>= 4.0.1) + rspec-rails + rubocop-rails-omakase + sidekiq + stripe + tzinfo-data + +BUNDLED WITH + 2.5.22 diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..7db80e4 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,24 @@ +# README + +This README would normally document whatever steps are necessary to get the +application up and running. + +Things you may want to cover: + +* Ruby version + +* System dependencies + +* Configuration + +* Database creation + +* Database initialization + +* How to run the test suite + +* Services (job queues, cache servers, search engines, etc.) + +* Deployment instructions + +* ... diff --git a/backend/Rakefile b/backend/Rakefile new file mode 100644 index 0000000..9a5ea73 --- /dev/null +++ b/backend/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/backend/app/channels/application_cable/channel.rb b/backend/app/channels/application_cable/channel.rb new file mode 100644 index 0000000..d672697 --- /dev/null +++ b/backend/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/backend/app/channels/application_cable/connection.rb b/backend/app/channels/application_cable/connection.rb new file mode 100644 index 0000000..e1044a9 --- /dev/null +++ b/backend/app/channels/application_cable/connection.rb @@ -0,0 +1,17 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + payload = JsonWebToken.decode(token_from_header) + self.current_user = User.find_by(id: payload[:user_id]) if payload + reject_unauthorized_connection unless current_user + end + + private + + def token_from_header + request.headers["Authorization"].to_s.delete_prefix("Bearer ").presence + end + end +end diff --git a/backend/app/channels/concerns/cable_broadcastable.rb b/backend/app/channels/concerns/cable_broadcastable.rb new file mode 100644 index 0000000..cf89fbb --- /dev/null +++ b/backend/app/channels/concerns/cable_broadcastable.rb @@ -0,0 +1,9 @@ +module CableBroadcastable + extend ActiveSupport::Concern + + class_methods do + def broadcast_message(session, data) + broadcast_to(session, method: "receive_message", data: data) + end + end +end diff --git a/backend/app/channels/session_channel.rb b/backend/app/channels/session_channel.rb new file mode 100644 index 0000000..2d81bea --- /dev/null +++ b/backend/app/channels/session_channel.rb @@ -0,0 +1,83 @@ +class SessionChannel < ApplicationCable::Channel + include CableBroadcastable + def subscribed + session = StreamSession.joins(match: :team) + .merge(current_user.teams) + .find(params[:session_id]) + stream_for session + session.device_states.find_or_create_by!(device_role: params[:device_role] || "controller") + end + + def unsubscribed + stop_all_streams + end + + def receive(data) + session = StreamSession.find(params[:session_id]) + handle_message(session, data) + end + + private + + def handle_message(session, data) + case data["type"] + when "command" + SessionChannel.broadcast_message(session, data) + execute_command(session, data) + when "score_update" + update_score(session, data) + when "timeout" + update_timeout(session, data) + when "device_state" + update_device(session, data) + end + end + + def execute_command(session, data) + case data["action"] + when "start_stream" + Sessions::Start.new(session).call + when "stop_stream" + Sessions::Stop.new(session).call + when "pause_stream" + Sessions::Pause.new(session).call + end + end + + def update_score(session, data) + score = session.score_state || session.create_score_state! + score.update!( + home_sets: data["home_sets"] || score.home_sets, + away_sets: data["away_sets"] || score.away_sets, + home_points: data["home_points"] || score.home_points, + away_points: data["away_points"] || score.away_points, + current_set: data["current_set"] || score.current_set, + set_partials: data.key?("set_partials") ? data["set_partials"] : score.set_partials + ) + SessionChannel.broadcast_message(session, score.as_cable_payload) + end + + def update_timeout(session, data) + score = session.score_state + return unless score + + if data["team"] == "home" + score.update!(timeout_home: true) + else + score.update!(timeout_away: true) + end + SessionChannel.broadcast_message(session, { type: "timeout", team: data["team"] }) + end + + def update_device(session, data) + state = session.device_states.find_or_initialize_by(device_role: data["device_role"] || "camera") + state.update!( + battery_level: data["battery"], + network_type: data["network"], + current_bitrate: data["bitrate"], + fps: data["fps"], + last_seen_at: Time.current + ) + SessionChannel.broadcast_message(session, state.as_cable_payload) + end +end diff --git a/backend/app/controllers/admin/base_controller.rb b/backend/app/controllers/admin/base_controller.rb new file mode 100644 index 0000000..ebf3d4c --- /dev/null +++ b/backend/app/controllers/admin/base_controller.rb @@ -0,0 +1,6 @@ +module Admin + class BaseController < ActionController::Base + layout "admin" + protect_from_forgery with: :null_session + end +end diff --git a/backend/app/controllers/admin/dashboard_controller.rb b/backend/app/controllers/admin/dashboard_controller.rb new file mode 100644 index 0000000..94dd830 --- /dev/null +++ b/backend/app/controllers/admin/dashboard_controller.rb @@ -0,0 +1,8 @@ +module Admin + class DashboardController < Admin::BaseController + def index + @teams = Team.includes(:matches).order(:name) + @active_sessions = StreamSession.where(status: %w[live reconnecting connecting]).includes(:match) + end + end +end diff --git a/backend/app/controllers/admin/sessions_controller.rb b/backend/app/controllers/admin/sessions_controller.rb new file mode 100644 index 0000000..cdf87e2 --- /dev/null +++ b/backend/app/controllers/admin/sessions_controller.rb @@ -0,0 +1,12 @@ +module Admin + class SessionsController < Admin::BaseController + def index + @sessions = StreamSession.includes(:match, :user).order(created_at: :desc).limit(50) + end + + def show + @session = StreamSession.find(params[:id]) + @events = @session.stream_events.recent.limit(50) + end + end +end diff --git a/backend/app/controllers/admin/teams_controller.rb b/backend/app/controllers/admin/teams_controller.rb new file mode 100644 index 0000000..fb09527 --- /dev/null +++ b/backend/app/controllers/admin/teams_controller.rb @@ -0,0 +1,12 @@ +module Admin + class TeamsController < Admin::BaseController + def index + @teams = Team.all.order(:name) + end + + def show + @team = Team.find(params[:id]) + @matches = @team.matches.order(scheduled_at: :desc) + end + end +end diff --git a/backend/app/controllers/api/v1/auth_controller.rb b/backend/app/controllers/api/v1/auth_controller.rb new file mode 100644 index 0000000..378e8d2 --- /dev/null +++ b/backend/app/controllers/api/v1/auth_controller.rb @@ -0,0 +1,65 @@ +module Api + module V1 + class AuthController < ApplicationController + skip_before_action :authenticate_request!, only: %i[login refresh register] + + def register + user = User.new( + email: params[:email]&.downcase, + name: params[:name], + password: params[:password], + role: "coach" + ) + if user.save + render json: token_response(user), status: :created + else + render json: { error: user.errors.full_messages.join(", ") }, status: :unprocessable_entity + end + end + + def login + user = User.find_by(email: params[:email]&.downcase) + if user&.authenticate(params[:password]) + render json: token_response(user), status: :ok + else + render json: { error: "Invalid credentials" }, status: :unauthorized + end + end + + def logout + render json: { message: "Logged out" } + end + + def refresh + payload = JsonWebToken.decode(params[:refresh_token] || bearer_token) + user = User.find_by(id: payload&.dig(:user_id)) + return render json: { error: "Invalid token" }, status: :unauthorized unless user + + render json: token_response(user) + end + + def me + render json: user_json(current_user) + end + + private + + def token_response(user) + { + user: user_json(user), + access_token: JsonWebToken.encode({ user_id: user.id, type: "access" }), + refresh_token: JsonWebToken.encode({ user_id: user.id, type: "refresh" }, exp: 30.days.from_now) + } + end + + def user_json(user) + { + id: user.id, + email: user.email, + name: user.name, + role: user.role + } + end + end + end +end diff --git a/backend/app/controllers/api/v1/base_controller.rb b/backend/app/controllers/api/v1/base_controller.rb new file mode 100644 index 0000000..95536eb --- /dev/null +++ b/backend/app/controllers/api/v1/base_controller.rb @@ -0,0 +1,17 @@ +module Api + module V1 + class BaseController < ApplicationController + rescue_from Teams::EntitlementError, with: :render_entitlement_error + + private + + def render_entitlement_error(error) + render json: { + error: error.message, + error_code: error.code, + billing_url: error.billing_url + }, status: :forbidden + end + end + end +end diff --git a/backend/app/controllers/api/v1/matches_controller.rb b/backend/app/controllers/api/v1/matches_controller.rb new file mode 100644 index 0000000..464ffd0 --- /dev/null +++ b/backend/app/controllers/api/v1/matches_controller.rb @@ -0,0 +1,85 @@ +module Api + module V1 + class MatchesController < BaseController + before_action :set_team, only: %i[index create] + before_action :set_match, only: %i[show update destroy] + + def index + matches = @team.matches + .includes(:team, :stream_sessions) + .order(scheduled_at: :desc) + render json: matches.map { |m| match_json(m) } + end + + def create + match = @team.matches.create!(match_params) + render json: match_json(match), status: :created + end + + def show + render json: match_json(@match, detail: true) + end + + def update + @match.update!(match_params) + render json: match_json(@match) + end + + def destroy + active = active_session_for(@match) + if active&.resumable? + return render json: { + error: "Chiudi la diretta prima di eliminare questa partita" + }, status: :unprocessable_entity + end + + @match.destroy! + head :no_content + end + + private + + def set_team + @team = current_user.teams.find(params[:team_id]) + end + + def set_match + @match = Match.joins(:team).merge(current_user.teams).find(params[:id]) + end + + def match_params + params.require(:match).permit( + :opponent_name, :location, :scheduled_at, :sport, :sets_to_win, + :category, :phase, roster_numbers: [], + scoring_rules: %i[points_per_set points_deciding_set min_point_lead] + ) + end + + def match_json(match, detail: false) + active = active_session_for(match) + { + id: match.id, + team_id: match.team_id, + team_name: match.team.name, + opponent_name: match.opponent_name, + location: match.location, + scheduled_at: match.scheduled_at, + sport: match.sport, + sets_to_win: match.sets_to_win, + scoring_rules: match.scoring_rules.presence, + roster_numbers: match.roster_numbers, + category: match.category, + phase: match.phase, + active_session_id: active&.id, + active_session_status: active&.status + } + end + + def active_session_for(match) + match.stream_sessions + .order(created_at: :desc) + .find { |s| !%w[ended error].include?(s.status) } + end + end + end +end diff --git a/backend/app/controllers/api/v1/stream_sessions_controller.rb b/backend/app/controllers/api/v1/stream_sessions_controller.rb new file mode 100644 index 0000000..2c191e9 --- /dev/null +++ b/backend/app/controllers/api/v1/stream_sessions_controller.rb @@ -0,0 +1,146 @@ +module Api + module V1 + class StreamSessionsController < BaseController + before_action :set_session, except: :create + + def create + match = Match.joins(:team).merge(current_user.teams).find(params[:match_id]) + session = Sessions::Create.new(user: current_user, match: match, params: session_params).call + render json: session_json(session), status: :created + end + + def show + render json: session_json(@session, detail: true) + end + + def start + Sessions::Start.new(@session).call + render json: session_json(@session) + end + + def stop + Sessions::Stop.new(@session).call + render json: session_json(@session) + end + + def pause + Sessions::Pause.new(@session).call + render json: session_json(@session) + end + + def events + events = @session.stream_events.recent.limit(100) + render json: events.map { |e| event_json(e) } + end + + def telemetry + role = params.require(:device_role) + state = @session.device_states.find_or_initialize_by(device_role: role) + state.update!( + battery_level: params[:battery_level], + network_type: params[:network_type], + signal_strength: params[:signal_strength], + current_bitrate: params[:current_bitrate], + target_bitrate: params[:target_bitrate], + fps: params[:fps], + last_seen_at: Time.current + ) + SessionChannel.broadcast_message(@session, state.as_cable_payload) + head :no_content + end + + def pairing_token + token = SecureRandom.urlsafe_base64(24) + @session.update!( + pairing_token_digest: Digest::SHA256.hexdigest(token), + pairing_token_expires_at: 15.minutes.from_now + ) + render json: { + pairing_token: token, + expires_at: @session.pairing_token_expires_at, + qr_payload: { + session_id: @session.id, + pairing_token: token, + api_url: request.base_url + } + } + end + + def claim_pairing + token = params.require(:pairing_token) + digest = Digest::SHA256.hexdigest(token) + unless @session.pairing_token_digest == digest && + @session.pairing_token_expires_at&.future? + return render json: { error: "Invalid or expired token" }, status: :unauthorized + end + + @session.device_states.find_or_create_by!(device_role: params[:device_role] || "controller") + render json: session_json(@session) + end + + def network_test + @session.stream_events.create!( + event_type: "network_test", + metadata: params.permit(:download_mbps, :upload_mbps, :latency_ms, :network_type).to_h, + occurred_at: Time.current + ) + ready = params[:upload_mbps].to_f >= (@session.target_bitrate / 1_000_000.0 * 0.8) + render json: { ready: ready, target_upload_mbps: @session.target_bitrate / 1_000_000.0 } + end + + def youtube_stats + count = if @session.platform == "youtube" && @session.youtube_broadcast_id.present? + Youtube::BroadcastService.new(@session.match.team) + .viewer_count(@session.youtube_broadcast_id) + else + 0 + end + render json: { concurrent_viewers: count, live: @session.live? } + end + + private + + def set_session + @session = StreamSession.joins(match: :team) + .merge(current_user.teams) + .find(params[:id]) + end + + def session_params + params.permit(:platform, :privacy_status, :quality_preset, :target_bitrate, :target_fps) + end + + def session_json(session, detail: false) + data = { + id: session.id, + match_id: session.match_id, + status: session.status, + platform: session.platform, + rtmp_ingest_url: session.rtmp_ingest_url, + hls_playback_url: session.hls_playback_url, + watch_page_url: session.watch_page_url, + youtube_broadcast_id: session.youtube_broadcast_id, + privacy_status: session.privacy_status, + quality_preset: session.quality_preset, + target_bitrate: session.target_bitrate, + started_at: session.started_at, + disconnection_count: session.disconnection_count + } + if detail + data[:score] = session.score_state&.as_cable_payload + data[:devices] = session.device_states.map(&:as_cable_payload) + end + data + end + + def event_json(event) + { + id: event.id, + event_type: event.event_type, + metadata: event.metadata, + occurred_at: event.occurred_at + } + end + end + end +end diff --git a/backend/app/controllers/api/v1/teams_controller.rb b/backend/app/controllers/api/v1/teams_controller.rb new file mode 100644 index 0000000..656d4e6 --- /dev/null +++ b/backend/app/controllers/api/v1/teams_controller.rb @@ -0,0 +1,122 @@ +module Api + module V1 + class TeamsController < BaseController + def index + teams = current_user.teams.includes(:matches) + render json: teams.map { |t| team_json(t) } + end + + def show + team = current_user.teams.find(params[:id]) + render json: team_json(team, detail: true) + end + + def create + if current_user.user_teams.where(role: "owner").count >= 1 + return render json: { + error: "Puoi gestire una sola squadra. Usa il sito per inviti o contattaci." + }, status: :unprocessable_entity + end + + team = Team.create!(team_params) + UserTeam.create!(user: current_user, team: team, role: "owner") + Billing::AssignPlan.call(team: team, plan_slug: "free") + render json: team_json(team), status: :created + end + + def recordings + team = current_user.teams.find(params[:id]) + unless team.entitlements.can_access_recordings? + return render json: { + error: "Archivio gare disponibile con Premium Light o Full", + error_code: "premium_required", + billing_url: team.entitlements.billing_url + }, status: :forbidden + end + + recs = team.recordings.ready.includes(stream_session: :match).order(created_at: :desc).limit(50) + render json: recs.map { |r| recording_json(r) } + end + + def update + team = current_user.teams.find(params[:id]) + team.update!(team_params) + render json: team_json(team) + end + + def add_member + team = current_user.teams.find(params[:id]) + team.entitlements.assert_can_invite! + member = User.find(params[:user_id]) + UserTeam.find_or_create_by!(user: member, team: team) { |ut| ut.role = "member" } + head :no_content + end + + def remove_member + team = current_user.teams.find(params[:id]) + ut = team.user_teams.find_by!(user_id: params[:user_id], role: "member") + ut.destroy! + head :no_content + end + + private + + def team_params + params.require(:team).permit(:name, :sport, :logo_url) + end + + def team_json(team, detail: false) + ent = team.entitlements + data = { + id: team.id, + name: team.name, + sport: team.sport, + logo_url: team.logo_url, + youtube_connected: team.youtube_credential.present?, + plan_slug: ent.plan.slug, + plan_name: ent.plan.name, + premium_active: ent.premium_active?, + premium_full: ent.premium_full?, + subscription_status: ent.subscription.status, + features: ent.plan.features, + billing_url: ent.billing_url, + staff_manage_url: ent.staff_manage_url, + max_staff: ent.max_staff, + max_staff_transmission: ent.max_staff_transmission, + max_staff_regia: ent.max_staff_regia, + staff_used: ent.staff_count, + staff_transmission_used: ent.staff_count_for("transmission"), + staff_regia_used: ent.staff_count_for("regia"), + concurrent_streams_used: ent.concurrent_streams_used, + concurrent_streams_limit: ent.concurrent_streams_limit, + recordings_enabled: ent.can_access_recordings?, + phone_download_enabled: ent.phone_download_enabled?, + youtube_enabled: ent.youtube_enabled?, + youtube_mode: ent.plan.youtube_mode + } + if detail + data[:members] = team.user_teams.includes(:user).where.not(role: "owner").map do |ut| + { id: ut.user.id, name: ut.user.name, email: ut.user.email, role: ut.role } + end + end + data + end + + def recording_json(recording) + session = recording.stream_session + match = session.match + { + id: recording.id, + session_id: session.id, + match_id: match.id, + opponent_name: match.opponent_name, + team_name: match.team.name, + ended_at: session.ended_at, + replay_url: recording.replay_url, + download_url: recording.replay_url, + expires_at: recording.expires_at + } + end + end + end +end diff --git a/backend/app/controllers/api/v1/youtube_controller.rb b/backend/app/controllers/api/v1/youtube_controller.rb new file mode 100644 index 0000000..6268701 --- /dev/null +++ b/backend/app/controllers/api/v1/youtube_controller.rb @@ -0,0 +1,26 @@ +module Api + module V1 + class YoutubeController < BaseController + def authorize + team = current_user.teams.find(params[:team_id]) + team.entitlements.assert_can_connect_youtube! + url = Youtube::OauthUrl.build(team_id: team.id, redirect_uri: ENV.fetch("YOUTUBE_REDIRECT_URI")) + render json: { authorization_url: url } + end + + def callback + team = current_user.teams.find(params[:state]) + tokens = Youtube::OauthExchange.call(params[:code]) + cred = team.youtube_credential || team.build_youtube_credential + cred.update!( + access_token: tokens[:access_token], + refresh_token: tokens[:refresh_token], + expires_at: Time.current + tokens[:expires_in].seconds, + channel_id: tokens[:channel_id], + channel_title: tokens[:channel_title] + ) + redirect_to "/admin/teams/#{team.id}?youtube=connected" + end + end + end +end diff --git a/backend/app/controllers/application_controller.rb b/backend/app/controllers/application_controller.rb new file mode 100644 index 0000000..029725c --- /dev/null +++ b/backend/app/controllers/application_controller.rb @@ -0,0 +1,25 @@ +class ApplicationController < ActionController::API + include ActionController::HttpAuthentication::Token::ControllerMethods + + before_action :authenticate_request! + + attr_reader :current_user + + private + + def authenticate_request! + token = bearer_token + payload = JsonWebToken.decode(token) + @current_user = User.find_by(id: payload[:user_id]) if payload + render json: { error: "Unauthorized" }, status: :unauthorized unless @current_user + end + + def bearer_token + auth = request.headers["Authorization"].to_s + auth.start_with?("Bearer ") ? auth.split(" ", 2).last : nil + end + + def skip_auth? + false + end +end diff --git a/backend/app/controllers/concerns/.keep b/backend/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/controllers/hls_proxy_controller.rb b/backend/app/controllers/hls_proxy_controller.rb new file mode 100644 index 0000000..65d8dca --- /dev/null +++ b/backend/app/controllers/hls_proxy_controller.rb @@ -0,0 +1,44 @@ +class HlsProxyController < ActionController::Base + # Proxy pubblico verso MediaMTX HLS (NPM inoltra tutto a Rails :3000). + def show + upstream_path = params[:path].to_s + return head :not_found if upstream_path.blank? + + upstream = "#{MatchLiveTv.mediamtx_hls_url}/#{upstream_path}" + upstream = "#{upstream}?#{request.query_string}" if request.query_string.present? + cookie = request.headers["Cookie"].presence || "cookieCheck=1" + response = hls_conn.get(upstream) { |req| req.headers["Cookie"] = cookie } + + if response.status == 302 + location = response.headers["location"].to_s + follow_url = if location.start_with?("http") + location + else + "#{MatchLiveTv.mediamtx_hls_url.sub(%r{/$}, "")}#{location}" + end + set_cookie = response.headers["set-cookie"].to_s + cookie = set_cookie.presence || cookie + response = hls_conn.get(follow_url) { |req| req.headers["Cookie"] = cookie } + end + + response.headers.each do |key, value| + next if %w[transfer-encoding connection].include?(key.downcase) + + headers[key] = value + end + render body: response.body, status: response.status + rescue Faraday::Error => e + Rails.logger.warn("[HlsProxy] #{upstream_path}: #{e.message}") + head :bad_gateway + end + + private + + def hls_conn + @hls_conn ||= Faraday.new do |f| + f.adapter Faraday.default_adapter + f.options.timeout = 15 + f.options.open_timeout = 5 + end + end +end diff --git a/backend/app/controllers/public/invitations_controller.rb b/backend/app/controllers/public/invitations_controller.rb new file mode 100644 index 0000000..5710a1e --- /dev/null +++ b/backend/app/controllers/public/invitations_controller.rb @@ -0,0 +1,28 @@ +module Public + class InvitationsController < WebBaseController + def show + @token = params[:token] + @invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(@token.to_s)) + unless @invitation + redirect_to public_pricing_path, alert: "Invito non valido o scaduto" + end + end + + def accept + invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(params[:token].to_s)) + return redirect_to public_pricing_path, alert: "Invito non valido" unless invitation + + if logged_in? + if current_user.email.downcase != invitation.email.downcase + redirect_to public_pricing_path, alert: "Questo invito è per #{invitation.email}" + return + end + invitation.accept!(current_user) + redirect_to public_team_dashboard_path(invitation.team), notice: "Sei entrato nella squadra!" + else + session[:pending_invite_token] = params[:token] + redirect_to public_signup_path, notice: "Registrati con #{invitation.email} per accettare l'invito" + end + end + end +end diff --git a/backend/app/controllers/public/live_controller.rb b/backend/app/controllers/public/live_controller.rb new file mode 100644 index 0000000..0a1b85b --- /dev/null +++ b/backend/app/controllers/public/live_controller.rb @@ -0,0 +1,47 @@ +module Public + class LiveController < SiteBaseController + layout "marketing_live" + + ACTIVE_STATUSES = %w[live connecting reconnecting].freeze + + def index + @query = params[:q].to_s.strip + @sessions = StreamSession + .broadcasting + .includes(:score_state, match: :team) + .search_by_team_or_opponent(@query) + .order(Arel.sql("started_at DESC NULLS LAST"), created_at: :desc) + + @online_paths = Mediamtx::Client.new.online_path_names + end + + def show + @session = StreamSession.includes(match: :team).find(params[:id]) + @match = @session.match + @stream_closed = @session.terminal? + @on_air = !@stream_closed && mediamtx_online?(@session) + end + + def status + session = StreamSession.includes(match: :team).find(params[:id]) + closed = session.terminal? + render json: { + status: session.status, + stream_closed: closed, + live: !closed && (session.live? || mediamtx_online?(session)), + on_air: !closed && mediamtx_online?(session), + ended_at: session.ended_at, + home_name: session.match.team.name, + away_name: session.match.opponent_name, + score: session.score_state&.as_cable_payload + } + end + + private + + def mediamtx_online?(session) + @online_paths_cache ||= Mediamtx::Client.new.online_path_names + @online_paths_cache.include?(session.mediamtx_path_name) + end + end +end diff --git a/backend/app/controllers/public/pages_controller.rb b/backend/app/controllers/public/pages_controller.rb new file mode 100644 index 0000000..7e919b2 --- /dev/null +++ b/backend/app/controllers/public/pages_controller.rb @@ -0,0 +1,19 @@ +module Public + class PagesController < WebBaseController + def home + end + + def features + end + + def pricing + @plans = Plan.ordered + end + + def privacy + end + + def terms + end + end +end diff --git a/backend/app/controllers/public/registrations_controller.rb b/backend/app/controllers/public/registrations_controller.rb new file mode 100644 index 0000000..8e23d27 --- /dev/null +++ b/backend/app/controllers/public/registrations_controller.rb @@ -0,0 +1,33 @@ +module Public + class RegistrationsController < WebBaseController + def new + redirect_to public_team_dashboard_path(current_user.teams.first) if logged_in? && current_user.teams.any? + @user = User.new + end + + def create + @user = User.new(user_params.merge(role: "coach")) + if @user.save + session[:user_id] = @user.id + if session[:pending_invite_token].present? + token = session.delete(:pending_invite_token) + invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(token)) + if invitation && invitation.email.downcase == @user.email.downcase + invitation.accept!(@user) + return redirect_to public_team_dashboard_path(invitation.team), notice: "Benvenuto nella squadra!" + end + end + redirect_to new_public_team_path, notice: "Account creato. Ora crea la tua squadra." + else + flash.now[:alert] = @user.errors.full_messages.join(", ") + render :new, status: :unprocessable_entity + end + end + + private + + def user_params + params.require(:user).permit(:name, :email, :password, :password_confirmation) + end + end +end diff --git a/backend/app/controllers/public/replay_controller.rb b/backend/app/controllers/public/replay_controller.rb new file mode 100644 index 0000000..bc4c252 --- /dev/null +++ b/backend/app/controllers/public/replay_controller.rb @@ -0,0 +1,15 @@ +module Public + class ReplayController < SiteBaseController + layout "marketing_live" + + def show + @session = StreamSession.includes(match: :team).find(params[:id]) + @recording = Recording.ready.find_by(stream_session: @session) + @match = @session.match + + unless @recording + redirect_to public_live_path(@session), alert: "Replay non disponibile" + end + end + end +end diff --git a/backend/app/controllers/public/sessions_controller.rb b/backend/app/controllers/public/sessions_controller.rb new file mode 100644 index 0000000..3759be3 --- /dev/null +++ b/backend/app/controllers/public/sessions_controller.rb @@ -0,0 +1,24 @@ +module Public + class SessionsController < WebBaseController + def new + redirect_to public_team_dashboard_path(current_user.teams.first) if logged_in? && current_user.teams.any? + end + + def create + user = User.find_by(email: params[:email]&.downcase) + if user&.authenticate(params[:password]) + session[:user_id] = user.id + dest = user.teams.first ? public_team_dashboard_path(user.teams.first) : new_public_team_path + redirect_to dest, notice: "Bentornato!" + else + flash.now[:alert] = "Email o password non validi" + render :new, status: :unauthorized + end + end + + def destroy + reset_session + redirect_to public_pricing_path, notice: "Disconnesso" + end + end +end diff --git a/backend/app/controllers/public/site_base_controller.rb b/backend/app/controllers/public/site_base_controller.rb new file mode 100644 index 0000000..bd7ead4 --- /dev/null +++ b/backend/app/controllers/public/site_base_controller.rb @@ -0,0 +1,19 @@ +module Public + class SiteBaseController < ActionController::Base + layout "marketing" + + helper_method :current_user, :logged_in? + + private + + def current_user + return @current_user if defined?(@current_user) + + @current_user = User.find_by(id: session[:user_id]) if session[:user_id] + end + + def logged_in? + current_user.present? + end + end +end diff --git a/backend/app/controllers/public/teams_controller.rb b/backend/app/controllers/public/teams_controller.rb new file mode 100644 index 0000000..fd64d9b --- /dev/null +++ b/backend/app/controllers/public/teams_controller.rb @@ -0,0 +1,119 @@ +module Public + class TeamsController < WebBaseController + before_action :require_login! + before_action :set_team, only: %i[ + dashboard billing checkout portal invite create_invitation + remove_member destroy_invitation + ] + + def new + redirect_to public_team_dashboard_path(current_user.teams.first) if current_user.teams.any? + end + + def create + if current_user.user_teams.where(role: "owner").exists? + redirect_to public_team_dashboard_path(current_user.teams.first), alert: "Hai già una squadra" + return + end + + team = Team.create!(team_params) + UserTeam.create!(user: current_user, team: team, role: "owner") + plan = params[:plan].presence_in(%w[free premium_light premium_full]) || "free" + Billing::AssignPlan.call(team: team, plan_slug: plan) + + if plan.in?(%w[premium_light premium_full]) && MatchLiveTv.stripe_enabled? + redirect_to public_team_checkout_path(team, plan: plan) + else + redirect_to public_team_dashboard_path(team), notice: "Squadra creata con piano Free" + end + end + + def dashboard + require_team_owner!(@team) + @entitlements = @team.entitlements + @recordings = @team.recordings.ready.includes(stream_session: :match).order(created_at: :desc).limit(10) + @members = @team.user_teams.includes(:user).where.not(role: "owner") + @pending_invitations = @team.team_invitations.pending.order(created_at: :desc) + end + + def billing + require_team_owner!(@team) + @entitlements = @team.entitlements + @plans = Plan.ordered + end + + def checkout + require_team_owner!(@team) + unless MatchLiveTv.stripe_enabled? + redirect_to public_team_billing_path(@team), alert: "Pagamenti non ancora configurati sul server" + return + end + + plan_slug = params[:plan].presence_in(%w[premium_light premium_full]) || "premium_light" + url = Billing::Stripe::CheckoutSession.new(team: @team, user: current_user, plan_slug: plan_slug).url + redirect_to url, allow_other_host: true + end + + def portal + require_team_owner!(@team) + unless MatchLiveTv.stripe_enabled? + redirect_to public_team_billing_path(@team), alert: "Portale pagamenti non configurato" + return + end + + url = Billing::Stripe::CheckoutSession.new(team: @team, user: current_user).portal_url + redirect_to url, allow_other_host: true + end + + def invite + require_team_owner!(@team) + @entitlements = @team.entitlements + end + + def create_invitation + require_team_owner!(@team) + @entitlements = @team.entitlements + staff_kind = params[:staff_kind].presence_in(TeamInvitation::STAFF_KINDS) || "transmission" + @entitlements.assert_can_invite!(staff_kind: staff_kind) + + email = params[:email]&.downcase&.strip + token = TeamInvitation.generate_token + @team.team_invitations.create!( + email: email, + token_digest: Digest::SHA256.hexdigest(token), + role: "member", + staff_kind: staff_kind, + expires_at: 7.days.from_now + ) + @invite_url = join_public_invitation_url(token: token) + flash.now[:notice] = "Link invito generato (valido 7 giorni)" + render :invite + rescue Teams::EntitlementError => e + redirect_to public_team_invite_path(@team), alert: e.message + end + + def remove_member + require_team_owner!(@team) + ut = @team.user_teams.find_by!(user_id: params[:user_id], role: "member") + ut.destroy! + redirect_to public_team_dashboard_path(@team), notice: "Accesso revocato" + end + + def destroy_invitation + require_team_owner!(@team) + inv = @team.team_invitations.pending.find(params[:invitation_id]) + inv.destroy! + redirect_to public_team_dashboard_path(@team), notice: "Invito annullato" + end + + private + + def set_team + @team = current_user.teams.find(params[:id]) + end + + def team_params + params.require(:team).permit(:name, :sport, :logo_url) + end + end +end diff --git a/backend/app/controllers/public/web_base_controller.rb b/backend/app/controllers/public/web_base_controller.rb new file mode 100644 index 0000000..ccd966d --- /dev/null +++ b/backend/app/controllers/public/web_base_controller.rb @@ -0,0 +1,18 @@ +module Public + class WebBaseController < SiteBaseController + protect_from_forgery with: :exception + + private + + def require_login! + return if logged_in? + + redirect_to public_login_path, alert: "Accedi per continuare" + end + + def require_team_owner!(team) + membership = current_user.user_teams.find_by(team: team) + redirect_to public_prezzi_path, alert: "Accesso non consentito" unless membership&.role == "owner" + end + end +end diff --git a/backend/app/controllers/webhooks/mediamtx_controller.rb b/backend/app/controllers/webhooks/mediamtx_controller.rb new file mode 100644 index 0000000..a89fdea --- /dev/null +++ b/backend/app/controllers/webhooks/mediamtx_controller.rb @@ -0,0 +1,53 @@ +module Webhooks + class MediamtxController < ApplicationController + skip_before_action :authenticate_request! + + before_action :verify_signature! + + def connect + handle_event("connect") + end + + def disconnect + handle_event("disconnect") + end + + def ready + handle_event("ready") + end + + def validate_publish + session_id = params[:session_id] || extract_session_id(params[:path]) + token = params[:token] + session = StreamSession.find_by(id: session_id) + + valid = session && session.publish_token == token && !session.ended? + render json: { valid: valid }, status: valid ? :ok : :forbidden + end + + private + + def handle_event(event) + session_id = params[:session_id].presence || extract_session_id(params[:path]) + Webhooks::MediamtxHandler.new( + event: event, + session_id: session_id, + metadata: params.permit!.to_h.except("controller", "action") + ).call + head :ok + end + + def extract_session_id(path) + path.to_s[%r{match_([0-9a-f-]+)}, 1] + end + + def verify_signature! + signature = request.headers["X-MediaMTX-Signature"] + payload = request.raw_post.presence || request.query_string + return if Webhooks::Signature.valid?(payload, signature) + return if Rails.env.development? && signature.blank? + + head :unauthorized + end + end +end diff --git a/backend/app/controllers/webhooks/stripe_controller.rb b/backend/app/controllers/webhooks/stripe_controller.rb new file mode 100644 index 0000000..56fd09a --- /dev/null +++ b/backend/app/controllers/webhooks/stripe_controller.rb @@ -0,0 +1,32 @@ +module Webhooks + class StripeController < ActionController::API + def create + payload = request.body.read + sig_header = request.env["HTTP_STRIPE_SIGNATURE"] + + unless MatchLiveTv.stripe_webhook_secret.present? + Rails.logger.info("[Stripe webhook] ignored — STRIPE_WEBHOOK_SECRET not set") + return head :ok + end + + event = ::Stripe::Webhook.construct_event( + payload, sig_header, MatchLiveTv.stripe_webhook_secret + ) + + Billing::Stripe::WebhookHandler.call(event) + + head :ok + rescue JSON::ParserError, ::Stripe::SignatureVerificationError => e + Rails.logger.warn("[Stripe webhook] #{e.message}") + head :bad_request + end + + private + + def stripe_object_from_hash(hash) + OpenStruct.new(hash.transform_keys(&:to_s).transform_values do |v| + v.is_a?(Hash) ? stripe_object_from_hash(v) : v + end) + end + end +end diff --git a/backend/app/helpers/public/live_helper.rb b/backend/app/helpers/public/live_helper.rb new file mode 100644 index 0000000..8ad61e0 --- /dev/null +++ b/backend/app/helpers/public/live_helper.rb @@ -0,0 +1,35 @@ +module Public + module LiveHelper + def live_score_sets_label(score_state) + return nil unless score_state + + "Set #{score_state.current_set} · Set vinti #{score_state.home_sets}-#{score_state.away_sets}" + end + + def live_score_partials_label(score_state) + return nil unless score_state + + partials = Array(score_state.set_partials) + return nil if partials.empty? + + partials.map { |p| format_set_partial_entry(p) }.join(" · ") + end + + def live_score_points_label(match, score_state) + return "—" unless score_state + + "#{match.team.name} #{score_state.home_points} - #{score_state.away_points} #{match.opponent_name}" + end + + private + + def format_set_partial_entry(partial) + data = partial.respond_to?(:with_indifferent_access) ? partial.with_indifferent_access : partial + set_no = data[:set] || data["set"] + home = data[:home] || data["home"] + away = data[:away] || data["away"] + label = set_no.present? ? "Set #{set_no}" : "Set" + "#{label} #{home}-#{away}" + end + end +end diff --git a/backend/app/jobs/application_job.rb b/backend/app/jobs/application_job.rb new file mode 100644 index 0000000..d394c3d --- /dev/null +++ b/backend/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/backend/app/jobs/cleanup_expired_sessions_job.rb b/backend/app/jobs/cleanup_expired_sessions_job.rb new file mode 100644 index 0000000..b7d521f --- /dev/null +++ b/backend/app/jobs/cleanup_expired_sessions_job.rb @@ -0,0 +1,12 @@ +class CleanupExpiredSessionsJob + include Sidekiq::Job + + def perform + StreamSession.where(status: %w[connecting reconnecting]) + .where("updated_at < ?", 6.hours.ago) + .find_each do |session| + session.fail! if session.may_fail? + Mediamtx::Client.new.delete_path(session) + end + end +end diff --git a/backend/app/jobs/disconnection_timeout_job.rb b/backend/app/jobs/disconnection_timeout_job.rb new file mode 100644 index 0000000..1467bc5 --- /dev/null +++ b/backend/app/jobs/disconnection_timeout_job.rb @@ -0,0 +1,22 @@ +class DisconnectionTimeoutJob + include Sidekiq::Job + + def perform(session_id) + session = StreamSession.find_by(id: session_id) + return unless session&.reconnecting? + + session.finish! if session.may_finish? + session.update!(timeout_job_id: nil) + session.stream_events.create!( + event_type: "error", + metadata: { reason: "reconnect_timeout" }, + occurred_at: Time.current + ) + SessionChannel.broadcast_message(session, { + type: "stream_event", + event: "timeout_ended", + message: "Session ended after reconnect timeout" + }) + Sessions::Stop.new(session).call + end +end diff --git a/backend/app/jobs/notify_stream_health_job.rb b/backend/app/jobs/notify_stream_health_job.rb new file mode 100644 index 0000000..00320a5 --- /dev/null +++ b/backend/app/jobs/notify_stream_health_job.rb @@ -0,0 +1,17 @@ +class NotifyStreamHealthJob + include Sidekiq::Job + + def perform(session_id) + session = StreamSession.find_by(id: session_id) + return unless session&.live? + + camera = session.device_states.find_by(device_role: "camera") + return unless camera&.current_bitrate && camera.current_bitrate < session.target_bitrate * 0.5 + + SessionChannel.broadcast_message(session, { + type: "stream_event", + event: "quality_degraded", + bitrate: camera.current_bitrate + }) + end +end diff --git a/backend/app/jobs/stream_analytics_job.rb b/backend/app/jobs/stream_analytics_job.rb new file mode 100644 index 0000000..6b4e164 --- /dev/null +++ b/backend/app/jobs/stream_analytics_job.rb @@ -0,0 +1,16 @@ +class StreamAnalyticsJob + include Sidekiq::Job + + def perform(session_id) + session = StreamSession.find_by(id: session_id) + return unless session&.ended? + + duration = session.total_duration_secs + disconnects = session.disconnection_count + session.stream_events.create!( + event_type: "quality_changed", + metadata: { analytics: true, duration: duration, disconnects: disconnects }, + occurred_at: Time.current + ) + end +end diff --git a/backend/app/mailers/application_mailer.rb b/backend/app/mailers/application_mailer.rb new file mode 100644 index 0000000..3c34c81 --- /dev/null +++ b/backend/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/backend/app/models/application_record.rb b/backend/app/models/application_record.rb new file mode 100644 index 0000000..b63caeb --- /dev/null +++ b/backend/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/backend/app/models/concerns/.keep b/backend/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/models/device_state.rb b/backend/app/models/device_state.rb new file mode 100644 index 0000000..922ec9c --- /dev/null +++ b/backend/app/models/device_state.rb @@ -0,0 +1,19 @@ +class DeviceState < ApplicationRecord + ROLES = %w[camera controller].freeze + + belongs_to :stream_session + + validates :device_role, inclusion: { in: ROLES } + validates :device_role, uniqueness: { scope: :stream_session_id } + + def as_cable_payload + { + type: "device_state", + battery: battery_level, + network: network_type, + bitrate: current_bitrate, + fps: fps, + status: stream_session.status + } + end +end diff --git a/backend/app/models/match.rb b/backend/app/models/match.rb new file mode 100644 index 0000000..cc55889 --- /dev/null +++ b/backend/app/models/match.rb @@ -0,0 +1,7 @@ +class Match < ApplicationRecord + belongs_to :team + has_many :stream_sessions, dependent: :destroy + + validates :opponent_name, presence: true + validates :sets_to_win, numericality: { greater_than: 0, less_than: 6 } +end diff --git a/backend/app/models/plan.rb b/backend/app/models/plan.rb new file mode 100644 index 0000000..00eb601 --- /dev/null +++ b/backend/app/models/plan.rb @@ -0,0 +1,86 @@ +class Plan < ApplicationRecord + SLUGS = %w[free premium_light premium_full].freeze + EXTERNAL_PLATFORMS = %w[youtube facebook twitch].freeze + PREMIUM_SLUGS = %w[premium_light premium_full].freeze + + has_many :subscriptions, dependent: :restrict_with_error + + validates :slug, presence: true, uniqueness: true, inclusion: { in: SLUGS } + validates :name, presence: true + + scope :ordered, -> { + order(Arel.sql("CASE slug WHEN 'free' THEN 0 WHEN 'premium_light' THEN 1 ELSE 2 END")) + } + + def self.[](slug) + find_by!(slug: slug.to_s) + end + + def allows_platform?(platform) + return false if platform.to_s == "youtube" && !youtube_enabled? + + Array(features["platforms"]).map(&:to_s).include?(platform.to_s) + end + + def feature(key) + features[key.to_s] + end + + def recordings_enabled? + feature("recordings_enabled") == true + end + + def recording_days + feature("recording_days").to_i + end + + def max_staff_transmission + val = feature("max_staff_transmission") + return legacy_max_staff if val.nil? && feature("max_staff").present? + + val.nil? ? nil : val.to_i + end + + def max_staff_regia + val = feature("max_staff_regia") + return legacy_max_staff if val.nil? && feature("max_staff").present? + + val.nil? ? nil : val.to_i + end + + def max_staff + t = max_staff_transmission + r = max_staff_regia + return legacy_max_staff if legacy_max_staff.positive? && t.nil? && r.nil? && feature("max_staff").present? + return nil if t.nil? && r.nil? + + (t || 0) + (r || 0) + end + + def youtube_mode + feature("youtube_mode")&.to_s + end + + def concurrent_streams_limit + val = feature("concurrent_streams_limit") + val.nil? ? nil : val.to_i + end + + def phone_download_enabled? + feature("phone_download_enabled") == true + end + + def youtube_enabled? + feature("youtube_enabled") == true + end + + def premium? + slug.in?(PREMIUM_SLUGS) + end + + private + + def legacy_max_staff + feature("max_staff").to_i + end +end diff --git a/backend/app/models/recording.rb b/backend/app/models/recording.rb new file mode 100644 index 0000000..edd5000 --- /dev/null +++ b/backend/app/models/recording.rb @@ -0,0 +1,21 @@ +class Recording < ApplicationRecord + STATUSES = %w[processing ready expired].freeze + + belongs_to :stream_session + belongs_to :team + + validates :status, inclusion: { in: STATUSES } + + scope :ready, -> { where(status: "ready").where("expires_at IS NULL OR expires_at > ?", Time.current) } + scope :for_team, ->(team) { where(team: team) } + + def replay_url + return nil unless ready? && stream_session_id.present? + + "#{MatchLiveTv.app_public_url.chomp('/')}/replay/#{stream_session_id}" + end + + def ready? + status == "ready" && (expires_at.nil? || expires_at.future?) + end +end diff --git a/backend/app/models/score_state.rb b/backend/app/models/score_state.rb new file mode 100644 index 0000000..425a512 --- /dev/null +++ b/backend/app/models/score_state.rb @@ -0,0 +1,17 @@ +class ScoreState < ApplicationRecord + belongs_to :stream_session + + def as_cable_payload + { + type: "score_update", + home_sets: home_sets, + away_sets: away_sets, + home_points: home_points, + away_points: away_points, + current_set: current_set, + set_partials: set_partials || [], + timeout_home: timeout_home, + timeout_away: timeout_away + } + end +end diff --git a/backend/app/models/stream_event.rb b/backend/app/models/stream_event.rb new file mode 100644 index 0000000..4d8cdf5 --- /dev/null +++ b/backend/app/models/stream_event.rb @@ -0,0 +1,13 @@ +class StreamEvent < ApplicationRecord + EVENT_TYPES = %w[ + connected disconnected reconnected quality_changed error paused resumed + started ended network_test pairing + ].freeze + + belongs_to :stream_session + + validates :event_type, inclusion: { in: EVENT_TYPES } + validates :occurred_at, presence: true + + scope :recent, -> { order(occurred_at: :desc) } +end diff --git a/backend/app/models/stream_session.rb b/backend/app/models/stream_session.rb new file mode 100644 index 0000000..6b17f40 --- /dev/null +++ b/backend/app/models/stream_session.rb @@ -0,0 +1,150 @@ +class StreamSession < ApplicationRecord + include AASM + + PLATFORMS = %w[matchlivetv youtube facebook twitch].freeze + STATUSES = %w[idle connecting live reconnecting paused ended error].freeze + + belongs_to :match + belongs_to :user + has_many :stream_events, dependent: :destroy + has_one :score_state, dependent: :destroy + has_many :device_states, dependent: :destroy + + validates :platform, inclusion: { in: PLATFORMS } + validates :status, inclusion: { in: STATUSES } + + before_validation :ensure_publish_token, on: :create + + scope :broadcasting, -> { where(status: %w[live connecting reconnecting]) } + scope :search_by_team_or_opponent, lambda { |query| + q = query.to_s.strip + return all if q.blank? + + term = "%#{sanitize_sql_like(q)}%" + joins(match: :team).where( + "teams.name ILIKE :term OR matches.opponent_name ILIKE :term", + term: term + ) + } + + aasm column: :status do + state :idle, initial: true + state :connecting, :live, :reconnecting, :paused, :ended, :error + + event :begin_connect do + transitions from: %i[idle paused], to: :connecting + end + + event :go_live do + transitions from: %i[connecting reconnecting], to: :live + after { update!(started_at: Time.current) if started_at.nil? } + end + + event :lose_connection do + transitions from: :live, to: :reconnecting + after { increment!(:disconnection_count) } + end + + event :reconnect do + transitions from: :reconnecting, to: :live + end + + event :pause do + transitions from: :live, to: :paused + end + + event :resume do + transitions from: :paused, to: :live + end + + event :finish do + transitions from: %i[live reconnecting paused connecting], to: :ended + after { close_session! } + end + + event :fail do + transitions from: %i[connecting reconnecting idle], to: :error + end + end + + def rtmp_ingest_url + # RootEncoder richiede rtmp://host:port/app/stream (due segmenti). + # MediaMTX path = live/match_{uuid} (no ?token= nel path). + "#{MatchLiveTv.mediamtx_rtmp_url}/#{mediamtx_path_name}" + end + + def mediamtx_path_name + "live/match_#{id}" + end + + def matchlivetv_platform? + platform == "matchlivetv" + end + + def hls_playback_url + base = MatchLiveTv.hls_public_url.chomp("/") + "#{base}/#{mediamtx_path_name}/index.m3u8" + end + + def watch_page_url + "#{MatchLiveTv.app_public_url.chomp('/')}/live/#{id}" + end + + def terminal? + status.in?(%w[ended error]) + end + + def resumable? + status.in?(%w[connecting live reconnecting paused]) + end + + def end_stream! + return if terminal? + + if may_finish? + finish! + else + update!(status: "ended") + record_ended_timestamps! + end + end + + def stream_key + return @stream_key if instance_variable_defined?(:@stream_key) + return nil if stream_key_encrypted.blank? + + stream_key_encryptor.decrypt_and_verify(stream_key_encrypted) + rescue ActiveSupport::MessageEncryptor::InvalidMessage + nil + end + + def stream_key=(value) + @stream_key = value + self.stream_key_encrypted = + value.present? ? stream_key_encryptor.encrypt_and_sign(value) : nil + end + + private + + def stream_key_encryptor + key = ActiveSupport::KeyGenerator.new(Rails.application.secret_key_base) + .generate_key("stream_session_stream_key", 32) + ActiveSupport::MessageEncryptor.new(key) + end + + def ensure_publish_token + self.publish_token ||= SecureRandom.urlsafe_base64(32) + end + + def record_ended_timestamps! + now = Time.current + update!(ended_at: now) if ended_at.nil? + if started_at && total_duration_secs.to_i.zero? + update!(total_duration_secs: (now - started_at).to_i) + end + end + + def close_session! + record_ended_timestamps! + end +end diff --git a/backend/app/models/subscription.rb b/backend/app/models/subscription.rb new file mode 100644 index 0000000..1ad36cf --- /dev/null +++ b/backend/app/models/subscription.rb @@ -0,0 +1,28 @@ +class Subscription < ApplicationRecord + STATUSES = %w[active trialing past_due canceled incomplete].freeze + ACTIVE_STATUSES = %w[active trialing].freeze + + belongs_to :team + belongs_to :plan + + validates :status, inclusion: { in: STATUSES } + validates :team_id, uniqueness: true + + scope :active, -> { where(status: ACTIVE_STATUSES) } + + def active? + status.in?(ACTIVE_STATUSES) + end + + def premium? + active? && plan.premium? + end + + def premium_full? + active? && plan.slug == "premium_full" + end + + def free? + plan.slug == "free" + end +end diff --git a/backend/app/models/team.rb b/backend/app/models/team.rb new file mode 100644 index 0000000..f56281b --- /dev/null +++ b/backend/app/models/team.rb @@ -0,0 +1,19 @@ +class Team < ApplicationRecord + has_many :user_teams, dependent: :destroy + has_many :users, through: :user_teams + has_many :matches, dependent: :destroy + has_one :youtube_credential, dependent: :destroy + has_one :subscription, dependent: :destroy + has_many :recordings, dependent: :destroy + has_many :team_invitations, dependent: :destroy + + validates :name, presence: true + + def entitlements + @entitlements ||= Teams::Entitlements.new(self) + end + + def owner + user_teams.find_by(role: "owner")&.user + end +end diff --git a/backend/app/models/team_invitation.rb b/backend/app/models/team_invitation.rb new file mode 100644 index 0000000..846c5b0 --- /dev/null +++ b/backend/app/models/team_invitation.rb @@ -0,0 +1,28 @@ +class TeamInvitation < ApplicationRecord + STAFF_KINDS = %w[transmission regia].freeze + + belongs_to :team + + validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } + validates :token_digest, presence: true, uniqueness: true + validates :role, inclusion: { in: UserTeam::ROLES } + validates :staff_kind, inclusion: { in: STAFF_KINDS } + + scope :pending, -> { where(accepted_at: nil).where("expires_at > ?", Time.current) } + + def self.generate_token + SecureRandom.urlsafe_base64(32) + end + + def accept!(user) + ut = UserTeam.find_or_initialize_by(user: user, team: team) + ut.role = role + ut.staff_kind = staff_kind + ut.save! + update!(accepted_at: Time.current) + end + + def expired? + expires_at.past? + end +end diff --git a/backend/app/models/user.rb b/backend/app/models/user.rb new file mode 100644 index 0000000..5d6117f --- /dev/null +++ b/backend/app/models/user.rb @@ -0,0 +1,17 @@ +class User < ApplicationRecord + ROLES = %w[admin coach parent volunteer].freeze + + has_secure_password + + has_many :user_teams, dependent: :destroy + has_many :teams, through: :user_teams + has_many :stream_sessions, dependent: :nullify + + validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP } + validates :name, presence: true + validates :role, inclusion: { in: ROLES } + + def admin? + role == "admin" + end +end diff --git a/backend/app/models/user_team.rb b/backend/app/models/user_team.rb new file mode 100644 index 0000000..1e7550c --- /dev/null +++ b/backend/app/models/user_team.rb @@ -0,0 +1,10 @@ +class UserTeam < ApplicationRecord + ROLES = %w[owner member].freeze + + belongs_to :user + belongs_to :team + + validates :role, inclusion: { in: ROLES } + validates :staff_kind, inclusion: { in: TeamInvitation::STAFF_KINDS }, allow_nil: true + validates :user_id, uniqueness: { scope: :team_id } +end diff --git a/backend/app/models/youtube_credential.rb b/backend/app/models/youtube_credential.rb new file mode 100644 index 0000000..db04251 --- /dev/null +++ b/backend/app/models/youtube_credential.rb @@ -0,0 +1,22 @@ +class YoutubeCredential < ApplicationRecord + belongs_to :team + + attr_encrypted :access_token, + key: :encryption_key, + attribute: "access_token_encrypted", + mode: :single_iv_salt + attr_encrypted :refresh_token, + key: :encryption_key, + attribute: "refresh_token_encrypted", + mode: :single_iv_salt + + def expired? + expires_at.present? && expires_at < Time.current + end + + private + + def encryption_key + Rails.application.secret_key_base[0, 32] + end +end diff --git a/backend/app/services/billing/assign_plan.rb b/backend/app/services/billing/assign_plan.rb new file mode 100644 index 0000000..0814181 --- /dev/null +++ b/backend/app/services/billing/assign_plan.rb @@ -0,0 +1,12 @@ +module Billing + class AssignPlan + def self.call(team:, plan_slug:, status: "active", stripe_attrs: {}) + plan = Plan[plan_slug] + sub = team.subscription || team.build_subscription + attrs = { plan: plan, status: status }.merge(stripe_attrs.symbolize_keys) + sub.assign_attributes(attrs) + sub.save! + sub + end + end +end diff --git a/backend/app/services/billing/stripe/checkout_session.rb b/backend/app/services/billing/stripe/checkout_session.rb new file mode 100644 index 0000000..ef80376 --- /dev/null +++ b/backend/app/services/billing/stripe/checkout_session.rb @@ -0,0 +1,79 @@ +module Billing + module Stripe + class CheckoutSession + VALID_PLANS = %w[premium_light premium_full].freeze + + def initialize(team:, user:, plan_slug: "premium_light") + @team = team + @user = user + @plan_slug = plan_slug.to_s + end + + def url + raise "Stripe non configurato" unless MatchLiveTv.stripe_enabled? + raise "Piano non valido" unless VALID_PLANS.include?(@plan_slug) + + plan = Plan[@plan_slug] + price_id = plan.stripe_price_id.presence || stripe_price_id_for(@plan_slug) + raise "Price ID Stripe mancante per #{@plan_slug}" if price_id.blank? + + customer_id = ensure_customer_id! + session = ::Stripe::Checkout::Session.create( + mode: "subscription", + customer: customer_id, + line_items: [{ price: price_id, quantity: 1 }], + success_url: success_url, + cancel_url: cancel_url, + metadata: { team_id: @team.id, user_id: @user.id, plan_slug: @plan_slug }, + subscription_data: { metadata: { team_id: @team.id, plan_slug: @plan_slug } } + ) + session.url + end + + def portal_url + raise "Stripe non configurato" unless MatchLiveTv.stripe_enabled? + + customer_id = ensure_customer_id! + session = ::Stripe::BillingPortal::Session.create( + customer: customer_id, + return_url: dashboard_url + ) + session.url + end + + private + + def stripe_price_id_for(slug) + case slug + when "premium_light" then MatchLiveTv.stripe_premium_light_price_id + when "premium_full" then MatchLiveTv.stripe_premium_full_price_id + end + end + + def ensure_customer_id! + sub = @team.subscription || @team.build_subscription(plan: Plan["free"], status: "active") + return sub.stripe_customer_id if sub.stripe_customer_id.present? + + customer = ::Stripe::Customer.create( + email: @user.email, + name: @team.name, + metadata: { team_id: @team.id } + ) + sub.update!(stripe_customer_id: customer.id) + customer.id + end + + def success_url + "#{dashboard_url}?checkout=success" + end + + def cancel_url + "#{dashboard_url}?checkout=canceled" + end + + def dashboard_url + "#{MatchLiveTv.app_public_url.chomp('/')}/teams/#{@team.id}/dashboard" + end + end + end +end diff --git a/backend/app/services/billing/stripe/webhook_handler.rb b/backend/app/services/billing/stripe/webhook_handler.rb new file mode 100644 index 0000000..35440f5 --- /dev/null +++ b/backend/app/services/billing/stripe/webhook_handler.rb @@ -0,0 +1,131 @@ +module Billing + module Stripe + class WebhookHandler + def self.call(event) + new(event).call + end + + def initialize(event) + @event = event + end + + def call + case @event.type + when "checkout.session.completed" + handle_checkout_completed(@event.data.object) + when "customer.subscription.updated", "customer.subscription.created" + sync_subscription(@event.data.object) + when "customer.subscription.deleted" + downgrade_to_free(@event.data.object) + when "invoice.payment_failed" + mark_past_due(@event.data.object) + end + end + + private + + def handle_checkout_completed(session) + team_id = metadata_team_id(session) + return if team_id.blank? + + team = Team.find_by(id: team_id) + return unless team + + sub = team.subscription || team.build_subscription + sub.update!(stripe_customer_id: session.customer) if session.customer.present? + + if session.subscription.present? + stripe_sub = ::Stripe::Subscription.retrieve(session.subscription) + plan_slug = metadata_plan_slug(session) || "premium_light" + plan_slug = "premium_full" if plan_slug == "premium" + sync_subscription(stripe_sub, team: team, plan_slug: plan_slug) + end + end + + def sync_subscription(stripe_sub, team: nil, plan_slug: nil) + team ||= Team.joins(:subscription) + .find_by(subscriptions: { stripe_subscription_id: stripe_sub.id }) + team ||= Team.find_by(id: metadata_team_id(stripe_sub)) + return unless team + + plan_slug ||= metadata_plan_slug(stripe_sub) || "premium_light" + plan_slug = "premium_full" if plan_slug == "premium" + plan = Plan[plan_slug] + status = map_status(stripe_sub.status) + period_start = unix_time(stripe_sub.current_period_start) + period_end = unix_time(stripe_sub.current_period_end) + + Billing::AssignPlan.call( + team: team, + plan_slug: plan.slug, + status: status, + stripe_attrs: { + stripe_customer_id: stripe_sub.customer, + stripe_subscription_id: stripe_sub.id, + current_period_start: period_start, + current_period_end: period_end, + cancel_at_period_end: stripe_sub.cancel_at_period_end + } + ) + end + + def downgrade_to_free(stripe_sub) + team = Team.joins(:subscription) + .find_by(subscriptions: { stripe_subscription_id: stripe_sub.id }) + return unless team + + Billing::AssignPlan.call( + team: team, + plan_slug: "free", + status: "active", + stripe_attrs: { + stripe_subscription_id: nil, + current_period_start: nil, + current_period_end: nil, + cancel_at_period_end: false + } + ) + end + + def mark_past_due(invoice) + return if invoice.subscription.blank? + + team = Team.joins(:subscription) + .find_by(subscriptions: { stripe_subscription_id: invoice.subscription }) + return unless team&.subscription + + team.subscription.update!(status: "past_due") + end + + def unix_time(value) + return nil if value.blank? + + Time.zone.at(value.to_i) + end + + def metadata_plan_slug(obj) + meta = obj.metadata + return nil if meta.blank? + + meta["plan_slug"] || meta[:plan_slug] || (meta.respond_to?(:plan_slug) ? meta.plan_slug : nil) + end + + def metadata_team_id(obj) + meta = obj.metadata + return nil if meta.blank? + + meta["team_id"] || meta[:team_id] || (meta.respond_to?(:team_id) ? meta.team_id : nil) + end + + def map_status(stripe_status) + case stripe_status + when "trialing" then "trialing" + when "active" then "active" + when "past_due", "unpaid" then "past_due" + when "canceled", "incomplete_expired" then "canceled" + else "incomplete" + end + end + end + end +end diff --git a/backend/app/services/mediamtx/client.rb b/backend/app/services/mediamtx/client.rb new file mode 100644 index 0000000..4d56a74 --- /dev/null +++ b/backend/app/services/mediamtx/client.rb @@ -0,0 +1,101 @@ +require "set" + +module Mediamtx + class Client + class Error < StandardError; end + + def initialize(base_url: MatchLiveTv.mediamtx_api_url) + @conn = Faraday.new(url: base_url) do |f| + f.request :json + f.response :json + f.adapter Faraday.default_adapter + end + end + + def create_path(session) + path = session.mediamtx_path_name + record = session.match.team.entitlements.recording_enabled_for_mediamtx? + body = { + source: "publisher", + overridePublisher: true, + record: record, + recordPath: "/recordings/%path/%Y-%m-%d_%H-%M-%S", + recordSegmentDuration: "60s" + } + unless session.matchlivetv_platform? + body[:runOnReady] = run_on_ready_script(session) + body[:runOnReadyRestart] = true + body[:runOnNotReady] = webhook_curl(session, "disconnect") + end + response = @conn.post("/v3/config/paths/add/#{CGI.escape(path)}", body) + unless response.success? + err = response.body.is_a?(Hash) ? response.body["error"] : response.body + raise Error, "MediaMTX path create failed: #{response.status} #{err}" + end + true + end + + def delete_path(session) + delete_path_name(session.mediamtx_path_name) + end + + def delete_path_name(path_name) + @conn.post("/v3/config/paths/delete/#{CGI.escape(path_name)}") + end + + def list_paths + response = @conn.get("/v3/paths/list") + return [] unless response.success? + + body = response.body + body.is_a?(Hash) ? (body["items"] || []) : [] + rescue Error, Faraday::Error + [] + end + + def online_path_names + Set.new(list_paths.filter_map { |item| item["name"] if item["online"] }) + end + + private + + def publish_webhook(session) + webhook_curl(session, "connect") + end + + def disconnect_webhook(session) + webhook_curl(session, "disconnect") + end + + def webhook_curl(session, event) + secret = MatchLiveTv.mediamtx_webhook_secret + rails = ENV.fetch("RAILS_WEBHOOK_URL", "http://rails:3000") + payload = %({"session_id":"#{session.id}"}) + # MediaMTX image non include curl; wget è disponibile nell'immagine ufficiale + <<~SCRIPT.squish + wget -q -O- --post-data='#{payload}' --header='Content-Type: application/json' + --header="X-MediaMTX-Signature: $(printf '%s' '#{payload}' | openssl dgst -sha256 -hmac '#{secret}' | cut -d' ' -f2)" + #{rails}/webhooks/mediamtx/#{event} + SCRIPT + end + + def run_on_ready_script(session) + connect = webhook_curl(session, "connect") + return connect if session.matchlivetv_platform? + + "#{connect} & #{relay_script(session)}" + end + + def relay_script(session) + return "echo 'no youtube key'" if session.stream_key.blank? + return "echo 'youtube relay skipped'" unless session.platform == "youtube" + + key = session.stream_key + path = session.mediamtx_path_name + <<~SCRIPT.squish + ffmpeg -re -i rtmp://127.0.0.1:1935/#{path} -c copy -f flv + rtmp://a.rtmp.youtube.com/live2/#{key} 2>/var/log/ffmpeg-#{path}.log + SCRIPT + end + end +end diff --git a/backend/app/services/recordings/index_session.rb b/backend/app/services/recordings/index_session.rb new file mode 100644 index 0000000..fe5c227 --- /dev/null +++ b/backend/app/services/recordings/index_session.rb @@ -0,0 +1,25 @@ +module Recordings + class IndexSession + def initialize(session) + @session = session + end + + def call + team = @session.match.team + return unless team.entitlements.can_access_recordings? + + retention_days = team.entitlements.recording_retention_days + expires_at = retention_days.positive? ? retention_days.days.from_now : nil + + recording = Recording.find_or_initialize_by(stream_session: @session) + recording.assign_attributes( + team: team, + status: "ready", + storage_path: @session.mediamtx_path_name, + expires_at: expires_at + ) + recording.save! + recording + end + end +end diff --git a/backend/app/services/sessions/create.rb b/backend/app/services/sessions/create.rb new file mode 100644 index 0000000..c50a14d --- /dev/null +++ b/backend/app/services/sessions/create.rb @@ -0,0 +1,61 @@ +module Sessions + class Create + def initialize(user:, match:, params:) + @user = user + @match = match + @params = params + end + + def call + platform = @params[:platform].presence || "matchlivetv" + ent = @match.team.entitlements + ent.assert_can_stream_on!(platform) + ent.assert_concurrent_stream! + + session = StreamSession.new( + match: @match, + user: @user, + platform: platform, + privacy_status: @params[:privacy_status] || "unlisted", + quality_preset: @params[:quality_preset] || "720p_30_2.5mbps", + target_bitrate: @params[:target_bitrate] || 2_500_000, + target_fps: @params[:target_fps] || 30, + status: "idle" + ) + + if session.platform == "youtube" + attach_youtube_broadcast!(session) + end + + StreamSession.transaction do + session.save! + session.create_score_state! + Mediamtx::Client.new.create_path(session) + log_event(session, "pairing", { created: true, platform: session.platform }) + end + + session + end + + private + + def attach_youtube_broadcast!(session) + yt = Youtube::BroadcastService.new(session.match.team) + title = "#{session.match.team.name} vs #{session.match.opponent_name}" + broadcast = yt.create_broadcast!( + title: title, + privacy_status: session.privacy_status, + scheduled_at: session.match.scheduled_at + ) + + session.youtube_broadcast_id = broadcast[:broadcast_id] + session.youtube_stream_id = broadcast[:stream_id] + session.stream_key = broadcast[:stream_key] + session.rtmp_url = broadcast[:rtmp_url] + end + + def log_event(session, type, metadata) + session.stream_events.create!(event_type: type, metadata: metadata, occurred_at: Time.current) + end + end +end diff --git a/backend/app/services/sessions/pause.rb b/backend/app/services/sessions/pause.rb new file mode 100644 index 0000000..3852008 --- /dev/null +++ b/backend/app/services/sessions/pause.rb @@ -0,0 +1,20 @@ +module Sessions + class Pause + def initialize(session) + @session = session + end + + def call + @session.pause! if @session.may_pause? + log_event("paused") + SessionChannel.broadcast_message(@session, { type: "command", action: "pause_stream" }) + @session + end + + private + + def log_event(type) + @session.stream_events.create!(event_type: type, occurred_at: Time.current, metadata: {}) + end + end +end diff --git a/backend/app/services/sessions/start.rb b/backend/app/services/sessions/start.rb new file mode 100644 index 0000000..864f05f --- /dev/null +++ b/backend/app/services/sessions/start.rb @@ -0,0 +1,21 @@ +module Sessions + class Start + def initialize(session) + @session = session + end + + def call + @session.match.team.entitlements.assert_concurrent_stream!(excluding_session: @session) + @session.begin_connect! if @session.may_begin_connect? + @session.update!(status: "connecting") unless @session.connecting? + broadcast_status("connecting") + @session + end + + private + + def broadcast_status(status) + SessionChannel.broadcast_message(@session, { type: "stream_event", event: "starting", status: status }) + end + end +end diff --git a/backend/app/services/sessions/stop.rb b/backend/app/services/sessions/stop.rb new file mode 100644 index 0000000..6077fcf --- /dev/null +++ b/backend/app/services/sessions/stop.rb @@ -0,0 +1,30 @@ +module Sessions + class Stop + def initialize(session) + @session = session + end + + def call + cancel_timeout_job + @session.end_stream! + Youtube::BroadcastService.new(@session.match.team).complete_broadcast!(@session.youtube_broadcast_id) + Recordings::IndexSession.new(@session).call + Mediamtx::Client.new.delete_path(@session) + log_event("ended") + SessionChannel.broadcast_message(@session, { type: "stream_event", event: "ended" }) + @session + end + + private + + def cancel_timeout_job + return if @session.timeout_job_id.blank? + + Sidekiq::ScheduledSet.new.find_job(@session.timeout_job_id)&.delete + end + + def log_event(type) + @session.stream_events.create!(event_type: type, occurred_at: Time.current, metadata: {}) + end + end +end diff --git a/backend/app/services/teams/entitlement_error.rb b/backend/app/services/teams/entitlement_error.rb new file mode 100644 index 0000000..6ca01cd --- /dev/null +++ b/backend/app/services/teams/entitlement_error.rb @@ -0,0 +1,11 @@ +module Teams + class EntitlementError < StandardError + attr_reader :code, :billing_url + + def initialize(message, code: "premium_required", billing_url: nil) + super(message) + @code = code + @billing_url = billing_url + end + end +end diff --git a/backend/app/services/teams/entitlements.rb b/backend/app/services/teams/entitlements.rb new file mode 100644 index 0000000..8afbeef --- /dev/null +++ b/backend/app/services/teams/entitlements.rb @@ -0,0 +1,239 @@ +module Teams + class Entitlements + ACTIVE_STREAM_STATUSES = %w[idle connecting live reconnecting paused].freeze + + def initialize(team) + @team = team + end + + def subscription + @subscription ||= @team.subscription || ensure_free_subscription! + end + + def plan + subscription.plan + end + + def active? + subscription.active? + end + + def premium_active? + subscription.premium? + end + + def premium_full? + subscription.premium_full? + end + + def youtube_enabled? + active? && plan.youtube_enabled? + end + + def phone_download_enabled? + active? && plan.phone_download_enabled? + end + + def max_staff_transmission + plan.max_staff_transmission + end + + def max_staff_regia + plan.max_staff_regia + end + + def max_staff + plan.max_staff + end + + def staff_count + staff_count_for("transmission") + staff_count_for("regia") + end + + def staff_count_for(kind) + members_count_for(kind) + pending_invitations_count_for(kind) + end + + def members_count + @team.user_teams.where(role: "member").count + end + + def members_count_for(kind) + @team.user_teams.where(role: "member", staff_kind: kind).count + end + + def pending_invitations_count + @team.team_invitations.pending.count + end + + def pending_invitations_count_for(kind) + @team.team_invitations.pending.where(staff_kind: kind).count + end + + def concurrent_streams_limit + plan.concurrent_streams_limit + end + + def concurrent_streams_used + StreamSession.joins(:match) + .where(matches: { team_id: @team.id }) + .where(status: ACTIVE_STREAM_STATUSES) + .count + end + + def concurrent_streams_limit_reached? + limit = concurrent_streams_limit + return false if limit.nil? + + concurrent_streams_used >= limit + end + + def can_stream_on?(platform) + return false unless active? + + platform = platform.to_s + return false unless plan.allows_platform?(platform) + + true + end + + def assert_can_stream_on!(platform) + platform = platform.to_s + unless active? + raise EntitlementError.new( + "Abbonamento non attivo per questa squadra", + code: "subscription_inactive", + billing_url: billing_url + ) + end + + if platform == "youtube" && !youtube_enabled? + raise EntitlementError.new( + "YouTube richiede un piano Premium", + code: "premium_required", + billing_url: billing_url + ) + end + + if platform == "youtube" && premium_full? == false && plan.youtube_mode != "matchlivetv_light" + raise EntitlementError.new( + "Il canale YouTube della società richiede Premium Full", + code: "premium_full_required", + billing_url: billing_url + ) + end + + return if plan.allows_platform?(platform) + + raise EntitlementError.new("Piattaforma non consentita", code: "platform_denied", billing_url: billing_url) + end + + def assert_concurrent_stream!(excluding_session: nil) + return unless active? + + used = concurrent_streams_used + used -= 1 if excluding_session && ACTIVE_STREAM_STATUSES.include?(excluding_session.status) + + limit = concurrent_streams_limit + return if limit.nil? + return if used < limit + + raise EntitlementError.new( + "Limite dirette concorrenti raggiunto (#{limit}). Chiudi una diretta o passa a un piano superiore.", + code: "concurrent_limit_reached", + billing_url: billing_url + ) + end + + def assert_can_invite!(staff_kind: "transmission") + return unless active? + + kind = staff_kind.to_s + unless TeamInvitation::STAFF_KINDS.include?(kind) + raise EntitlementError.new("Ruolo staff non valido", code: "invalid_staff_kind") + end + + limit = staff_limit_for(kind) + return if limit.nil? + + used = staff_count_for(kind) + return if used < limit + + label = kind == "regia" ? "regia" : "trasmissione" + raise EntitlementError.new( + "Limite staff #{label} raggiunto (#{limit} all'anno). Passa a un piano superiore.", + code: "staff_limit_reached", + billing_url: billing_url + ) + end + + def staff_limit_for(kind) + kind == "regia" ? max_staff_regia : max_staff_transmission + end + + def assert_can_connect_youtube! + unless premium_full? + raise EntitlementError.new( + "Collegamento al canale YouTube della società richiede Premium Full", + code: "premium_full_required", + billing_url: billing_url + ) + end + + assert_can_stream_on!("youtube") + end + + def can_access_recordings? + active? && plan.recordings_enabled? + end + + def recording_enabled_for_mediamtx? + can_access_recordings? + end + + def recording_retention_days + plan.recording_days + end + + def billing_url + "#{MatchLiveTv.app_public_url.chomp('/')}/teams/#{@team.id}/billing" + end + + def staff_manage_url + "#{MatchLiveTv.app_public_url.chomp('/')}/teams/#{@team.id}/dashboard" + end + + def as_json + { + plan_slug: plan.slug, + plan_name: plan.name, + premium_active: premium_active?, + premium_full: premium_full?, + subscription_status: subscription.status, + features: plan.features, + billing_url: billing_url, + staff_manage_url: staff_manage_url, + max_staff: max_staff, + max_staff_transmission: max_staff_transmission, + max_staff_regia: max_staff_regia, + staff_used: staff_count, + staff_transmission_used: staff_count_for("transmission"), + staff_regia_used: staff_count_for("regia"), + concurrent_streams_used: concurrent_streams_used, + concurrent_streams_limit: concurrent_streams_limit, + recordings_enabled: can_access_recordings?, + recording_retention_days: recording_retention_days, + phone_download_enabled: phone_download_enabled?, + youtube_enabled: youtube_enabled?, + youtube_mode: plan.youtube_mode + } + end + + private + + def ensure_free_subscription! + Billing::AssignPlan.call(team: @team, plan_slug: "free") + @team.reload.subscription + end + end +end diff --git a/backend/app/services/webhooks/mediamtx_handler.rb b/backend/app/services/webhooks/mediamtx_handler.rb new file mode 100644 index 0000000..f40ed31 --- /dev/null +++ b/backend/app/services/webhooks/mediamtx_handler.rb @@ -0,0 +1,94 @@ +module Webhooks + class MediamtxHandler + def initialize(event:, session_id:, metadata: {}) + @event = event + @session_id = session_id + @metadata = metadata + end + + def call + session = StreamSession.find_by(id: @session_id) + return unless session + + case @event + when "connect" + handle_connect(session) + when "disconnect" + handle_disconnect(session) + when "ready" + handle_ready(session) + end + end + + private + + def handle_connect(session) + return if session.live? && session.stream_events.where(event_type: "connected").exists? + + if session.reconnecting? + session.reconnect! if session.may_reconnect? + cancel_timeout(session) + log(session, "reconnected") + broadcast(session, "reconnected") + elsif session.connecting? || session.idle? + session.go_live! if session.may_go_live? + log(session, "connected") + broadcast(session, "connected") + end + end + + def handle_disconnect(session) + return unless session.live? || session.connecting? + + if session.connecting? + session.update!(status: "reconnecting") + elsif session.may_lose_connection? + session.lose_connection! + end + log(session, "disconnected", @metadata) + broadcast(session, "disconnected") + schedule_timeout(session) + end + + def handle_ready(session) + log(session, "connected", { ready: true }) + end + + def schedule_timeout(session) + job = DisconnectionTimeoutJob.perform_in( + MatchLiveTv.reconnect_timeout_seconds.seconds, + session.id + ) + session.update!(timeout_job_id: job) + end + + def cancel_timeout(session) + return if session.timeout_job_id.blank? + + Sidekiq::ScheduledSet.new.find_job(session.timeout_job_id)&.delete + session.update!(timeout_job_id: nil) + end + + def log(session, type, meta = {}) + return if duplicate_event?(session, type, meta) + + session.stream_events.create!( + event_type: type, + metadata: meta.merge(source: "mediamtx"), + occurred_at: Time.current + ) + end + + def duplicate_event?(session, type, meta) + session.stream_events + .where(event_type: type) + .where("occurred_at > ?", 2.seconds.ago) + .where(metadata: meta.merge(source: "mediamtx")) + .exists? + end + + def broadcast(session, event) + SessionChannel.broadcast_message(session, { type: "stream_event", event: event }) + end + end +end diff --git a/backend/app/services/webhooks/signature.rb b/backend/app/services/webhooks/signature.rb new file mode 100644 index 0000000..fce95ba --- /dev/null +++ b/backend/app/services/webhooks/signature.rb @@ -0,0 +1,14 @@ +module Webhooks + class Signature + def self.valid?(payload, signature) + return false if signature.blank? + + expected = OpenSSL::HMAC.hexdigest( + "SHA256", + MatchLiveTv.mediamtx_webhook_secret, + payload + ) + ActiveSupport::SecurityUtils.secure_compare(expected, signature) + end + end +end diff --git a/backend/app/services/youtube/broadcast_service.rb b/backend/app/services/youtube/broadcast_service.rb new file mode 100644 index 0000000..c82a88d --- /dev/null +++ b/backend/app/services/youtube/broadcast_service.rb @@ -0,0 +1,89 @@ +module Youtube + class BroadcastService + class Error < StandardError; end + + def initialize(team) + @team = team + @credential = team.youtube_credential + end + + def create_broadcast!(title:, privacy_status: "unlisted", scheduled_at: nil) + return mock_broadcast if @credential.blank? || missing_oauth_config? + + client = authorized_client + broadcast = Google::Apis::YoutubeV3::LiveBroadcast.new( + snippet: Google::Apis::YoutubeV3::LiveBroadcastSnippet.new( + title: title, + scheduled_start_time: scheduled_at&.iso8601 + ), + status: Google::Apis::YoutubeV3::LiveBroadcastStatus.new( + privacy_status: privacy_status, + self_declared_made_for_kids: false + ), + content_details: Google::Apis::YoutubeV3::LiveBroadcastContentDetails.new( + enable_auto_start: true, + enable_auto_stop: true + ) + ) + result = client.insert_live_broadcast("snippet,status,contentDetails", broadcast) + + stream = Google::Apis::YoutubeV3::LiveStream.new( + snippet: Google::Apis::YoutubeV3::LiveStreamSnippet.new(title: "#{title} stream"), + cdn: Google::Apis::YoutubeV3::CdnSettings.new( + frame_rate: "30fps", + ingestion_type: "rtmp", + resolution: "720p" + ) + ) + stream_result = client.insert_live_stream("snippet,cdn", stream) + client.bind_live_broadcast(result.id, "id,contentDetails", stream_result.id) + + { + broadcast_id: result.id, + stream_id: stream_result.id, + stream_key: stream_result.cdn.ingestion_info.stream_name, + rtmp_url: stream_result.cdn.ingestion_info.ingestion_address + } + rescue Google::Apis::Error => e + raise Error, e.message + end + + def complete_broadcast!(broadcast_id) + return if @credential.blank? || missing_oauth_config? + + client = authorized_client + client.transition_live_broadcast(broadcast_id, "complete") + rescue Google::Apis::Error + nil + end + + def viewer_count(broadcast_id) + return 0 if @credential.blank? || missing_oauth_config? + + client = authorized_client + resp = client.list_live_broadcasts("statistics", id: broadcast_id) + resp.items&.first&.statistics&.concurrent_viewers.to_i + rescue Google::Apis::Error + 0 + end + + private + + def mock_broadcast + { + broadcast_id: "mock_#{SecureRandom.hex(8)}", + stream_id: "mock_stream", + stream_key: ENV.fetch("YOUTUBE_MOCK_STREAM_KEY", "mock-stream-key"), + rtmp_url: "rtmp://a.rtmp.youtube.com/live2" + } + end + + def missing_oauth_config? + ENV["YOUTUBE_CLIENT_ID"].blank? + end + + def authorized_client + OauthRefresh.new(@credential).apply!(Google::Apis::YoutubeV3::YouTubeService.new) + end + end +end diff --git a/backend/app/services/youtube/oauth_exchange.rb b/backend/app/services/youtube/oauth_exchange.rb new file mode 100644 index 0000000..7a47726 --- /dev/null +++ b/backend/app/services/youtube/oauth_exchange.rb @@ -0,0 +1,25 @@ +module Youtube + class OauthExchange + TOKEN_URL = "https://oauth2.googleapis.com/token".freeze + + def self.call(code) + response = Faraday.post(TOKEN_URL, { + code: code, + client_id: ENV["YOUTUBE_CLIENT_ID"], + client_secret: ENV["YOUTUBE_CLIENT_SECRET"], + redirect_uri: ENV["YOUTUBE_REDIRECT_URI"], + grant_type: "authorization_code" + }) + data = JSON.parse(response.body) + raise BroadcastService::Error, data["error_description"] if data["error"] + + { + access_token: data["access_token"], + refresh_token: data["refresh_token"], + expires_in: data["expires_in"].to_i, + channel_id: nil, + channel_title: nil + } + end + end +end diff --git a/backend/app/services/youtube/oauth_refresh.rb b/backend/app/services/youtube/oauth_refresh.rb new file mode 100644 index 0000000..15e89d8 --- /dev/null +++ b/backend/app/services/youtube/oauth_refresh.rb @@ -0,0 +1,31 @@ +module Youtube + class OauthRefresh + TOKEN_URL = "https://oauth2.googleapis.com/token".freeze + + def initialize(credential) + @credential = credential + end + + def apply!(service) + refresh! if @credential.expired? + service.authorization = @credential.access_token + service + end + + def refresh! + response = Faraday.post(TOKEN_URL, { + client_id: ENV["YOUTUBE_CLIENT_ID"], + client_secret: ENV["YOUTUBE_CLIENT_SECRET"], + refresh_token: @credential.refresh_token, + grant_type: "refresh_token" + }) + data = JSON.parse(response.body) + raise Youtube::BroadcastService::Error, data["error"] if data["error"] + + @credential.update!( + access_token: data["access_token"], + expires_at: Time.current + data["expires_in"].to_i.seconds + ) + end + end +end diff --git a/backend/app/services/youtube/oauth_url.rb b/backend/app/services/youtube/oauth_url.rb new file mode 100644 index 0000000..36b3738 --- /dev/null +++ b/backend/app/services/youtube/oauth_url.rb @@ -0,0 +1,22 @@ +module Youtube + class OauthUrl + AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth".freeze + SCOPES = [ + "https://www.googleapis.com/auth/youtube", + "https://www.googleapis.com/auth/youtube.force-ssl" + ].join(" ").freeze + + def self.build(team_id:, redirect_uri:) + params = { + client_id: ENV.fetch("YOUTUBE_CLIENT_ID", "not_configured"), + redirect_uri: redirect_uri, + response_type: "code", + scope: SCOPES, + access_type: "offline", + prompt: "consent", + state: team_id + } + "#{AUTH_URL}?#{URI.encode_www_form(params)}" + end + end +end diff --git a/backend/app/views/admin/dashboard/index.html.erb b/backend/app/views/admin/dashboard/index.html.erb new file mode 100644 index 0000000..50bffd6 --- /dev/null +++ b/backend/app/views/admin/dashboard/index.html.erb @@ -0,0 +1,25 @@ +

Sessioni attive

+<% if @active_sessions.any? %> + + + + <% @active_sessions.each do |s| %> + + + + + + + <% end %> + +
MatchStatusIniziata
<%= s.match.team.name %> vs <%= s.match.opponent_name %><%= s.status %><%= s.started_at %><%= link_to "Dettaglio", admin_session_path(s) %>
+<% else %> +

Nessuna sessione attiva.

+<% end %> + +

Squadre

+ diff --git a/backend/app/views/admin/sessions/index.html.erb b/backend/app/views/admin/sessions/index.html.erb new file mode 100644 index 0000000..2a7a157 --- /dev/null +++ b/backend/app/views/admin/sessions/index.html.erb @@ -0,0 +1,14 @@ +

Stream Sessions

+ + + + <% @sessions.each do |s| %> + + + + + + + <% end %> + +
MatchStatusDisconnects
<%= s.match.team.name %> vs <%= s.match.opponent_name %><%= s.status %><%= s.disconnection_count %><%= link_to "Dettaglio", admin_session_path(s) %>
diff --git a/backend/app/views/admin/sessions/show.html.erb b/backend/app/views/admin/sessions/show.html.erb new file mode 100644 index 0000000..6829e3e --- /dev/null +++ b/backend/app/views/admin/sessions/show.html.erb @@ -0,0 +1,21 @@ +

Session <%= @session.id %>

+

Status: <%= @session.status %>

+

Match: <%= @session.match.team.name %> vs <%= @session.match.opponent_name %>

+<% if @session.youtube_broadcast_id %> +

YouTube: Broadcast

+<% end %> +

RTMP ingest: <%= @session.rtmp_ingest_url %>

+ +

Eventi

+ + + + <% @events.each do |e| %> + + + + + + <% end %> + +
TipoQuandoMeta
<%= e.event_type %><%= e.occurred_at %><%= e.metadata.to_json %>
diff --git a/backend/app/views/admin/teams/index.html.erb b/backend/app/views/admin/teams/index.html.erb new file mode 100644 index 0000000..b57ef48 --- /dev/null +++ b/backend/app/views/admin/teams/index.html.erb @@ -0,0 +1,13 @@ +

Teams

+ + + + <% @teams.each do |t| %> + + + + + + <% end %> + +
NomeSportYouTube
<%= link_to t.name, admin_team_path(t) %><%= t.sport %><%= t.youtube_credential.present? ? "✓" : "—" %>
diff --git a/backend/app/views/admin/teams/show.html.erb b/backend/app/views/admin/teams/show.html.erb new file mode 100644 index 0000000..13b4a88 --- /dev/null +++ b/backend/app/views/admin/teams/show.html.erb @@ -0,0 +1,10 @@ +

<%= @team.name %>

+

Sport: <%= @team.sport %>

+

YouTube: <%= @team.youtube_credential ? "Connesso" : link_to("Connetti", "/api/v1/teams/#{@team.id}/youtube/authorize") %>

+ +

Partite

+ diff --git a/backend/app/views/layouts/admin.html.erb b/backend/app/views/layouts/admin.html.erb new file mode 100644 index 0000000..28ee4c9 --- /dev/null +++ b/backend/app/views/layouts/admin.html.erb @@ -0,0 +1,32 @@ + + + + Match Live TV Admin + + + + +
+

MATCH LIVE TV — Admin

+ +
+ <%= yield %> + + diff --git a/backend/app/views/layouts/mailer.html.erb b/backend/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000..3aac900 --- /dev/null +++ b/backend/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/backend/app/views/layouts/mailer.text.erb b/backend/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000..37f0bdd --- /dev/null +++ b/backend/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/backend/app/views/layouts/marketing.html.erb b/backend/app/views/layouts/marketing.html.erb new file mode 100644 index 0000000..ec2f0ee --- /dev/null +++ b/backend/app/views/layouts/marketing.html.erb @@ -0,0 +1,20 @@ + + + + + + <%= content_for?(:title) ? yield(:title) : "Match Live TV" %> + <%= render "shared/meta_tags" %> + + + + + <%= render "shared/marketing_nav" %> +
+ <% if flash[:notice] %>
<%= flash[:notice] %>
<% end %> + <% if flash[:alert] %>
<%= flash[:alert] %>
<% end %> + <%= yield %> +
+ <%= render "shared/marketing_footer" %> + + diff --git a/backend/app/views/layouts/marketing_live.html.erb b/backend/app/views/layouts/marketing_live.html.erb new file mode 100644 index 0000000..3dc6fc8 --- /dev/null +++ b/backend/app/views/layouts/marketing_live.html.erb @@ -0,0 +1,20 @@ + + + + + + <%= content_for?(:title) ? yield(:title) : "Match Live TV" %> + <%= render "shared/meta_tags" %> + + + + <%= yield :head %> + + + <%= render "shared/marketing_nav" %> +
+ <%= yield %> +
+ <%= render "shared/marketing_footer" %> + + diff --git a/backend/app/views/layouts/public.html.erb b/backend/app/views/layouts/public.html.erb new file mode 100644 index 0000000..5b94f43 --- /dev/null +++ b/backend/app/views/layouts/public.html.erb @@ -0,0 +1,56 @@ + + + + + + <%= content_for?(:title) ? yield(:title) : "Match Live TV" %> + + + +
+
+ <%= link_to public_pricing_path, class: "brand" do %>Match Live TV<% end %> + +
+
+
+ <% if flash[:notice] %>
<%= flash[:notice] %>
<% end %> + <% if flash[:alert] %>
<%= flash[:alert] %>
<% end %> + <%= yield %> +
+ + diff --git a/backend/app/views/public/invitations/show.html.erb b/backend/app/views/public/invitations/show.html.erb new file mode 100644 index 0000000..0f39838 --- /dev/null +++ b/backend/app/views/public/invitations/show.html.erb @@ -0,0 +1,12 @@ +<% content_for :title, "Invito squadra" %> + +
+

Accesso staff — <%= @invitation.team.name %>

+

Sei stato aggiunto allo staff come <%= @invitation.staff_kind == "regia" ? "Regia" : "Trasmissione" %> (<%= @invitation.email %>).

+ <% if logged_in? %> + <%= button_to "Accetta invito", public_invitation_path(token: @token), method: :post, class: "btn btn-primary" %> + <% else %> +

<%= link_to "Accedi", public_login_path %> o <%= link_to "registrati", public_signup_path %> con <%= @invitation.email %>.

+ <%= button_to "Accetta (se già loggato)", public_invitation_path(token: @token), method: :post, class: "btn btn-secondary" %> + <% end %> +
diff --git a/backend/app/views/public/live/index.html.erb b/backend/app/views/public/live/index.html.erb new file mode 100644 index 0000000..8e465b6 --- /dev/null +++ b/backend/app/views/public/live/index.html.erb @@ -0,0 +1,76 @@ +<% content_for :title do %>Dirette in corso — Match Live TV<% end %> + +
+

Dirette in corso

+

+ Cerca per nome squadra o avversario, poi apri la diretta per guardare lo stream. +

+ +<%= form_with url: public_live_index_path, method: :get, local: true, class: "search-form" do %> + + + <% if @query.present? %> + <%= link_to "Azzera", public_live_index_path, class: "btn btn-secondary" %> + <% end %> +<% end %> + +<% if @query.present? %> +

+ Risultati per «<%= h @query %>»: <%= @sessions.size %> diretta<%= @sessions.size == 1 ? "" : "e" %> +

+<% end %> + +<% if @sessions.any? %> +
+ <% @sessions.each do |session| %> + <% match = session.match %> + <% on_air = @online_paths.include?(session.mediamtx_path_name) %> +
+

<%= match.team.name %> vs <%= match.opponent_name %>

+

+ <% if match.location.present? %><%= match.location %> · <% end %> + <%= session.platform == "matchlivetv" ? "Match Live TV" : session.platform.capitalize %> +

+ <% if session.score_state %> +

+ <%= live_score_sets_label(session.score_state) %> + <% if live_score_partials_label(session.score_state).present? %> + Parziali: <%= live_score_partials_label(session.score_state) %> + <% end %> + <%= session.score_state.home_points %> - <%= session.score_state.away_points %> +

+ <% end %> +
+ <% if on_air %> + In onda + <% elsif session.status == "live" %> + Live + <% elsif session.status == "reconnecting" %> + Riconnessione + <% else %> + In avvio + <% end %> +
+ <%= link_to "Guarda diretta →", public_live_path(session), class: "btn-watch" %> +
+ <% end %> +
+<% else %> +
+ <% if @query.present? %> +

Nessuna diretta attiva per «<%= h @query %>».

+

Prova un altro nome squadra o <%= link_to "mostra tutte le dirette", public_live_index_path %>.

+ <% else %> +

Al momento non ci sono dirette attive.

+

Quando una squadra avvia lo streaming dall’app, la partita comparirà qui.

+ <% end %> +
+<% end %> +
diff --git a/backend/app/views/public/live/show.html.erb b/backend/app/views/public/live/show.html.erb new file mode 100644 index 0000000..1a3db06 --- /dev/null +++ b/backend/app/views/public/live/show.html.erb @@ -0,0 +1,227 @@ +<% content_for :title do %><%= @match.team.name %> vs <%= @match.opponent_name %> — Match Live TV<% end %> + +<% content_for :head do %> + <% unless @stream_closed %> + + <% end %> +<% end %> + +
+<%= link_to "← Tutte le dirette", public_live_index_path, class: "back-link" %> + +

<%= @match.team.name %> vs <%= @match.opponent_name %>

+

+ <% if @stream_closed %> + Diretta chiusa + <% elsif @on_air %> + In onda + <% else %> + In attesa + <% end %> +

+ +
+

<%= live_score_sets_label(@session.score_state) || "—" %>

+

> + Parziali: <%= live_score_partials_label(@session.score_state) %> +

+

<%= live_score_points_label(@match, @session.score_state) %>

+
+ +<% if @stream_closed %> +
+ +

Diretta terminata

+

Lo streaming di questa partita è stato chiuso.

+ <% if @session.ended_at %> +

Chiusa il <%= l(@session.ended_at.in_time_zone, format: :long) %>

+ <% end %> +

Il punteggio finale resta visibile sopra.

+ <%= link_to "Tutte le dirette", public_live_index_path, class: "btn btn-secondary stream-ended-btn" %> +
+<% else %> + + + + +<% end %> +
diff --git a/backend/app/views/public/pages/features.html.erb b/backend/app/views/public/pages/features.html.erb new file mode 100644 index 0000000..7cd3c5a --- /dev/null +++ b/backend/app/views/public/pages/features.html.erb @@ -0,0 +1,63 @@ +<% content_for :title, "Funzionalità — Match Live TV" %> +<% content_for :meta_description, "Camera, regia, punteggio live, gestione staff, archivio gare e YouTube con Premium Light e Full." %> + +
+

Funzionalità

+

Tutto ciò che serve per trasmettere partite giovanili in modo professionale, senza complessità da broadcaster TV.

+ +
+

Per la società e lo staff

+
+
+

Staff con email

+

Delega trasmissione e regia a coach e volontari: ognuno ha credenziali personali.

+
+
+

Revoca accessi

+

L'owner rimuove membri dello staff e annulla richieste pendenti dalla dashboard web.

+
+
+

Limiti per piano

+

Posti staff (trasmissione e regia) e partite concorrenti scalano con Free, Premium Light e Premium Full.

+
+
+
+ +
+

In campo e in diretta

+
+
+

Modalità camera

+

Streaming RTMP resiliente: la diretta continua anche con reti instabili.

+
+
+

Regia separata

+

Secondo dispositivo per aggiornare il punteggio via QR pairing.

+
+
+

Pagina pubblica /live

+

Genitori e tifosi guardano dal browser senza installare nulla.

+
+
+

Archivio replay

+

Premium Light/Full: salvataggio server (30 o 90 giorni) e download su telefono.

+
+
+

YouTube Match TV Light

+

Premium Light: le dirette sono visibili anche sul canale YouTube Match TV Light.

+
+
+

YouTube della società

+

Premium Full: relay verso il canale YouTube del club.

+
+
+

Sito della società

+

Integrazione embed sul sito del club — in arrivo con Premium Full.

+
+
+
+ +

+ <%= link_to "Registra la squadra", public_signup_path, class: "btn btn-primary" %> +

+
diff --git a/backend/app/views/public/pages/home.html.erb b/backend/app/views/public/pages/home.html.erb new file mode 100644 index 0000000..7e0e541 --- /dev/null +++ b/backend/app/views/public/pages/home.html.erb @@ -0,0 +1,88 @@ +<% content_for :title, "Match Live TV — Ogni partita, ogni evento, per chi non può esserci" %> +<% content_for :meta_description, "Trasmetti le partite dallo smartphone. Nonni, parenti e amici guardano dal browser, senza app. Se te la perdi, la ritrovi nell'archivio." %> + +
+
+
+

LIVE

+

Ogni partita, ogni evento, per chi non può esserci.

+

+ Trasmetti in diretta dallo smartphone. I nonni, i parenti lontani, gli amici — + guardano dal browser, senza installare nulla. + E se te la sei persa, la ritrovi nell'archivio. +

+
+ <%= link_to "Registra la tua squadra", public_signup_path, class: "btn btn-primary" %> + <%= link_to "Guarda le dirette", public_live_index_path, class: "btn btn-secondary" %> +
+
    +
  • + + Dirette stabili +
  • +
  • + + Archivio sicuro +
  • +
  • + + Condividi con chi vuoi +
  • +
  • + + Tutto dal tuo telefono +
  • +
+
+
+ <%= image_tag "/hero-devices.png", alt: "Smartphone su treppiede per la diretta e secondo telefono per il punteggio in palestra", class: "hero-devices-img", loading: "eager", fetchpriority: "high" %> +
+
+
+ +
+

Come funziona

+
+
+
+ <%= image_tag "/home-step-registra-squadra.png?v=1", alt: "Registrazione squadra: scudo, pallone e modulo con email, password e nome squadra", class: "step-visual-img", loading: "lazy" %> +
+
01
+

Registra la squadra

+

La società si iscrive sul sito e sceglie il piano più adatto.

+
+
+
+ <%= image_tag "/home-step-invita-trasmette.png?v=1", alt: "Invito a trasmettere: smartphone con invio email e busta con accetta invito", class: "step-visual-img", loading: "lazy" %> +
+
02
+

Invita chi trasmette

+

Coach e volontari ricevono un invito: ognuno accede con la propria email.

+
+
+
+ <%= image_tag "/home-step-vai-in-diretta.png?v=1", alt: "Vai in diretta: smartphone su treppiede, punteggio live e condivisione link", class: "step-visual-img", loading: "lazy" %> +
+
03
+

Vai in diretta

+

Dal telefono si avvia la partita: punteggio aggiornato e link da condividere con le famiglie.

+
+
+
+ <%= image_tag "/home-step-tutti-guardano.png?v=2", alt: "Tutti guardano da casa: archivio partite, partita salvata e link per genitori e parenti", class: "step-visual-img", loading: "lazy" %> +
+
04
+

Tutti guardano da casa

+

Genitori, nonni e parenti aprono il link nel browser. La partita resta anche in archivio.

+
+
+
+ +
+

Piani per ogni esigenza

+

Inizia gratis. Passa a Premium quando vuoi più partite in contemporanea, archivio più lungo e diretta anche su YouTube.

+
+ <%= image_tag "/home-piani-ecosistema.png?v=1", alt: "Ecosistema Match Live TV: smartphone in diretta, laptop con live e statistiche, tablet con archivio partite e cloud", class: "plans-teaser-img", loading: "lazy" %> +
+ <%= link_to "Confronta i piani", public_prezzi_path, class: "btn btn-primary" %> +
diff --git a/backend/app/views/public/pages/pricing.html.erb b/backend/app/views/public/pages/pricing.html.erb new file mode 100644 index 0000000..bb20003 --- /dev/null +++ b/backend/app/views/public/pages/pricing.html.erb @@ -0,0 +1,72 @@ +<% content_for :title, "Prezzi — Match Live TV" %> + +
+

Piani per la tua squadra

+

Abbonamento annuale per squadra: lo staff gestisce trasmissione e regia con credenziali dedicate. Pagamento sicuro su Stripe.

+ +
+
+

Free

+
€0
+
    +
  • 1 persona staff / anno per la trasmissione
  • +
  • 1 persona staff / anno per la regia
  • +
  • 1 live alla volta su matchlivetv.eminux.it
  • +
  • Punteggio live e regia da app
  • +
  • No salvataggio / replay
  • +
  • No YouTube
  • +
+ <%= link_to "Inizia gratis", public_signup_path, class: "btn btn-secondary" %> +
+ + + +
+

Premium Full

+
€200/anno
+

oppure €20/mese

+
    +
  • Staff illimitato / anno (trasmissione e regia)
  • +
  • Fino a 10 partite in contemporanea
  • +
  • Collegamento con il canale YouTube della società
  • +
  • Archivio server 90 giorni e download su telefono
  • +
  • Sito società (in arrivo)
  • +
+ <%= link_to "Registrati", public_signup_path, class: "btn btn-primary" %> +
+
+ + + + + + + + + + + + + + + +
FreePremium LightPremium Full
Staff trasmissione / anno15Illimitato
Staff regia / anno15Illimitato
Partite in contemporanea1310
Live su Match Live TV
YouTubeNoMatch TV LightCanale società
Replay serverNo30 gg90 gg
Download telefonoNo
Prezzo€0€40/anno o €5/mese€200/anno o €20/mese
+ +
+

Esigenze diverse?

+

Per società con più squadre, tornei o integrazioni custom, contattaci: troviamo insieme l'offerta migliore.

+ <%= mail_to "info@matchlivetv.eminux.it", "Contattaci", class: "btn btn-primary" %> +
+
diff --git a/backend/app/views/public/pages/privacy.html.erb b/backend/app/views/public/pages/privacy.html.erb new file mode 100644 index 0000000..da3c60d --- /dev/null +++ b/backend/app/views/public/pages/privacy.html.erb @@ -0,0 +1,9 @@ +<% content_for :title, "Privacy — Match Live TV" %> +
+

Informativa privacy

+
+

Match Live TV tratta i dati necessari per account, gestione staff e streaming (email, nome, dati di utilizzo delle sessioni).

+

Per richieste o cancellazione account contattare il gestore del servizio all'indirizzo indicato nella dashboard della squadra.

+

Documento provvisorio — da completare con i dati del titolare del trattamento.

+
+
diff --git a/backend/app/views/public/pages/terms.html.erb b/backend/app/views/public/pages/terms.html.erb new file mode 100644 index 0000000..6c81b3b --- /dev/null +++ b/backend/app/views/public/pages/terms.html.erb @@ -0,0 +1,9 @@ +<% content_for :title, "Termini di servizio — Match Live TV" %> +
+

Termini di servizio

+
+

L'uso di Match Live TV implica l'accettazione dei limiti del piano sottoscritto (staff, dirette concorrenti, archivio).

+

Le dirette sono responsabilità della squadra titolare dell'account. È vietato l'uso per contenuti non conformi alle norme sulla protezione dei minori.

+

Documento provvisorio — da rivedere con consulenza legale.

+
+
diff --git a/backend/app/views/public/registrations/new.html.erb b/backend/app/views/public/registrations/new.html.erb new file mode 100644 index 0000000..062a178 --- /dev/null +++ b/backend/app/views/public/registrations/new.html.erb @@ -0,0 +1,20 @@ +<% content_for :title, "Registrati — Match Live TV" %> + +
+

Registra la squadra

+

Crea l'account del referente (coach o dirigente), poi configurerai la squadra.

+
+ <%= form_with model: @user, url: public_signup_path do |f| %> + <%= f.label :name, "Nome" %> + <%= f.text_field :name, required: true %> + <%= f.label :email, "Email" %> + <%= f.email_field :email, required: true %> + <%= f.label :password, "Password" %> + <%= f.password_field :password, required: true, minlength: 8 %> + <%= f.label :password_confirmation, "Conferma password" %> + <%= f.password_field :password_confirmation, required: true %> + <%= f.submit "Continua", class: "btn btn-primary" %> + <% end %> + +
+
diff --git a/backend/app/views/public/replay/show.html.erb b/backend/app/views/public/replay/show.html.erb new file mode 100644 index 0000000..04cc59b --- /dev/null +++ b/backend/app/views/public/replay/show.html.erb @@ -0,0 +1,37 @@ +<% content_for :title, "Replay — #{@match.team.name} vs #{@match.opponent_name}" %> + +
+ <%= link_to "← Tutte le dirette", public_live_index_path, class: "back-link" %> + +

Replay

+

<%= @match.team.name %> vs <%= @match.opponent_name %>

+ + <% if @recording %> +
+ <% if @session.hls_playback_url.present? %> + + + + <% else %> +

Registrazione indicizzata. Il playback dipende dalla retention del piano Premium.

+ <% end %> + <% if @recording.expires_at %> +

Disponibile fino al <%= l @recording.expires_at, format: :long %>

+ <% end %> +
+ <% end %> +
diff --git a/backend/app/views/public/sessions/new.html.erb b/backend/app/views/public/sessions/new.html.erb new file mode 100644 index 0000000..c05c7b3 --- /dev/null +++ b/backend/app/views/public/sessions/new.html.erb @@ -0,0 +1,15 @@ +<% content_for :title, "Accedi — Match Live TV" %> + +
+

Accedi

+
+ <%= form_with url: public_login_path do |f| %> + <%= label_tag :email, "Email" %> + <%= email_field_tag :email, params[:email], required: true %> + <%= label_tag :password, "Password" %> + <%= password_field_tag :password, required: true %> + <%= submit_tag "Entra", class: "btn btn-primary" %> + <% end %> + +
+
diff --git a/backend/app/views/public/teams/billing.html.erb b/backend/app/views/public/teams/billing.html.erb new file mode 100644 index 0000000..256efa2 --- /dev/null +++ b/backend/app/views/public/teams/billing.html.erb @@ -0,0 +1,49 @@ +<% content_for :title, "Abbonamento — #{@team.name}" %> + +
+

Abbonamento

+

Squadra: <%= @team.name %> · Piano attuale: <%= @entitlements.plan.name %>

+ +
+ <% @plans.each do |plan| %> +
+

<%= plan.name %>

+ <% if plan.slug == "free" %> +
€0
+ <% elsif plan.slug == "premium_light" %> +
€40/anno
+

oppure €5/mese

+ <% else %> +
€200/anno
+

oppure €20/mese

+ <% end %> +
    +
  • Staff trasmissione: <%= plan.max_staff_transmission || "illimitato" %> / anno
  • +
  • Staff regia: <%= plan.max_staff_regia || "illimitato" %> / anno
  • +
  • Partite: <%= plan.concurrent_streams_limit || "illimitate" %> in contemporanea
  • +
  • Replay: <%= plan.recordings_enabled? ? "#{plan.recording_days} giorni" : "no" %>
  • +
  • YouTube: <% if plan.slug == "premium_light" %>Match TV Light<% elsif plan.youtube_enabled? %>canale società<% else %>no<% end %>
  • +
+ <% if plan.slug == @entitlements.plan.slug %> + Piano attuale + <% elsif plan.slug == "free" %> +

Contatta il supporto per downgrade.

+ <% elsif MatchLiveTv.stripe_enabled? %> + <%= link_to "Attiva #{plan.name}", public_team_checkout_path(@team, plan: plan.slug), class: "btn btn-primary" %> + <% else %> +

Stripe non configurato

+ <% end %> +
+ <% end %> +
+ +
+

Esigenze diverse? <%= mail_to "info@matchlivetv.eminux.it", "Contattaci" %> per un'offerta su misura.

+
+ + <% if @entitlements.premium_active? && MatchLiveTv.stripe_enabled? %> +

<%= button_to "Gestisci su Stripe", public_team_portal_path(@team), class: "btn btn-secondary" %>

+ <% end %> + +

<%= link_to "← Dashboard", public_team_dashboard_path(@team) %>

+
diff --git a/backend/app/views/public/teams/dashboard.html.erb b/backend/app/views/public/teams/dashboard.html.erb new file mode 100644 index 0000000..379bd4f --- /dev/null +++ b/backend/app/views/public/teams/dashboard.html.erb @@ -0,0 +1,82 @@ +<% content_for :title, "#{@team.name} — Dashboard" %> + +
+

<%= @team.name %>

+

+ Piano: <%= @entitlements.plan.name %> + · Staff trasmissione: <%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %> + · Staff regia: <%= @entitlements.staff_count_for("regia") %> / <%= @entitlements.max_staff_regia || "∞" %> + · Partite attive: <%= @entitlements.concurrent_streams_used %><% if @entitlements.concurrent_streams_limit %> / <%= @entitlements.concurrent_streams_limit %><% else %> (illimitate)<% end %> +

+ +
+ <%= link_to "Abbonamento", public_team_billing_path(@team), class: "btn btn-primary" %> + <%= link_to "Aggiungi staff", public_team_invite_path(@team), class: "btn btn-secondary" %> + <%= link_to "Dirette live", public_live_index_path, class: "btn btn-secondary" %> +
+ +

Staff attivo

+
+ <% if @members.any? %> + + + + <% @members.each do |ut| %> + + + + + + + <% end %> + +
NomeEmailRuolo
<%= ut.user.name %><%= ut.user.email %><%= ut.staff_kind == "regia" ? "Regia" : "Trasmissione" %> + <%= button_to "Revoca", public_team_remove_member_path(@team, ut.user), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { turbo_confirm: "Revocare l'accesso a #{ut.user.name}?" } } %> +
+ <% else %> +

Nessun membro staff. <%= link_to "Aggiungi qualcuno", public_team_invite_path(@team) %>.

+ <% end %> +
+ +

Staff in attesa

+
+ <% if @pending_invitations.any? %> + + + + <% @pending_invitations.each do |inv| %> + + + + + + + <% end %> + +
EmailRuoloScade
<%= inv.email %><%= inv.staff_kind == "regia" ? "Regia" : "Trasmissione" %><%= l inv.expires_at, format: :short %> + <%= button_to "Annulla", public_team_destroy_invitation_path(@team, inv), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem" %> +
+ <% else %> +

Nessuna richiesta in sospeso.

+ <% end %> +
+ + <% if @entitlements.can_access_recordings? && @recordings.any? %> +

Archivio gare

+
+ + + + <% @recordings.each do |rec| %> + + + + + <% end %> + +
PartitaReplay
<%= rec.stream_session.match.team.name %> vs <%= rec.stream_session.match.opponent_name %><%= link_to "Guarda", rec.replay_url %>
+
+ <% end %> + +

Usa l'app mobile con le stesse credenziali per avviare partite e dirette.

+
diff --git a/backend/app/views/public/teams/invite.html.erb b/backend/app/views/public/teams/invite.html.erb new file mode 100644 index 0000000..2c86303 --- /dev/null +++ b/backend/app/views/public/teams/invite.html.erb @@ -0,0 +1,28 @@ +<% content_for :title, "Aggiungi staff — #{@team.name}" %> + +
+

Aggiungi staff

+

+ Trasmissione: <%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %> + · Regia: <%= @entitlements.staff_count_for("regia") %> / <%= @entitlements.max_staff_regia || "∞" %> +

+ +
+ <%= form_with url: public_team_invite_path(@team), method: :post do %> + <%= label_tag :staff_kind, "Ruolo" %> + <%= select_tag :staff_kind, options_for_select([ + ["Trasmissione (camera / streaming)", "transmission"], + ["Regia (punteggio)", "regia"] + ], params[:staff_kind] || "transmission") %> + <%= label_tag :email, "Email del membro staff" %> + <%= email_field_tag :email, params[:email], required: true %> + <%= submit_tag "Genera link di accesso", class: "btn btn-primary" %> + <% end %> + <% if defined?(@invite_url) && @invite_url.present? %> +

Condividi (valido 7 giorni):

+ <%= @invite_url %> +

Il membro dello staff si registra con la stessa email e accetta l'accesso.

+ <% end %> +
+

<%= link_to "← Dashboard", public_team_dashboard_path(@team) %>

+
diff --git a/backend/app/views/public/teams/new.html.erb b/backend/app/views/public/teams/new.html.erb new file mode 100644 index 0000000..6f9e65e --- /dev/null +++ b/backend/app/views/public/teams/new.html.erb @@ -0,0 +1,21 @@ +<% content_for :title, "Crea squadra — Match Live TV" %> + +
+

La tua squadra

+

Ultimo passo: nome, sport e piano iniziale.

+
+ <%= form_with url: public_teams_path do %> + <%= label_tag "team[name]", "Nome squadra" %> + <%= text_field_tag "team[name]", nil, required: true %> + <%= label_tag "team[sport]", "Sport" %> + <%= select_tag "team[sport]", options_for_select([["Pallavolo", "volleyball"], ["Calcio", "football"], ["Basket", "basketball"]], "volleyball") %> + <%= label_tag :plan, "Piano iniziale" %> + <%= select_tag :plan, options_for_select([ + ["Free — 1 staff trasmissione + 1 regia, 1 live", "free"], + ["Premium Light — €40/anno o €5/mese", "premium_light"], + ["Premium Full — €200/anno o €20/mese", "premium_full"] + ], "free") %> + <%= submit_tag "Crea squadra", class: "btn btn-primary" %> + <% end %> +
+
diff --git a/backend/app/views/shared/_marketing_footer.html.erb b/backend/app/views/shared/_marketing_footer.html.erb new file mode 100644 index 0000000..f362b95 --- /dev/null +++ b/backend/app/views/shared/_marketing_footer.html.erb @@ -0,0 +1,13 @@ +
+
+
+ Match Live TV — Ogni partita, ogni evento, per chi non può esserci +
+
+ <%= link_to "Prezzi", public_prezzi_path %> · + <%= link_to "Dirette", public_live_index_path %> · + <%= link_to "Privacy", public_privacy_path %> · + <%= link_to "Termini", public_termini_path %> +
+
+
diff --git a/backend/app/views/shared/_marketing_nav.html.erb b/backend/app/views/shared/_marketing_nav.html.erb new file mode 100644 index 0000000..7ecb4f6 --- /dev/null +++ b/backend/app/views/shared/_marketing_nav.html.erb @@ -0,0 +1,76 @@ +<% live_section = request.path.start_with?("/live") %> +
+
+
+ <%= link_to root_path, class: "brand" do %>Match Live TV<% end %> + + +
+
+ + +
+ + + + diff --git a/backend/app/views/shared/_meta_tags.html.erb b/backend/app/views/shared/_meta_tags.html.erb new file mode 100644 index 0000000..ed59ed4 --- /dev/null +++ b/backend/app/views/shared/_meta_tags.html.erb @@ -0,0 +1,7 @@ +<% description = content_for?(:meta_description) ? yield(:meta_description) : "Trasmetti le partite dal telefono. Parenti e nonni guardano dal browser. Se te la perdi, la ritrovi nell'archivio." %> + + + + + + diff --git a/backend/bin/brakeman b/backend/bin/brakeman new file mode 100755 index 0000000..ace1c9b --- /dev/null +++ b/backend/bin/brakeman @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +ARGV.unshift("--ensure-latest") + +load Gem.bin_path("brakeman", "brakeman") diff --git a/backend/bin/bundle b/backend/bin/bundle new file mode 100755 index 0000000..50da5fd --- /dev/null +++ b/backend/bin/bundle @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require "rubygems" + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($0) == File.expand_path(__FILE__) + end + + def env_var_version + ENV["BUNDLER_VERSION"] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) + bundler_version = a + end + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + bundler_version = $1 + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV["BUNDLE_GEMFILE"] + return gemfile if gemfile && !gemfile.empty? + + File.expand_path("../Gemfile", __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + Regexp.last_match(1) + end + + def bundler_requirement + @bundler_requirement ||= + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) + end + + def bundler_requirement_for(version) + return "#{Gem::Requirement.default}.a" unless version + + bundler_gem_version = Gem::Version.new(version) + + bundler_gem_version.approximate_recommendation + end + + def load_bundler! + ENV["BUNDLE_GEMFILE"] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem "bundler", bundler_requirement + end + return if gem_error.nil? + require_error = activation_error_handling do + require "bundler/version" + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +if m.invoked_as_script? + load Gem.bin_path("bundler", "bundle") +end diff --git a/backend/bin/docker-entrypoint b/backend/bin/docker-entrypoint new file mode 100755 index 0000000..840d093 --- /dev/null +++ b/backend/bin/docker-entrypoint @@ -0,0 +1,13 @@ +#!/bin/bash -e + +# Enable jemalloc for reduced memory usage and latency. +if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then + export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)" +fi + +# If running the rails server then create or migrate existing database +if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then + ./bin/rails db:prepare +fi + +exec "${@}" diff --git a/backend/bin/rails b/backend/bin/rails new file mode 100755 index 0000000..efc0377 --- /dev/null +++ b/backend/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/backend/bin/rake b/backend/bin/rake new file mode 100755 index 0000000..4fbf10b --- /dev/null +++ b/backend/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/backend/bin/rubocop b/backend/bin/rubocop new file mode 100755 index 0000000..40330c0 --- /dev/null +++ b/backend/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/backend/bin/setup b/backend/bin/setup new file mode 100755 index 0000000..eb1e55e --- /dev/null +++ b/backend/bin/setup @@ -0,0 +1,37 @@ +#!/usr/bin/env ruby +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) +APP_NAME = "app" + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + puts "\n== Restarting application server ==" + system! "bin/rails restart" + + # puts "\n== Configuring puma-dev ==" + # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" + # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" +end diff --git a/backend/config.ru b/backend/config.ru new file mode 100644 index 0000000..4a3c09a --- /dev/null +++ b/backend/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/backend/config/application.rb b/backend/config/application.rb new file mode 100644 index 0000000..7f71b3e --- /dev/null +++ b/backend/config/application.rb @@ -0,0 +1,30 @@ +require_relative "boot" + +require "rails" +# Pick the frameworks you want: +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "active_storage/engine" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_mailbox/engine" +require "action_text/engine" +require "action_view/railtie" +require "action_cable/engine" +# require "rails/test_unit/railtie" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module App + class Application < Rails::Application + config.load_defaults 7.2 + config.autoload_lib(ignore: %w[assets tasks]) + config.api_only = false + config.active_job.queue_adapter = :sidekiq + config.time_zone = "Europe/Rome" + config.generators { |g| g.orm :active_record, primary_key_type: :uuid } + end +end diff --git a/backend/config/boot.rb b/backend/config/boot.rb new file mode 100644 index 0000000..988a5dd --- /dev/null +++ b/backend/config/boot.rb @@ -0,0 +1,4 @@ +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/backend/config/cable.yml b/backend/config/cable.yml new file mode 100644 index 0000000..1aad390 --- /dev/null +++ b/backend/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: redis + url: <%= ENV.fetch("REDIS_URL", "redis://localhost:6379/0") %> + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL", "redis://localhost:6379/0") %> diff --git a/backend/config/credentials.yml.enc b/backend/config/credentials.yml.enc new file mode 100644 index 0000000..90b4726 --- /dev/null +++ b/backend/config/credentials.yml.enc @@ -0,0 +1 @@ +iUeJHiuhCvxm/D8G4wDoJUfOcttCPg3HCQwWGreLmNBwzZDpxcHwZvNezpVkotHCl9+uwHHXx+u4cncDr2iwD/K7sDjYdd34+wiqXKOIjbuy8yRArndjvcTOpPKoY6JrTCmoJ+NhYz9g4G3NZXxc604MrjuNvOEAr3bYJdH932699WaZJYJbBTODbZ4EFJByOn30qgdSwPHp3aG2R2zMsJn46v00QftdEdAr/Nyy1PergwKtxj0JfI7ChAev3Wy6JBruzONX1krBauSzqkeMK7NCqyboVP8HmJC6SRhvuhuHXGKtCRep1oOdPwyYf0OLMFnyARbTMnxN1W+MDTbs2n6rOg4vpzy3QIgYb/aXLXZd+B8znP44pvN9x963pmaI6Jkimez5UYT0HNV8Df+dhbtHgfE4--pHQJ1krK0oIVvx7F--cqzNinUJSxx+aUQJyyKBdA== \ No newline at end of file diff --git a/backend/config/database.yml b/backend/config/database.yml new file mode 100644 index 0000000..1cc3387 --- /dev/null +++ b/backend/config/database.yml @@ -0,0 +1,83 @@ +# PostgreSQL. Versions 9.3 and up are supported. +# +# Install the pg driver: +# gem install pg +# On macOS with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem "pg" +# +default: &default + adapter: postgresql + encoding: unicode + # For details on connection pooling, see Rails configuration guide + # https://guides.rubyonrails.org/configuring.html#database-pooling + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + + +development: + <<: *default + database: app_development + + # The specified database role being used to connect to PostgreSQL. + # To create additional roles in PostgreSQL see `$ createuser --help`. + # When left blank, PostgreSQL will use the default role. This is + # the same name as the operating system user running Rails. + #username: app + + # The password associated with the PostgreSQL role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: app_test + +# As with config/credentials.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password or a full connection URL as an environment +# variable when you boot the app. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# If the connection URL is provided in the special DATABASE_URL environment +# variable, Rails will automatically merge its configuration values on top of +# the values provided in this file. Alternatively, you can specify a connection +# URL environment variable explicitly: +# +# production: +# url: <%= ENV["MY_APP_DATABASE_URL"] %> +# +# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full overview on how database connection configuration can be specified. +# +production: + <<: *default + url: <%= ENV["DATABASE_URL"] %> diff --git a/backend/config/deploy.yml b/backend/config/deploy.yml new file mode 100644 index 0000000..164f8b8 --- /dev/null +++ b/backend/config/deploy.yml @@ -0,0 +1,29 @@ +# Kamal deploy configuration +service: match-live-tv +image: matchlivetv/backend + +servers: + web: + hosts: + - YOUR_HETZNER_IP + +proxy: + ssl: true + host: api.matchlivetv.example.com + +env: + clear: + RAILS_ENV: production + MEDIAMTX_API_URL: http://mediamtx:9997 + MEDIAMTX_RTMP_URL: rtmp://mediamtx:1935 + secret: + - SECRET_KEY_BASE + - DATABASE_URL + - REDIS_URL + - JWT_SECRET + - MEDIAMTX_WEBHOOK_SECRET + - YOUTUBE_CLIENT_ID + - YOUTUBE_CLIENT_SECRET + +builder: + arch: amd64 diff --git a/backend/config/environment.rb b/backend/config/environment.rb new file mode 100644 index 0000000..cac5315 --- /dev/null +++ b/backend/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/backend/config/environments/development.rb b/backend/config/environments/development.rb new file mode 100644 index 0000000..98128ff --- /dev/null +++ b/backend/config/environments/development.rb @@ -0,0 +1,75 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded any time + # it changes. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.cache_store = :memory_store + config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Disable caching for Action Mailer templates even if Action Controller + # caching is enabled. + config.action_mailer.perform_caching = false + + config.action_mailer.default_url_options = { host: "localhost", port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true + + # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. + # config.generators.apply_rubocop_autocorrect_after_generate! +end diff --git a/backend/config/environments/production.rb b/backend/config/environments/production.rb new file mode 100644 index 0000000..9e16a40 --- /dev/null +++ b/backend/config/environments/production.rb @@ -0,0 +1,49 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + config.enable_reloading = false + config.eager_load = true + config.consider_all_requests_local = false + + config.active_storage.service = :local + + # Nginx Proxy Manager termina TLS + config.assume_ssl = true + config.force_ssl = true + config.ssl_options = { + redirect: { + exclude: ->(request) { request.path == "/up" || request.path == "/cable" } + } + } + + config.action_cable.disable_request_forgery_protection = true + + if (cable_url = ENV["CABLE_URL"]).present? + config.action_cable.url = cable_url + end + + cable_origins = ENV.fetch("CORS_ORIGINS", "*").split(",").map(&:strip) + config.action_cable.allowed_request_origins = cable_origins + [ + %r{https?://.*} + ] + + allowed_hosts = ENV.fetch("ALLOWED_HOSTS", "").split(",").map(&:strip).reject(&:empty?) + config.hosts = allowed_hosts if allowed_hosts.any? + + config.host_authorization = { + exclude: ->(request) { request.path == "/up" || request.path.start_with?("/cable") } + } + + config.logger = ActiveSupport::Logger.new(STDOUT) + .tap { |logger| logger.formatter = ::Logger::Formatter.new } + .then { |logger| ActiveSupport::TaggedLogging.new(logger) } + + config.log_tags = [ :request_id ] + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + config.action_mailer.perform_caching = false + config.i18n.fallbacks = true + config.active_support.report_deprecations = false + config.active_record.dump_schema_after_migration = false + config.active_record.attributes_for_inspect = [ :id ] +end diff --git a/backend/config/environments/test.rb b/backend/config/environments/test.rb new file mode 100644 index 0000000..0c616a1 --- /dev/null +++ b/backend/config/environments/test.rb @@ -0,0 +1,67 @@ +require "active_support/core_ext/integer/time" + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :rescuable + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Disable caching for Action Mailer templates even if Action Controller + # caching is enabled. + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Unlike controllers, the mailer instance doesn't have any context about the + # incoming request so you'll need to provide the :host parameter yourself. + config.action_mailer.default_url_options = { host: "www.example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/backend/config/initializers/attr_encrypted.rb b/backend/config/initializers/attr_encrypted.rb new file mode 100644 index 0000000..be7c5b5 --- /dev/null +++ b/backend/config/initializers/attr_encrypted.rb @@ -0,0 +1 @@ +# attr_encrypted uses per-model :key option (see YoutubeCredential, StreamSession) diff --git a/backend/config/initializers/cors.rb b/backend/config/initializers/cors.rb new file mode 100644 index 0000000..34672bf --- /dev/null +++ b/backend/config/initializers/cors.rb @@ -0,0 +1,9 @@ +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins ENV.fetch("CORS_ORIGINS", "*").split(",") + resource "*", + headers: :any, + methods: %i[get post put patch delete options head], + expose: %w[Authorization] + end +end diff --git a/backend/config/initializers/filter_parameter_logging.rb b/backend/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000..6c73e85 --- /dev/null +++ b/backend/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +Rails.application.config.filter_parameters += [ + :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, + :stream_key, :password, :access_token, :refresh_token, :pairing_token +] diff --git a/backend/config/initializers/inflections.rb b/backend/config/initializers/inflections.rb new file mode 100644 index 0000000..3860f65 --- /dev/null +++ b/backend/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/backend/config/initializers/match_live_tv.rb b/backend/config/initializers/match_live_tv.rb new file mode 100644 index 0000000..08fb6f0 --- /dev/null +++ b/backend/config/initializers/match_live_tv.rb @@ -0,0 +1,55 @@ +module MatchLiveTv + class << self + def jwt_secret + ENV.fetch("JWT_SECRET", Rails.application.secret_key_base) + end + + def mediamtx_api_url + ENV.fetch("MEDIAMTX_API_URL", "http://localhost:9997") + end + + def mediamtx_rtmp_url + ENV.fetch("MEDIAMTX_RTMP_URL", "rtmp://localhost:1935") + end + + def mediamtx_webhook_secret + ENV.fetch("MEDIAMTX_WEBHOOK_SECRET", "change_me") + end + + def reconnect_timeout_seconds + ENV.fetch("RECONNECT_TIMEOUT_SECONDS", "300").to_i + end + + def hls_public_url + ENV.fetch("HLS_PUBLIC_URL", "http://localhost:8888") + end + + def mediamtx_hls_url + ENV.fetch("MEDIAMTX_HLS_URL", "http://mediamtx:8888") + end + + def app_public_url + ENV.fetch("APP_PUBLIC_URL", "http://localhost:3000") + end + + def stripe_secret_key + ENV["STRIPE_SECRET_KEY"].presence + end + + def stripe_webhook_secret + ENV["STRIPE_WEBHOOK_SECRET"].presence + end + + def stripe_premium_light_price_id + ENV.fetch("STRIPE_PREMIUM_LIGHT_PRICE_ID", ENV.fetch("STRIPE_PREMIUM_PRICE_ID", "")) + end + + def stripe_premium_full_price_id + ENV.fetch("STRIPE_PREMIUM_FULL_PRICE_ID", ENV.fetch("STRIPE_PREMIUM_PRICE_ID", "")) + end + + def stripe_enabled? + stripe_secret_key.present? + end + end +end diff --git a/backend/config/initializers/rack_attack.rb b/backend/config/initializers/rack_attack.rb new file mode 100644 index 0000000..ce8f295 --- /dev/null +++ b/backend/config/initializers/rack_attack.rb @@ -0,0 +1,11 @@ +class Rack::Attack + throttle("api/ip", limit: 300, period: 5.minutes) do |req| + req.ip if req.path.start_with?("/api/") + end + + throttle("auth/ip", limit: 20, period: 5.minutes) do |req| + req.ip if req.path.include?("/auth/login") + end +end + +Rails.application.config.middleware.use Rack::Attack if defined?(Rack::Attack) diff --git a/backend/config/initializers/sidekiq.rb b/backend/config/initializers/sidekiq.rb new file mode 100644 index 0000000..3caf607 --- /dev/null +++ b/backend/config/initializers/sidekiq.rb @@ -0,0 +1,9 @@ +require "sidekiq/api" + +Sidekiq.configure_server do |config| + config.redis = { url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") } +end + +Sidekiq.configure_client do |config| + config.redis = { url: ENV.fetch("REDIS_URL", "redis://localhost:6379/0") } +end diff --git a/backend/config/initializers/sidekiq_cron.rb b/backend/config/initializers/sidekiq_cron.rb new file mode 100644 index 0000000..57d20c9 --- /dev/null +++ b/backend/config/initializers/sidekiq_cron.rb @@ -0,0 +1,2 @@ +# Schedule CleanupExpiredSessionsJob hourly via host cron or Kamal: +# 0 * * * * cd /app && bundle exec rails runner "CleanupExpiredSessionsJob.perform_async" diff --git a/backend/config/initializers/stripe.rb b/backend/config/initializers/stripe.rb new file mode 100644 index 0000000..6ae98a7 --- /dev/null +++ b/backend/config/initializers/stripe.rb @@ -0,0 +1,3 @@ +if MatchLiveTv.stripe_secret_key.present? + Stripe.api_key = MatchLiveTv.stripe_secret_key +end diff --git a/backend/config/locales/en.yml b/backend/config/locales/en.yml new file mode 100644 index 0000000..6c349ae --- /dev/null +++ b/backend/config/locales/en.yml @@ -0,0 +1,31 @@ +# Files in the config/locales directory are used for internationalization and +# are automatically loaded by Rails. If you want to use locales other than +# English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more about the API, please read the Rails Internationalization guide +# at https://guides.rubyonrails.org/i18n.html. +# +# Be aware that YAML interprets the following case-insensitive strings as +# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings +# must be quoted to be interpreted as strings. For example: +# +# en: +# "yes": yup +# enabled: "ON" + +en: + hello: "Hello world" diff --git a/backend/config/puma.rb b/backend/config/puma.rb new file mode 100644 index 0000000..03c166f --- /dev/null +++ b/backend/config/puma.rb @@ -0,0 +1,34 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. + +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# to prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/backend/config/routes.rb b/backend/config/routes.rb new file mode 100644 index 0000000..98fa609 --- /dev/null +++ b/backend/config/routes.rb @@ -0,0 +1,96 @@ +Rails.application.routes.draw do + mount ActionCable.server => "/cable" + + get "up" => "rails/health#show", as: :rails_health_check + + namespace :api do + namespace :v1 do + post "auth/login", to: "auth#login" + post "auth/register", to: "auth#register" + post "auth/logout", to: "auth#logout" + post "auth/refresh", to: "auth#refresh" + get "auth/me", to: "auth#me" + + resources :teams do + member do + post :members, action: :add_member + delete "members/:user_id", action: :remove_member + get :recordings + end + resources :matches, only: %i[index create] + get "youtube/authorize", to: "youtube#authorize" + end + + resources :matches, only: %i[show update destroy] do + resources :sessions, only: %i[create], controller: "stream_sessions" + end + + resources :sessions, only: %i[show], controller: "stream_sessions" do + member do + patch :start + patch :stop + patch :pause + get :events + post :telemetry + post :pairing_token + post :claim_pairing + post :network_test + get :youtube_stats + end + end + + get "youtube/callback", to: "youtube#callback" + end + end + + namespace :webhooks do + post "mediamtx/connect", to: "mediamtx#connect" + post "mediamtx/disconnect", to: "mediamtx#disconnect" + post "mediamtx/ready", to: "mediamtx#ready" + post "mediamtx/validate_publish", to: "mediamtx#validate_publish" + post "stripe", to: "stripe#create" + end + + post "internal/validate_publish", to: "webhooks/mediamtx#validate_publish" + + namespace :admin do + root to: "dashboard#index" + resources :teams, only: %i[index show] + resources :sessions, only: %i[index show] + end + + get "hls/*path", to: "hls_proxy#show", format: false, constraints: { path: /.+/ } + + get "live", to: "public/live#index", as: :public_live_index + get "live/:id", to: "public/live#show", as: :public_live + get "live/:id/status.json", to: "public/live#status", as: :public_live_status + get "replay/:id", to: "public/replay#show", as: :public_replay + + root to: "public/pages#home" + + scope module: :public, as: :public do + get "funzionalita", to: "pages#features", as: :features + get "prezzi", to: "pages#pricing", as: :prezzi + get "pricing", to: redirect("/prezzi") + get "privacy", to: "pages#privacy", as: :privacy + get "termini", to: "pages#terms", as: :termini + get "signup", to: "registrations#new" + post "signup", to: "registrations#create" + get "login", to: "sessions#new" + post "login", to: "sessions#create" + delete "logout", to: "sessions#destroy" + get "teams/new", to: "teams#new" + post "teams", to: "teams#create" + get "teams/:id/dashboard", to: "teams#dashboard", as: :team_dashboard + get "teams/:id/billing", to: "teams#billing", as: :team_billing + get "teams/:id/checkout", to: "teams#checkout", as: :team_checkout + post "teams/:id/portal", to: "teams#portal", as: :team_portal + get "teams/:id/invite", to: "teams#invite", as: :team_invite + post "teams/:id/invite", to: "teams#create_invitation" + delete "teams/:id/members/:user_id", to: "teams#remove_member", as: :team_remove_member + delete "teams/:id/invitations/:invitation_id", to: "teams#destroy_invitation", as: :team_destroy_invitation + get "join/:token", to: "invitations#show", as: :invitation + post "join/:token", to: "invitations#accept" + end + +end diff --git a/backend/config/sidekiq.yml b/backend/config/sidekiq.yml new file mode 100644 index 0000000..3d67b00 --- /dev/null +++ b/backend/config/sidekiq.yml @@ -0,0 +1,4 @@ +:concurrency: 5 +:queues: + - default + - critical diff --git a/backend/config/storage.yml b/backend/config/storage.yml new file mode 100644 index 0000000..4942ab6 --- /dev/null +++ b/backend/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/backend/db/migrate/20260525000001_create_match_live_tv_schema.rb b/backend/db/migrate/20260525000001_create_match_live_tv_schema.rb new file mode 100644 index 0000000..d91377a --- /dev/null +++ b/backend/db/migrate/20260525000001_create_match_live_tv_schema.rb @@ -0,0 +1,113 @@ +class CreateMatchLiveTvSchema < ActiveRecord::Migration[7.2] + def change + enable_extension "pgcrypto" unless extension_enabled?("pgcrypto") + + create_table :users, id: :uuid do |t| + t.string :email, null: false + t.string :password_digest, null: false + t.string :name, null: false + t.string :role, null: false, default: "coach" + t.timestamps + end + add_index :users, :email, unique: true + + create_table :teams, id: :uuid do |t| + t.string :name, null: false + t.string :sport, default: "volleyball" + t.string :logo_url + t.timestamps + end + + create_table :user_teams, id: :uuid do |t| + t.references :user, type: :uuid, null: false, foreign_key: true + t.references :team, type: :uuid, null: false, foreign_key: true + t.string :role, null: false, default: "member" + t.timestamps + end + add_index :user_teams, %i[user_id team_id], unique: true + + create_table :youtube_credentials, id: :uuid do |t| + t.references :team, type: :uuid, null: false, foreign_key: true + t.text :access_token_encrypted + t.text :refresh_token_encrypted + t.datetime :expires_at + t.string :channel_id + t.string :channel_title + t.timestamps + end + + create_table :matches, id: :uuid do |t| + t.references :team, type: :uuid, null: false, foreign_key: true + t.string :opponent_name, null: false + t.string :location + t.datetime :scheduled_at + t.string :sport, default: "volleyball" + t.integer :sets_to_win, default: 3 + t.jsonb :roster_numbers, default: [] + t.string :category + t.string :phase + t.timestamps + end + + create_table :stream_sessions, id: :uuid do |t| + t.references :match, type: :uuid, null: false, foreign_key: true + t.references :user, type: :uuid, null: false, foreign_key: true + t.string :status, null: false, default: "idle" + t.string :platform, default: "youtube" + t.text :stream_key_encrypted + t.string :rtmp_url + t.string :youtube_broadcast_id + t.string :youtube_stream_id + t.string :privacy_status, default: "unlisted" + t.string :quality_preset, default: "720p_30_2.5mbps" + t.integer :target_bitrate, default: 2_500_000 + t.integer :target_fps, default: 30 + t.datetime :started_at + t.datetime :ended_at + t.integer :total_duration_secs, default: 0 + t.integer :disconnection_count, default: 0 + t.string :publish_token + t.string :pairing_token_digest + t.datetime :pairing_token_expires_at + t.string :timeout_job_id + t.timestamps + end + add_index :stream_sessions, :publish_token, unique: true + add_index :stream_sessions, :status + + create_table :stream_events, id: :uuid do |t| + t.references :stream_session, type: :uuid, null: false, foreign_key: true + t.string :event_type, null: false + t.jsonb :metadata, default: {} + t.datetime :occurred_at, null: false + end + add_index :stream_events, %i[stream_session_id occurred_at] + + create_table :score_states, id: :uuid do |t| + t.references :stream_session, type: :uuid, null: false, foreign_key: true, index: { unique: true } + t.integer :home_sets, default: 0 + t.integer :away_sets, default: 0 + t.integer :home_points, default: 0 + t.integer :away_points, default: 0 + t.integer :current_set, default: 1 + t.string :period + t.boolean :timeout_home, default: false + t.boolean :timeout_away, default: false + t.timestamps + end + + create_table :device_states, id: :uuid do |t| + t.references :stream_session, type: :uuid, null: false, foreign_key: true + t.string :device_role, null: false + t.integer :battery_level + t.string :network_type + t.integer :signal_strength + t.integer :current_bitrate + t.integer :target_bitrate + t.integer :fps + t.datetime :last_seen_at + t.timestamps + end + add_index :device_states, %i[stream_session_id device_role], unique: true + end +end diff --git a/backend/db/migrate/20260525120000_add_set_partials_to_score_states.rb b/backend/db/migrate/20260525120000_add_set_partials_to_score_states.rb new file mode 100644 index 0000000..dfa961d --- /dev/null +++ b/backend/db/migrate/20260525120000_add_set_partials_to_score_states.rb @@ -0,0 +1,5 @@ +class AddSetPartialsToScoreStates < ActiveRecord::Migration[7.2] + def change + add_column :score_states, :set_partials, :jsonb, null: false, default: [] + end +end diff --git a/backend/db/migrate/20260525140000_add_scoring_rules_to_matches.rb b/backend/db/migrate/20260525140000_add_scoring_rules_to_matches.rb new file mode 100644 index 0000000..d9cf751 --- /dev/null +++ b/backend/db/migrate/20260525140000_add_scoring_rules_to_matches.rb @@ -0,0 +1,5 @@ +class AddScoringRulesToMatches < ActiveRecord::Migration[7.2] + def change + add_column :matches, :scoring_rules, :jsonb, null: false, default: {} + end +end diff --git a/backend/db/migrate/20260525160000_add_billing_and_recordings.rb b/backend/db/migrate/20260525160000_add_billing_and_recordings.rb new file mode 100644 index 0000000..82bcb15 --- /dev/null +++ b/backend/db/migrate/20260525160000_add_billing_and_recordings.rb @@ -0,0 +1,47 @@ +class AddBillingAndRecordings < ActiveRecord::Migration[7.2] + def change + create_table :plans, id: :uuid do |t| + t.string :slug, null: false + t.string :name, null: false + t.string :stripe_price_id + t.jsonb :features, null: false, default: {} + t.timestamps + end + add_index :plans, :slug, unique: true + + create_table :subscriptions, id: :uuid do |t| + t.references :team, null: false, type: :uuid, foreign_key: true, index: { unique: true } + t.references :plan, null: false, type: :uuid, foreign_key: true + t.string :status, null: false, default: "active" + t.string :stripe_customer_id + t.string :stripe_subscription_id + t.datetime :current_period_start + t.datetime :current_period_end + t.boolean :cancel_at_period_end, default: false, null: false + t.timestamps + end + add_index :subscriptions, :stripe_subscription_id, unique: true, where: "stripe_subscription_id IS NOT NULL" + + create_table :recordings, id: :uuid do |t| + t.references :stream_session, null: false, type: :uuid, foreign_key: true, index: { unique: true } + t.references :team, null: false, type: :uuid, foreign_key: true + t.string :status, null: false, default: "processing" + t.string :storage_path + t.datetime :expires_at + t.timestamps + end + add_index :recordings, :expires_at + + create_table :team_invitations, id: :uuid do |t| + t.references :team, null: false, type: :uuid, foreign_key: true + t.string :email, null: false + t.string :token_digest, null: false + t.string :role, null: false, default: "member" + t.datetime :expires_at, null: false + t.datetime :accepted_at + t.timestamps + end + add_index :team_invitations, [:team_id, :email] + add_index :team_invitations, :token_digest, unique: true + end +end diff --git a/backend/db/migrate/20260526120000_update_plans_three_tier.rb b/backend/db/migrate/20260526120000_update_plans_three_tier.rb new file mode 100644 index 0000000..acbbf4f --- /dev/null +++ b/backend/db/migrate/20260526120000_update_plans_three_tier.rb @@ -0,0 +1,9 @@ +class UpdatePlansThreeTier < ActiveRecord::Migration[7.2] + def up + # Plan features and slugs are applied via db/seeds/plans.rb (loaded from db:seed) + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/backend/db/migrate/20260527120000_add_staff_kind.rb b/backend/db/migrate/20260527120000_add_staff_kind.rb new file mode 100644 index 0000000..fe06683 --- /dev/null +++ b/backend/db/migrate/20260527120000_add_staff_kind.rb @@ -0,0 +1,8 @@ +class AddStaffKind < ActiveRecord::Migration[7.2] + def change + add_column :team_invitations, :staff_kind, :string, null: false, default: "transmission" + add_column :user_teams, :staff_kind, :string + add_index :team_invitations, [:team_id, :staff_kind] + add_index :user_teams, [:team_id, :staff_kind] + end +end diff --git a/backend/db/schema.rb b/backend/db/schema.rb new file mode 100644 index 0000000..eefdffb --- /dev/null +++ b/backend/db/schema.rb @@ -0,0 +1,212 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.2].define(version: 2026_05_26_120000) do + # These are extensions that must be enabled in order to support this database + enable_extension "pgcrypto" + enable_extension "plpgsql" + + create_table "device_states", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "stream_session_id", null: false + t.string "device_role", null: false + t.integer "battery_level" + t.string "network_type" + t.integer "signal_strength" + t.integer "current_bitrate" + t.integer "target_bitrate" + t.integer "fps" + t.datetime "last_seen_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["stream_session_id", "device_role"], name: "index_device_states_on_stream_session_id_and_device_role", unique: true + t.index ["stream_session_id"], name: "index_device_states_on_stream_session_id" + end + + create_table "matches", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "team_id", null: false + t.string "opponent_name", null: false + t.string "location" + t.datetime "scheduled_at" + t.string "sport", default: "volleyball" + t.integer "sets_to_win", default: 3 + t.jsonb "roster_numbers", default: [] + t.string "category" + t.string "phase" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.jsonb "scoring_rules", default: {}, null: false + t.index ["team_id"], name: "index_matches_on_team_id" + end + + create_table "plans", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.string "slug", null: false + t.string "name", null: false + t.string "stripe_price_id" + t.jsonb "features", default: {}, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["slug"], name: "index_plans_on_slug", unique: true + end + + create_table "recordings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "stream_session_id", null: false + t.uuid "team_id", null: false + t.string "status", default: "processing", null: false + t.string "storage_path" + t.datetime "expires_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["expires_at"], name: "index_recordings_on_expires_at" + t.index ["stream_session_id"], name: "index_recordings_on_stream_session_id", unique: true + t.index ["team_id"], name: "index_recordings_on_team_id" + end + + create_table "score_states", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "stream_session_id", null: false + t.integer "home_sets", default: 0 + t.integer "away_sets", default: 0 + t.integer "home_points", default: 0 + t.integer "away_points", default: 0 + t.integer "current_set", default: 1 + t.string "period" + t.boolean "timeout_home", default: false + t.boolean "timeout_away", default: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.jsonb "set_partials", default: [], null: false + t.index ["stream_session_id"], name: "index_score_states_on_stream_session_id", unique: true + end + + create_table "stream_events", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "stream_session_id", null: false + t.string "event_type", null: false + t.jsonb "metadata", default: {} + t.datetime "occurred_at", null: false + t.index ["stream_session_id", "occurred_at"], name: "index_stream_events_on_stream_session_id_and_occurred_at" + t.index ["stream_session_id"], name: "index_stream_events_on_stream_session_id" + end + + create_table "stream_sessions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "match_id", null: false + t.uuid "user_id", null: false + t.string "status", default: "idle", null: false + t.string "platform", default: "youtube" + t.text "stream_key_encrypted" + t.string "rtmp_url" + t.string "youtube_broadcast_id" + t.string "youtube_stream_id" + t.string "privacy_status", default: "unlisted" + t.string "quality_preset", default: "720p_30_2.5mbps" + t.integer "target_bitrate", default: 2500000 + t.integer "target_fps", default: 30 + t.datetime "started_at" + t.datetime "ended_at" + t.integer "total_duration_secs", default: 0 + t.integer "disconnection_count", default: 0 + t.string "publish_token" + t.string "pairing_token_digest" + t.datetime "pairing_token_expires_at" + t.string "timeout_job_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["match_id"], name: "index_stream_sessions_on_match_id" + t.index ["publish_token"], name: "index_stream_sessions_on_publish_token", unique: true + t.index ["status"], name: "index_stream_sessions_on_status" + t.index ["user_id"], name: "index_stream_sessions_on_user_id" + end + + create_table "subscriptions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "team_id", null: false + t.uuid "plan_id", null: false + t.string "status", default: "active", null: false + t.string "stripe_customer_id" + t.string "stripe_subscription_id" + t.datetime "current_period_start" + t.datetime "current_period_end" + t.boolean "cancel_at_period_end", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["plan_id"], name: "index_subscriptions_on_plan_id" + t.index ["stripe_subscription_id"], name: "index_subscriptions_on_stripe_subscription_id", unique: true, where: "(stripe_subscription_id IS NOT NULL)" + t.index ["team_id"], name: "index_subscriptions_on_team_id", unique: true + end + + create_table "team_invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "team_id", null: false + t.string "email", null: false + t.string "token_digest", null: false + t.string "role", default: "member", null: false + t.datetime "expires_at", null: false + t.datetime "accepted_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["team_id", "email"], name: "index_team_invitations_on_team_id_and_email" + t.index ["team_id"], name: "index_team_invitations_on_team_id" + t.index ["token_digest"], name: "index_team_invitations_on_token_digest", unique: true + end + + create_table "teams", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.string "name", null: false + t.string "sport", default: "volleyball" + t.string "logo_url" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "user_teams", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "user_id", null: false + t.uuid "team_id", null: false + t.string "role", default: "member", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["team_id"], name: "index_user_teams_on_team_id" + t.index ["user_id", "team_id"], name: "index_user_teams_on_user_id_and_team_id", unique: true + t.index ["user_id"], name: "index_user_teams_on_user_id" + end + + create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.string "email", null: false + t.string "password_digest", null: false + t.string "name", null: false + t.string "role", default: "coach", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["email"], name: "index_users_on_email", unique: true + end + + create_table "youtube_credentials", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "team_id", null: false + t.text "access_token_encrypted" + t.text "refresh_token_encrypted" + t.datetime "expires_at" + t.string "channel_id" + t.string "channel_title" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["team_id"], name: "index_youtube_credentials_on_team_id" + end + + add_foreign_key "device_states", "stream_sessions" + add_foreign_key "matches", "teams" + add_foreign_key "recordings", "stream_sessions" + add_foreign_key "recordings", "teams" + add_foreign_key "score_states", "stream_sessions" + add_foreign_key "stream_events", "stream_sessions" + add_foreign_key "stream_sessions", "matches" + add_foreign_key "stream_sessions", "users" + add_foreign_key "subscriptions", "plans" + add_foreign_key "subscriptions", "teams" + add_foreign_key "team_invitations", "teams" + add_foreign_key "user_teams", "teams" + add_foreign_key "user_teams", "users" + add_foreign_key "youtube_credentials", "teams" +end diff --git a/backend/db/seeds.rb b/backend/db/seeds.rb new file mode 100644 index 0000000..6b145d2 --- /dev/null +++ b/backend/db/seeds.rb @@ -0,0 +1,34 @@ +load Rails.root.join("db/seeds/plans.rb") + +coach = User.find_or_create_by!(email: "coach@matchlivetv.test") do |u| + u.name = "Coach Demo" + u.password = "password123" + u.role = "coach" +end + +admin = User.find_or_create_by!(email: "admin@matchlivetv.test") do |u| + u.name = "Admin" + u.password = "password123" + u.role = "admin" +end + +team = Team.find_or_create_by!(name: "Tigers Volley") do |t| + t.sport = "volleyball" +end + +UserTeam.find_or_create_by!(user: coach, team: team) { |ut| ut.role = "owner" } +UserTeam.find_or_create_by!(user: admin, team: team) { |ut| ut.role = "member" } + +Billing::AssignPlan.call(team: team, plan_slug: "free") unless team.subscription + +match = team.matches.find_or_create_by!(opponent_name: "ASD Eagles Pavia") do |m| + m.location = "PalaTigers - Milano" + m.scheduled_at = 2.hours.from_now + m.sets_to_win = 3 + m.roster_numbers = [4, 6, 8, 9, 10, 11, 12, 14, 16, 17, 21] + m.category = "U16" + m.phase = "Semifinale" +end + +puts "Seed OK: coach@matchlivetv.test / password123" +puts "Team: #{team.name}, Match: #{match.opponent_name}" diff --git a/backend/db/seeds/plans.rb b/backend/db/seeds/plans.rb new file mode 100644 index 0000000..8429dec --- /dev/null +++ b/backend/db/seeds/plans.rb @@ -0,0 +1,56 @@ +def seed_plan!(slug, name, features, stripe_price_id: nil) + plan = Plan.find_or_initialize_by(slug: slug) + plan.name = name + plan.features = features + plan.stripe_price_id = stripe_price_id if stripe_price_id.present? + plan.save! +end + +legacy = Plan.find_by(slug: "premium") +if legacy && !Plan.exists?(slug: "premium_full") + legacy.update!(slug: "premium_full", name: "Premium Full") +end + +seed_plan!("free", "Free", { + "max_staff_transmission" => 1, + "max_staff_regia" => 1, + "concurrent_streams_limit" => 1, + "platforms" => %w[matchlivetv], + "recordings_enabled" => false, + "recording_days" => 0, + "phone_download_enabled" => false, + "youtube_enabled" => false, + "youtube_mode" => nil +}) + +seed_plan!("premium_light", "Premium Light", { + "max_staff_transmission" => 5, + "max_staff_regia" => 5, + "concurrent_streams_limit" => 3, + "platforms" => %w[matchlivetv], + "recordings_enabled" => true, + "recording_days" => 30, + "phone_download_enabled" => true, + "youtube_enabled" => true, + "youtube_mode" => "matchlivetv_light" +}, stripe_price_id: ENV["STRIPE_PREMIUM_LIGHT_PRICE_ID"].presence) + +seed_plan!("premium_full", "Premium Full", { + "max_staff_transmission" => nil, + "max_staff_regia" => nil, + "concurrent_streams_limit" => 10, + "platforms" => %w[matchlivetv youtube facebook twitch], + "recordings_enabled" => true, + "recording_days" => 90, + "phone_download_enabled" => true, + "youtube_enabled" => true, + "youtube_mode" => "team" +}, stripe_price_id: ENV["STRIPE_PREMIUM_FULL_PRICE_ID"].presence || ENV["STRIPE_PREMIUM_PRICE_ID"].presence) + +Subscription.joins(:plan).where(plans: { slug: "premium_full" }).find_each do |sub| + next if sub.plan.slug == "premium_full" + + sub.update!(plan: Plan["premium_full"]) +end + +puts "Plans: #{Plan.pluck(:slug).join(', ')}" diff --git a/backend/lib/json_web_token.rb b/backend/lib/json_web_token.rb new file mode 100644 index 0000000..4e62d4c --- /dev/null +++ b/backend/lib/json_web_token.rb @@ -0,0 +1,15 @@ +class JsonWebToken + class << self + def encode(payload, exp: 24.hours.from_now) + payload[:exp] = exp.to_i + JWT.encode(payload, MatchLiveTv.jwt_secret, "HS256") + end + + def decode(token) + body = JWT.decode(token, MatchLiveTv.jwt_secret, true, algorithm: "HS256").first + HashWithIndifferentAccess.new(body) + rescue JWT::DecodeError + nil + end + end +end diff --git a/backend/lib/tasks/.keep b/backend/lib/tasks/.keep new file mode 100644 index 0000000..e69de29 diff --git a/backend/lib/tasks/stream_sessions.rake b/backend/lib/tasks/stream_sessions.rake new file mode 100644 index 0000000..81eb644 --- /dev/null +++ b/backend/lib/tasks/stream_sessions.rake @@ -0,0 +1,50 @@ +namespace :streams do + desc "Termina sessioni attive non in onda su MediaMTX e rimuove path RTMP orfani" + task cleanup_stale: :environment do + client = Mediamtx::Client.new + online = client.online_path_names + stale = StreamSession.broadcasting.includes(match: :team).reject do |session| + online.include?(session.mediamtx_path_name) + end + + puts "MediaMTX online: #{online.size}" + puts "Sessioni da chiudere: #{stale.size}" + + stale.each do |session| + Sessions::Stop.new(session).call + puts " ended #{session.id} (#{session.match.team.name} vs #{session.match.opponent_name})" + rescue StandardError => e + session.finish! if session.may_finish? + client.delete_path(session) if session.mediamtx_path_name.present? + puts " ended #{session.id} (fallback: #{e.message})" + end + + removed = cleanup_orphan_mediamtx_paths(client, online) + puts "Path MediaMTX rimossi: #{removed}" + puts "Fatto." + end +end + +def cleanup_orphan_mediamtx_paths(client, online_paths) + removed = 0 + + client.list_paths.each do |item| + name = item["name"] + next unless name&.start_with?("match_") + + session_id = name.delete_prefix("match_") + session = StreamSession.find_by(id: session_id) + should_remove = !online_paths.include?(name) && + (session.nil? || %w[ended error].include?(session.status)) + + next unless should_remove + + client.delete_path_name(name) + removed += 1 + puts " deleted path #{name}" + rescue Mediamtx::Client::Error, Faraday::Error => e + puts " skip #{name}: #{e.message}" + end + + removed +end diff --git a/backend/public/favicon.svg b/backend/public/favicon.svg new file mode 100644 index 0000000..61eeea8 --- /dev/null +++ b/backend/public/favicon.svg @@ -0,0 +1 @@ +ML diff --git a/backend/public/hero-devices.png b/backend/public/hero-devices.png new file mode 100644 index 0000000000000000000000000000000000000000..e109db98198d3380f0f18e3a7ecf520ab9a5c557 GIT binary patch literal 107111 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 H=nwzda!goE literal 0 HcmV?d00001 diff --git a/backend/public/home-step-invita-trasmette.png b/backend/public/home-step-invita-trasmette.png new file mode 100644 index 0000000000000000000000000000000000000000..4bfe9de0881e2612b1241d8698738a1a550266a3 GIT binary patch literal 56201 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�H?FCSFw?0!3 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 z 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 literal 0 HcmV?d00001 diff --git a/backend/public/home-step-tutti-guardano.png b/backend/public/home-step-tutti-guardano.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe8a964fba0ae917318ea53dd22d64c63f4c0f2 GIT binary patch literal 110827 zcmb5VbyOTd(>FTH0*kvQ$P(N&!P!L@4X`W_AV3HqSa5fOL#fZGMoxSFD( zxeiiWMNL!rzk*HxQ?P{qz`@bo6^T%QJ~1+eLgxOv$DPc=(#`p9`+q4IyC0RW_708riczr6pAiL-^P#ed9UWB##Tz65}S zd;lOe0szW>03a~=j|}tof58UDh!`-qoG>42;1ys6Kmj$t5wHXVFq8-&1PB9!XO<1HDj@Y-<63A$#T1{r&9fc_XzvMalXX8(Fou^;w(fsW%* z<mB$-(3R47jSsc}O+7NVXP5hU}w5kxs=&NQGGd z@$-D#mMm}t*GAefD2%HC6{5vbRJV+Q*pCXb|E*C$Z(O` z0OtTu$BHH&fU~ue`R0^EyX3i90M3L4oq!0erxdZvpmFWeM&x_?lZ+gl;7@9D5uiEO zSP2y*3vi_qMh%z9uwWbJvcmVy4-E;U(Q|5nlF8^$Om_<>QheHjt5LM_!iv6dDd(WWV0x z#y*1!Dd5qmLXS{{++puDq4aCXC@V$?9vDwn?{aW&6%AaH4`q?!vBR10!$PRR-Oas)#ATev6--73`la zHyoowDv39p5Ie*IbSulfw*cO#2e^e}s2$3Q=U4J*NGiV%NF zR<2e+L5j5nXl$eJR3TrX0z)?CW!NL3VW4z2E2y(h627}U7Y+%WFIz%mM@t8D%Bi@< zu4OR!s35`IXBk+?z7%{Mf^jDAR-cpH;Mk1}w%9cV8Rux&LpamBZW%IPE+7F5xd-0} zPC`zRsjEU_)JZ49+6h1b{@Go08gl)?wq%jn(P98;o6LD`DrW+CEeR=;7wjXiv5g=| zc+zcwcbNcnO-I$aMn)F`t>hi3ify=DIDPk1=w7f$WNr)rK9dc;<2?PmBI{qybfOIA zXdEEnJ^ei2oQDEYnr~iq3?;TtrkeF+lzKFi9X!$l&XD7+naGgAKFk##MW#I*X#re# zY`pFkA(Q(JrO=6G?V&%(R0)X;V;~wF0tb2Xfv=UScg56od?k?1p8x`9dpF; zygob6I6h(1=7Qr0P;$+~1%16QVxuVa0S4^%7x9Pk8^Q8ST(UY;;BKk2gIEItKejQS z$FoS9Yf05PKiOOzTX}7MREWff9#hioB1Yf zYBZUIQM$J42Ig82m!m#LK;m?6YGtB-w_;c zj?pZH?bQVKog<~9;#9?Tn?5m92zUv9vJJCRT1fd`-0rkHa zsq!%x74I;@s4Np`%!0UV5w`%*+XdLN+Sr9&Ur*T`#`qSxd628q(VcSJK6421$*1EV z7_1@rss|x|($j4_G~CU}&sbMCoUWt<-(68lFt90LZLV&-3Ys49N6du=EWofC<+U-E?{>)rSbIP&heyn3 z{upe~*=MjTIljw|xxy>GR;=M!mc1W$P5I)~4~qV>0K<6a#$Sx0qA_W@1poZ|00W1D zwjikgIR>=8OIUe9`M=dqkn_kO@-q&>@1Sce(c|bAlr=jZpVvi(hDMU>p65Z z9zzD-jnAD{$4Mamhu`LFE*LXKq~>f5BRGW(zoOT4G4(9Red`Mb3<93PSSB03@8aIk z=c$+~H{oBnWq;Pi{uxijmNpuzbLKfK(_4%OJX)LmYYJE?bI7-Lf@c^P9?_U;#h&)6 z&CmYDOBjwx!uQ}x+Jc|`3z%jKragzP-1$N8MMC**yg$JD7cp#RStZ!tJHo&+!IFzm z)F(3Z27xh7fVK7a7Ep>_ydn6BIZgY`=Y&Vy{aXP0;s9Qkvj$hitNX2b^IP^a|B=@;QA4=T7=FlNFgxYD@eN=Z5jBWh! zKk{#vgIo%K&BjTORQtwm2o%oxj{c+lJ(7n|(GTNHH2S6~m(s^EC<}7|m?Mij@Sp{R z!agtmr>?R$G?)atxJb0~K0_0d`!KIzPR+65JV7n+1)KTtG0#siZ>Vy5pTk{9IGN1`9gTPmVcOkyYz@WP0 zTBhj)SBuXt-i3_JshM=G?2b#>ExN5klXtnS!Q3Vs1X~-p(pQ&2(O^10TGB(C8!oB$ zvv)id*O1_vdmo!`0p)ksL38ERw}6HqH5HNMh6?EnHH8gdy%A9x zKW@6n1T4X5wmZONcW~$$6-Vxnb!QSs2NP zl@8L7eQ}^?3Gv-hnTmL_I@-gb19)_rq+%7JD{0V=wF+`g zHEbt62y+{2x&TxDb5u?j+yb;|$OMHk-CA+&*9ix5(vWz#SO7j&dKGYC#j+-q!gt1lUl1QgxvdavwsfVV z%EV1kaL?!@H{C9P3P_F!GB(x0^^fny1K7Jk$A}P+b_)THps(cJeL6-*@{|XXM%iiv zhtnyDgth_~{m9d7o|tFzj7TH{7d8T~BaXC@9)*V)JH~Z`IMfN0@g6v*AwfbC`#Ra( zA|WwwW>;k~ib8DPOc9~raFPN!Zp?5Q5`ybC!l*ldCz{YQ&lZz+U9z<4Ke61Op4Ydy zYxIyhw^BO_O=_GJXpDgSCwh9fuDvSv-?T5R>gpn%#g~I%uD80UUE%n;q$CwwxI-~eAX?|u1=s8BA@ zgA_9!&>5RA=Kw)Wv_3RBBAE#f8;O+3K|}zE5Ty(%E*2a-5F`QHG69Q)ez8Zi9i@(C zq)HWH0T*LU%>>9^N4@JdlR*OoX+;lS6964tYmwQK^8hQO{>P9uqry^mnh)+Az8C8g zTG;SK_h@H(#P9IklbN=S%5|}$wD+i$i&FIQQ!6<(3JIr}1C^FTPodoK?Z%uzMp7;H z$vR_E*{82RdS#bg-cK{_JYAgfPjoA)FH(=L_Q$YJ*9)lQ#VK`v9rfcTku)z=9W=bHHftV$g* zwU9&#yBJ8L_4VV3i7!7^@&AEJZ8V7&uew2XVMb~=vZbcmpBn_M7 zUZ-Th7C<(`o=yRw@i;QUU$g6r-o*ukdivv^Vg;{I_R^|}TPN431Tb;lv}s!30?2PA zLtec0ifCKSXL}?k^)M`b9H$6;u1RBOZ#gw9JP~y)34Ds+7(|2?&yy3hE>$#snu7Ur zGSTh;ieB;&IFO0Ij3mqwZKdc2#V6D;={SdRS&6Zc1^ebvu!fcEep*SGK++))M!C=Xg%R~q=?z?-J7X`5hJyRdEg@J3Kz0Ws;f$23uAfol-J8-f z^tJjv7m_fWr+J1Z6PSC&Ok&VgMblnz#s>KSIHQ%6AV##Cd~rl5SQ#WE5HgX!5RoMH zFKTB;{*&z|=X1z9<=ku|sLz2j82^OZOM7+LtGOg+ghuEQ-D2Lewee4oe`DKEdQcNL z9B7vbzIPQ7nZr5%% z-1iEjkE6~qAG_5BX+>QRetLgHgmLh8aD{pF=R%g<3T*V}X$-?H;C>7Ado|}+#2+#4 znS#(?n0tRNL~DKuGQI^O$TD`!f`n(WLMd+n`B@x)p1i%pTY&0Xz%hsiBc@ut%)j~Y z(>=Qm<9|+nzv$B~V7wb4D`0VLf5Sz@hG8%v+*qzU{He$Cv=@1A0UuUFiJPih zpg|{G==B`NFr~1JH70UJuTAA(Y4N8K$$6T-f3I!^sj`J-ZY-N{7>7Dm(nIQx3g6s8 zo>A4;ie5Fp>?APzFSvlA-T(yiiT*&*m*dj29G(@(IVPf1(?fTdu%gyi4;iCf4COwH zZ<_C`j%ZzU`0G%$)29FRYR;XOVaBws#sf85?9~R-_;MV5mcjj78-s~_`X@|z7RQw* zx8zE5T9v`OT|MSeGLx)vFCp&r9%{)}KH+KmJI& z#q>fm9V25k6b!>0Ky|b2Ux%i98o$qrD)hN;%4VsfRNkww*?3h(1#%I%4G7{?>1#|8 z_pFF^$jYA-KM$iVqvKvoU?S1UVqxoRfrc@M$QJCMumA_KC!GE4k$y3FtN6i<>FWMp z&^>6v(VtDo6R(#A@3b|jTRF8{OCpavWbX&K4$Ykt()lwOde{to{i=tQ%2qtF@Egnf z^1k$5ijTSGnBTLUfA?kk693#6$zHy0@@Mb(WNsi@jlQ%MlFzsluYPy7GoM-gmNdvm z$22{N_qo8hlUMNQN1Uu3epzhKckF#PDD;!;rSCHF|H6;=8>co*>hiza6WjZ1-g{a6 z`C@#bVCP=C3jFlZ@wgTaJ+nrAQgux93p<1oh=)}XY1KOd7K^y^sP`S4STSGo{ zRaO$0%<8j^x+7OfRtXEUt|4yoQ`0tfuqy-J=g}Z`fubq%1C5ck-|`5T!h;>#!X?c3 zKd0C?Jua#$mp*RH{hi0(>d&{p5hQPkYyTD0wNb8g=@QK?T1S5m5tei@nqH05KIQ)K zVvP5Tu5GambNbpKZu$oWBd)2f6{7+VX#p~swucOqnq3eB>3TQg`E3$s*2y~WW7E;Z znPT^5((S~yCypd#8c*$>#|a#etkOV8Jn&Q&sRA`9Jlx3)@JOc(o=&H19A=Yf}6w%lsCkeB=FRDtm6 zP*hy~LJkiazFYH`LDgV2rSf~=2xTzZV@fgI|`Iywu3+AIYKNLn+d@5L=a zj*`iDO-mw*s|j(mtX3BGO=S@-*!=4Znl^4zK{ic7uzIhQ!*6en4JdwGg}l9d<)41t zA--dyFtMhm|0K_4!i6O})iSZo-ae?OubTcP1!`mf@${^vGrZhtpkj7<%j+_*O^ArjIv{Q;Ywl%$6^4x&m4c2`Q!^QUnm($9UAAI+-$G1vU|B`?hW8C z_cHEDW#ZvP(Hc(tIWfg_m*;M$;oaAN3b(FaE*fp+!@QLUgk1aec=#_^RMQNU$h4WU zIi#TcV$)61;gcMi+SwH%8qotz>`%wWPQ=wCe97KeC8xXSSg z%W>((W%)u=xhTTGt;+POX*!@qZtI>0TLtmkaMeVH`3E}=@FM_9)o*B64#j=JU__~xi%scN`_mO%oA&PgcO z;54L6YHm6kWh>Atqoe*#5_#DVL0Ry3Il{HaV`YwYrx!v33h=GdC^-p;x#JVCU!PJH4=cvt1u0?|W!F<7x(U52= zC`sWS#ad`L=saV83y!zXh5w5(Kentach`m39^S=v1~jD%Ts)5cF0ee)HoRbkRr5>2kyOuVvfW#M?;FlN-aV-`#b!K`JjRLn2ibV zon*aRhxZ9nr0Zf;T1C>*%O85fb#{7!e)`DOWT|Q=+S-n|ez!_yk;P4kZ$^)co_Lml znzYT8S*x@z3>!zJ-dz2Xh+Pd{%ZP^tOG!Oj@MejP4PX7yW9mCx`f<#!*s8z%QOCMo zojl!v4JR>EOK=fu-^&x zUv<0-x+~I}-^KROiJ&*$z^ni`vTq9zV#IzZ=qQI{N5^^%I%eZugrx+sKT7(OIjQmu zlLFiL(ADv}Ml4|ep4Q`b@=0KTwCt$&a^!=b;$W`@Q@CG#(Cdd{L!e_XBC{78MhKPA9Tvnu9&9B2 z-4$xmv?J%I>4ehfH}kIVe5A%G`+lRucNG1UESb+4<((sg4me~2w-uAWT=4z6$iG?F zu8VE74BAQLs`gVZtu&gN!~Gc-Y5DD7>^tey$Bi4g;gW=Jj2*UA@#kV=29dYG%205V zkBi&=WUPU0I^&YWQm;YCraJso*1n`vs=SJFiZdiv{L?WjgJK+i(WTeQDw`U5t+SBCj{4>I($?rn+bQ_So?vqg=|DE213hyllGF#kTc6iQrFm`hbERdPt?>V1t!&=mz_XK^lEBhOKISIpQm{yd| z4ujd(SL2chx2xT-+tX$BnMYbYPr{X6OJyGK*w=p|p4%k#Bz170v$adtxJz-v?M+Rx z<)lBg@avv^Jfgryt0TQGiLdU*nz9S72(6rpxqe&pPJI0f>cd1ZRMk4F%3yk5+uUgW zr|;JGsruQInGJM|c;}d&_1tRMQ*hk3n^&aUfxM{TM)h>m=no<{o0puu>WJ%Jld|C{ z`xbX!-?Z;R&u3?Xnu{Z3fd_u_tJ)+v=jBx)l z@_h|Rsym#p>8O}i_>x{(frXBu%a>mYgA3e^?6$fQ^Msl-tTNw8vFNm9qc zWb_UMU{gWVsfnQ)sUk~3!$H*Xt^hkc@qufG4z7;6k!k`KCyUYn<;IV#6X`b@B@T?$ zQFfEbVl~QOak7Eobf}El9M`1!M5-lsmkQ56-tUnXz5x~AO^WrolFHX#{pbwp`5XOt zamLdA^UZ-FQ;Xy9QD0KjvSVp%Bb3x%+pDHA^CvCNud{1`?WX5)DRO_jr^?QYG@H`|A-NF$Mrp`Y zZ$4wmLj|d#|L`L$V~DgS9DvGA+XcRh+G?zwqsvTgO*{>AohRPZ`E zV_chNM<)rFMr_YD`^NpJ6U-cr-A43uEPlDrJbFsKq(& z$=8Caro4e+Ujwg=0LjNZC6Br|-p5soe$4&s$u^y|J^ol8+ZoxP-5F!7aH&`BbosE# zGoyGcL8_bdOVQTok1zg@Hj9O4UEMX4=yExo$&(r!otx<|G~O_V&O*1;=kxlksVFx& z?DL-7FH+tSL^^I~_y6{pG*;{L`P%g3lZHa{rwEaz@8^d8?}{!NM|?fgRffjQwocPk zGV-V&I%^p{Y?o*Kdf)PyEzvJr29xM*L4j?>rR2%v7gG9@@w&Eai{)*+&(MWgHF_fv zl8nKlnh*RUUnGP2=%LnZRyKMv!3jX!PIo=qNMq$jp6xb^P28@VyW`it_W$lbcwa*j zhiG_HmdzUH8zU7%Yf&7i@iohaUpa59>V(`xzmRcyBCm2p*+|>$D%gaftfn$HHb%91 zX}>#1CM=c3Pw*9tjb!NZ?uq9j>W?;KnqgREm%~2Njr=GaNS0iIY<3!hI zd3NK$Yku-SH71(A=41^`x@qc|2@IDRfY_d^bTz#!Mxl_Dy%sq{~fK_zF~ zrpJ#qj7ll=6~3w#pK=)cQdaiwAJM>PwB0YlKlDu7v6au3A_aaK`$tIYZ`~wK z>wA?C)J2ugQx^;mm~wAe=AvV_EuW{hA5Js&+OSVy!8tKeVIm3fHb-9N-YCEVDl-FccSZ-FH3OY2>e<#8OFQOhCD>x$D1Ar9Tsi~}-!JTADZWt(+I zM%uc$ma-as8amJ57nRn=Y0Z50CL6!=V?(F$BW(M30-SFqZC7e`4*Xr>X)zg-_Dl`O zkzI#Mlzjf0Iem7;(A5Mud+k7URvqTh8#{r?bI2qoRefD-W>ha5ekd2~8b2z)&nduJ zD#`qEHe@gtBH(y!K$34Btc0dx zZD(8#Or%?HZ{wQ6CwQA2t+M{;dAnVf95#>z`!zVyO+Cf+>4-IFA*CFZ@^{;V1rGb& z-`bJgFj*`w&bvI?>Wg09^3!jNb13%4r2G(=l;22l`L!;M%gX|QE2yGSxkE43aed(8WFlzrrmz3% z^4oyan^|qX<45Xe&)zxum*#ofBR)&CQ$G@t+-T~q8A7}&M(0u}qQ-OFO^-O;T>;gp zp(ta2*>L|L*Eo8C#1JTJu;yNSH13WBGtT;1V3TnNyTl0d{cZCKx`6a3`nm3>m?IH2=F z(rnk4-`q>0hbF1eR2#gdNft|E0H@5yF5vB%wVclvcOmW79q*f@Qs6gB%q8Hvv}`UF zU%|xq$-Vlae)uNt zo)JE#n-J2Ih^aR=R#3?d{)?CLGsF$KP^Tx-qAzob-o(XLi43dHjUq@GRw?Ij3D!zx zIejk~-3ob?9jI05g;C0HK|4_DBS@#5iD~_h0C9a&b1kPR&c;^tWk`v~bd?&+2E`_rec>uVNBJU$*pxaInHbyeNz8eB z3wCa+(oIfy?;FV%Qm99PPTs$s|I500MFW->m#M(yO}LkOokucx@ulCHaML+j@vd8A zUwl3#Kg;R1d+W^Na%y3;GqpJP%|`Kl$nv9%oNj{&-kF}(xaTsz@QsgYF6zuzZS$fh z*E(+qPdLjQm?s2Mho(Q4L(QRAiPttt=6YFO*4!gH6gd$P|BK=0zF4u>zm)sG`bKFWI?whw z$MXg!+g=KydHS@AY@Y=2JI4OHRyMM}((+yXN_ZW1Ijo5!C9PHG{~A0p zYH0oFvPkqo9Pb_Tb@<1NsL}Zi4RhME{SMB3yQN5{jsfmhUYL|@@Fs3{wxj3h@U=Vt z4JH4(hyE^$R)gM+`?cwA5epww_h7~mG<`Zbqa&c&m*0dIoBL3+bs{uZ0hu$ID~zc1 zviZXg9TOb1F{@16nS3Y4fy29rPC^=njMi@^SVOYqwq9J(+)Ihz{kt1dJ)*SU#~#!E z?315oT437}Tk$7>WA}f?T~n4$s2n*KhFjl5fVY- zhlB}w%+m6>+++kSyJ_T%S$lM+{!bD~_cKMiYZwu0M^+eC?Aqs6Ra}A~Ux?C8EGjGF*`+!M3Vs4($hm)8<96oFL2`fOXk= z<%i$LWSDhH4kIO&)E%_b7Z*C!ueTIavAzwbyh+wHUM?+m`-1&~RKGrT;V}U2c{U`K z60iSHR_a3o*{*ObuV;+}pn68WZ}y-~HqG*>Z+gKWqr5Bifn768!|L-=V`7awu48@h zy1h|4-7@oIP9MyWg(Td6HLwC@ds(H0qsq7?&<>yfRp5L=-hC12D#`n|-ef>Oc@oX@ z&y>6H^EYA5(b|kThROpK-}*fDlLp==`!5diuE#D!56zuq)1rPN&bA*(_Yl+>EYr3j zH%$#&`{=pJ40^1nm8;R}c4!}NH0%7nX6u;v48fSoTxq4&Lz^4h1S@=rETY_Z>D7cSFi9QbYQA;J&ZMIrIx+2ODcpXUd;%5$(5 z|EZp5X`ZcBl;2Kf|LOgJ>J;0QjuDi!Bde_okd|Kx*M=L6>}r`-dR?dA`=M>ll`ij9 z?HR>wSNL?Lrj6G&o5ayr<1yX_KH=iYj^*S@+!#1UzJ-u0HFEj)(vGC8c1kC6E@RaR zJ;d%O4^x{*V*4(8cE$bcypU<_LWhy9@Pm=tO)IDR*Ow<|mPy+!A-0ZTXjY9?Sl;T!nR)ak_Fz zTm~zf-%43AZT+Qx_#8yCmn}xhN}WGl#s6)|MHeZ`1o~uohvKEQwzZuKsDz~9a}{xn%~b z!no>{rqhw0J}gC91yOHTYaKljV=heYb=OX_mGaM4{Zl2LuDW8ddgA#Wy(aeKI^)rO z;u+Z!Mynj|%AR)7b|amx`^=b`8Wq`I-a``G<-}gW*j=~%nwnLKL)}*g_~NuRHQw>v zTpwVHRcSFpNsnjhvCgGTtN4;Wisy0Vq%E4tOXkJ-CF+@T3_)8gVycBmbvT<_SlneZ zZPdTgAE=!x{o^@eudnEuHc{3iZX*2qgQvSdR`_+IzCfHUp5WwmKGAg5O)0&33i9!~ znxOR871WEuB!omALb7*5t&3Zs@28*YR#tlD=4_Xr!PwKqtgRUh>U?d(*|p8Zk4MKJ zDe7w=o+mQ3_2P>}F00sr3d4;l^%XbWfqDs~{qDvblEr>&Xy%}_WtRp$5jC*g3Plgc zXle+_C`0Y9uML5wuLS1e5KsBv>UUoytFB7?uoaM$I8di~&NnWcnh^1#;EeB`swF=8 zSOQj>4juz%BEvxr8D1JHB-#x1mH~Rd(IPz8V7e-Pwd6El(;Ji9DwZrxKbgin1i7p3 zMJ{C|0OVodK79c)JWH99)r$NS6N5rDcx?cI8)Wwqw)3upfvBYZvB`>Kbp5FC_A4Yx zQTxw#wF#E>#bJMxl?1w6Su^ACt>t`cp9+jkyUMQfkacRoH+9(w+3xpdu`|8u$*0b^ zBe~X3tDD@jX{(XxP`QeAC+2{o}}q$+EI%@{u}s@nHR{K`oupCXTmNIkm+v7l1kH{a2=qI`!v& ztW1Kew!P-QLAUGL7sQ9gL~nt-UzppRzrTB3fih>rWK&>}-f2Uc;GA*8P|6rP{dUcf z*{1#2cNh<3^h?EmTmX#c$)T$pE9R_y&e)oPjAyTcTIkLh0y*-lbj-4SEs6W zH31D`1`G^NKIEFc9?lfotiXm_mXr0Uznm@$2_G;<;9jmLzI7)7gAj; zpH{=oJ}uuuW-o#}U!e1a)%js__c^YyJe#8% zDw03tb1K>~^nJq(G-;1_3onw(=4#j@L=%P#O#>mVK?OIgp+@OiAGBjN?Bd;rHuZ;= z0_rE-CzDl1JjaZ@-&8fGx8(3YGL^7G!<4P1jI5T=P{}S=s4x)yWw`IO_=Ss>V$Hu=ph-amsQ~ z-BZ3J<^ld={qjHBTJ12-`>Dpg631|3>WjR64|m+AbwiW2qq6?Cby3OdX=z+~ysr6= zdrt7C4;E)9iyPhc?cake1O_L2)h*>JGOH)W9)-T4QCj=(@`Kfw?aeco{lp9fqiCLs zT()cd%=9HCW6V|jeGBw}$8f?USyKe#;Kc<+chP&HZXP?!*zG^I)2vw$d~=K+Ig`nn z47UlF)g}$?j9pEb@|y;yZP;IF33#MMNH%}$;UoSWQ>_wgJQKAhJQUt%Qg1nO(#Za5 zC?WZC;r8g}kG_QIiQkb$M3%W18dq)g$=oV~=T{RT*>CEB549%3LtmZLXk; zXzC4ZMjx5-a^#Zz=H~tzLdl@KyNkPEJpCIqyy#S(*7c40I%+_xKrHXei;>YU%d0xd zZWM?D)!R-eURLK%8~SnCDQz%fa#|)BdF%C5p+q_mWRJJCarz4P(p%WRIc* zRyomOi6ybQ#gaVbz99?pOYH3ZkN!F(FvPf4EvMopLqG+@o;Lup)XQFTso)EUy0p?TSg|@$B4ULYcF4 zu&eWbLFy~({?4}lsmgC_Ted0m_Qzj-FKP7agzrlXJR0SWmS6f%ui0Nj{D6$v$c?1> z%Bs%)V?Kdq8)fr~8bzUBMyU`jy7{-p8-;cQ#1fkA)$=(bBs7cs+MalJIb6%p#JpPD zEbi2vrz%rq=#^a7!j49TYcl#XL zeBu3m&uzmN_{m&q^!{bspSXL z%GeNzIrO#GB>S%3z%PU$YIHzBBRelVL}F4~FSyKW9P_f0v$o}@(}&GfJrdAk!o2<; zx>}x5N&bJdbkD_=9!vLeEL~HkXYGO`_tf)>ocxjlz4s4|wLA$DNFB=mJ@~O9pTC($ zlu_q!q~4{{-o{!JrI?cpT2O@lmKfw>n8|`BpSBw)kunv46F5i+$Qnk^ z&$~%?5Hp723w5%u++0F0(-#aS0`YV-J(dMsajC!>S=L#~a5W}wk_fn!4(LIc75o9M zUWQ3Z-7RqQ^m;Q%*3f8TuSXwq2T6wcW@IueK{K5D!&Q~Sp|Sh$j#`i(<`sp{wLz|O z2Vs@FhqR*eQ_&5%{ZKX^9Xam!*3c*&dX@l_h|Ndo-)M|Xs5=}j1%OpZjA%2PHT}LhFWru}Qax)1~Ic{@NLrf6sDOc6;@4KjhTwu3xzLr~WZ#ar^7UobWz9 zW$YHP4^iFggv7v`X2TC9S(bZRq_a;MnX>omp8CF}W$cpje;Al+kT*nSoHe=OTa9%R z-PsE%CEtUd`$WCnHkX*;;;5Lmv*Cfi>X#`SU-N%=LLPctTJUtMlzh87G2tR9D#Lkw zz3~-EZi&nl-kG3Z%Q0n?IeQcHPN4QpmbV;H9s7d!q7ih^X;d&OkbA_RZ_X{fMJDx| z#f`erNogtUt6Euf;l_iADEm;0Z_FwS#-0$`Iye_veoz+X-P*RY5{akm4WBOe20WOU*Dq2i?pUUQZ?vwexG-JxVxweTcJHxTifbu#uQjy*{M7E`V1~K5cD)!8&@J)?l}`&hWNe)T80oCp z@od`XRFuc%UH#ECi=KWsw7laiBH5}^DK3p*3vJezV`3P5`krCDMF8q&Xj@o$^11w^ zls)f$M^Fwe*P@4KCKZ)VY)v zCM$hk#*I(z?8N}KPG}NWL)i(O4{96tAMQ)=*reRF#H?84vwxd16OdFV{X(2pxA&j_fgBzBD?PcK3T|>$NjpBMht1oO$$o-w z`qKp0&M8;Pq@1sv_3Xzc%79^E-Fx1_w(i2)&^`%L+bPh?NBf(+?d$drajJi1m4qMu z3rZ6IqeLwj)4sMWtk;HJ@-K3JK?I>7k{h~;5O*@8k=;IQJt0I`AE6TJJmYrrc}l#{ zpEh?qhPP@69{p0IdHzm=wRe1C29rG8Vf+D*$JA@x&lTJGJd)zOtZkE(VCFtDV{5xE z5T~#Ai%%Ezuw7E|y|bT5Wr%5IoO~z2c=pPaWn5&b^VW}wOOo=m7V1A5zYtml0;Ty8 zQ3rcH*zznA++NT8$-7ira59BW1fA*Uhu=`>fCo@3V>LwHZ zEFjvoJJ|``0>d?_d-c4PFOw_&KxMoJdSLBFm>y}qN(JR}Cc~?)_s%GNfS@3Xpe&@0 zv4+)&ZW;A_|*JGwLk^$Zg!d`wF6`MX#NwVFf9Rrtv{ z@qdJND;H7}lTAi>M$x=P5$$>F&&A~7gXQeNGI5aZ^uT*AsnoB(`hL^)DhY@PbE?iE zaSKl#OhM=rDfT~=-^it!aBr%J4v}t;?dNxx;)|Nk#v^_gioZ(e5c24pr*k$Whh1`` zknkBD0^dE}#-+8NGRAk_YKFu#%{fymn@R0~rq%Pf&x0bs*lu6%7gd!h2h(Nv_P=)AM zuv>hrm8Ib{2$}Ka?(!BhC%1quTe6NDY{iMagf zoHSHTg7bA3p;E$bsJrJFC+uMHoXaFoTf}zVftKgPNB(X|a$OeLbh%LET$GUFTSmjS zk(Oe&(A1{T?V)AGZ+`Co4*=po9lwI#7OJ_5=%^Y*5waFU){~65hK@tcaTq{qpY7_sTxNC|Rd( zRMb75RMXVlnjH&`QHGU}v$-3`=La8Hw}QJa&p+(~?Bd(yOG0w5qUuS#=e88S?apkKnx6%Ix)4 zn!fsVE~&fSiE%aclT{M_r+dq}_^N_d)zH?)U6q034mjmz7|HTjruswNjHvEAG`hYR zu2dgMlZN`F@#_ae)88{sy@SbnXJt2tuDyclYgECd_G|SW#sWvI*y>@H89??ILr?Hu zIq3?QQ(o!Z)BR+59(a+b?*V&fV^1+@?$oKjtNFH>!`{mHG4>L%GAcVvx3;rONjm^8 zrInKObH}e$8IZxqDC5ZhW893WK*}j|LBvg{Z0{cw= z07bhG^$q*hGvuYF$@FWl_bvO%fjB@iqDpyCc|dhtnO<9E)|4_x?XgX|ndJBfw6q`I zEnh*7I8Jr-E&v^`B>RfOF;78cEO3b_Q%J5#?5qE5;bKH5} zeP5zKG&d(bCYkLR#8c`o=rcE}n)(-PBI0FjMz@(#GiG*a6zn zhn#>hoL~XM=C1a7`vuBdebSOz8rXwN;F-Hz(B_9XBih#k^{qEV(`YL|Nz zzfV%rQrl*Ag1A>(s;ViAr-j|;dSl!iGQ$2yBR)hdmOj<*mTN(vV`!vnE!QiysEsqLEWs_b_gOtcnzeHFr~wbe|wP>5YD z?JhmzosvmAj~_?K44l;Ie$uX5M%z(sj*{7Swaq&!jSb$AR7A;|FeG5eEp|9KI3oaq zfVnX2bKtVkQcHBW($Q1xGt|n+W882zIDj&J>nvfgZSY5Uz}vU#bhW~GqE>8Ph{@Vt)} zNZuShp*n-ScW)P|1#2H_)8?M0skQB;b*+wu3ZTus{B0!R-`#yJ znB#X$@ToWNCkoI#2IJe7qrBTM6m=B#hCcI7%6pY$Ht5+?RvK7Z=g2wS3}dDN$s`lX zs?vR;S7^#Q%1U}GWy*%F@yjJ8)`^ud_l(`K?FW!RbB6~69zk55Fno|sQi15u_WuB? z;&oJ${Mm4xdiUJpp1R)@&!wnyLtP{}7-tP=0~i}d03#}cQTBy+)p6~vnkM4uQ+R2D zFv8l2%M6YLcz#W|9t)Q0nBgcL?N4nwp08TeYKn>Js%|wDriy5#rDLh(fy~d*OH{{Xh{y#b2|#uG^J zfH=Zl2pp++AQ<+p?9ngDKr7bslPDyERRZ&F3`nW*rxGSGb zl9r+6!xwQEyP0RFaEas{%pD0@A-KuGRtBw=S&wEn4n6B5XkN|Wvv6qSszXS= zH!*>u4)My~uxZPVtG!WLEfjLs-794f)6+sdX&!n3o<6g{87BZN2APG~80W!#NAh*h z({^Jcbxm}QlJ_+ks-~r+s4aCRF^SSa>eZhc>5bm_G6 z)MQX!UxDiSN91p&J0sF`7d=@^4!G5BJ^Z%sNHJ2x&Ac)ulF>7><0FF|&H%u1^t(lm zPF$EP=rt+TwGjv zEAqb*)@pS*T61ak=Zg7y{JHJP_PTv8ED%(zqwOE$^`N^>zT7SJ)G+BSKqHNiTy1do zRFYH{m-kXc-^rJEcyViXNr!IrhhK}kMC``#*@mgCu-~8%)zkXSk%XA1jFYxl+(6vN zVBp{$2p|wZ{UC=?STydOG~LsrkC4{XIj7QQaU^$#f?VgEm*kw1?^hWfN%E`FaqBL^ zh2f{?3*MWpnpmzbaPMTIe5J3A-~qUO2|*_#*W=bC`+g1L^NiLU)3`npeQ*B&KOUZ^ znc^Av*flN8<7%~9SW0T1=)8V)NH93K_DEAEI*vL>SKyHiDQoM5VVW@RVP7? zg+QKqt(f6#jXt&yl2cP)WF7(oTscc}JHJ=}q$41bM*#>XpIE7b%2B(!c{l_RfJ1q3 zI8+ne$_vQxNzac1Zb9)1KTP3ED9oJYLwkulqE;BsD-)l}Sfe{0K|V;5Q0xq>JNQYj zJI9KJGfHsZH5Sb5^}t0-{{SYnn;|EZ2-!2e!)ZARQ%F0ufO%Tk9Gs-r^U4&@-xnRe z2$1qUixUYiIKl(Vk366=83KrYGE!@Dq#MpSLYd&1tOS*5K@^pM_f0OxCkma>9DQoS zg5fp1en{Y+GNWM|$r1mLN%x4&bu?uwSSwbRR&9#pC72X!Ic$O!robu0~8Amrt1 z$jIi;f%94nsxAr+tHycZLn9?cDPSe1D^11i4r${dOVZxreQpwvhh&qJgt11d$mxAX zhWIRgc?vw_;HU??apdr(HUN3gl7wt7Ymb*zHZh*Q=N_CWQA;l1FtDChR>DZ+_2o>( zhctnZf{ao6zBohW&j@x)$ODy}HKcL|;1i)t112(j0Y(eD1-96K$z0&voULy7?W#KSm{OyFSL#QDPtcQ8W*y5 zV_a!8uV@t~hRsh&27hX)um1q>*NR!Y!B7l&+#J)8a7GS#{{XVR15P^W4u`tcLMCkt z_0T~f9LDE0k9Y)mjOXiB0^aXj$J#5ff2fdux>u7ByE}!^-!s)RMtaU>(B_^wIOw?5 zkqD`?f<5XdoT)SE4|)){)@L>F{{VD8^11vg{bF-)i~FzQRRG~f)TDwiJ;={J5Kw@4 zKE%8bP69-uDKMTA8BG9+I-vNW0GC&wowfH*Z9n3;+=34T5!5em z)fdV=XFqCMaLGh%j#eBwjt3xv;6WK%l4?t&vRveK#-;LIGwJR!oYiIGL*9Out^>L88CbPt zK8Ed1n@UoReWjtMiS)KASw+4Jew#7WQNA}cM=Z6CCDJwd!<{Fp?cj`Xyvw%Rtd@@2 z?zQY>QcX(gDVQgTvc_E6Xh_`00@nW%9OK%Y{s}wQ}G|Jnt`iu+Y(xe1HSo0*MPgo7dHj)_tJ%n@v|k zZkXG&aGH$8Z-tUI<&;lz_cfu;aLa&kB(=E!fx@or>qk*DNN9TM+a*11(i=Vg>qkdZ zP6)4>85m#>ZZk7lH#jup5ZrWL7|zjkRCTu6w(47`skX#mqnpzt-%l9;7BJ_AkN_j5 z01F+%Ug}G&Z7pSO`sGtsOCzPHsFtorNekRTbC~Sqq?V4JK`2@2y`f!W*FBF~HC@tz z-$SSk=4*6Q+o+E#91fYBOX&Q)YI{w*h753XpDXf(e zRa@kyx5X6j$lHu@IGI||_wQ_u-MpNWo)%l~mbUAyU#@gTH3c0NUAt1laDt|u21fSL z$mYRP!r%eg&C$pQL5DA!B-9r z060}wi>+|4s%x21Rw`fK%-0M!J^9Ol9YH5PD$uj&TQJj|rPM`LC8~^CD|O>(si9;n zhkZL}+#rCtpb*!V#t6?P#PvB`)z$ZI(0et$)L*pq9Ubc5c%z}U8-1oaIUOvfK`j_; zY;OIbtZ)PuG>*Qq@p!Jauu%#oRnFQmFsKymmBKMDFdw%#-Htb7IN@(xwVt88%}F)F z{{VHamXX2q(p1+=BcO1`3k#ffo(Cfx0#LHPzLI-=Wc0N#$xTxO-4w9O($}%z;IxCt zAc8Qp)>@jWtEwfashUcPiCkooX^F*dFQGDVHn{sPm!us z$&L>{V^ub%sC4%t_Z+#|Cq8*2_$*mUj)g3#!x)wLJPxM|C2csSdq5zAo`?;LER2ff z3+4I^B%gs+cS^Wwo0$1?UDw#CAUS`yEsT-Rf~j{gZ?SIL2bx(WWP^J^J3!?|n!0dZ z8=OvfD@~R2Mjf@}WMHVkTQB;(Yv&^5OE29bWEySO%))g-}A*0w`eBXAfU86X(i{BF-)gE)3At=j&f z)bqBklBSZ9CKn0XMPGK$&3nvwBX0zkGz^8~I?q(LvgK)`fR@`SnTS{$v}}w9vw`E{ ze90%pT2N{x$0H=)Bp?7=Hg=5p4s)MyUpcAJ#l zo;W+YM#Avu+UE>SE=N}q3jy$Gc0QS1o~6{@li8lDb7qRBk@m{?swr?9=C}zMBn~GB zX#f`SkVyoVfbX2CZ;!Lx1e8w{?5W=Q*cri-Xxk%O;7Q@#0VEs~ozm?Cv}VEC?Z(Yb zC~ZxBOqBa~Wx^pbk>vaRL7yGY@+-!VhD#=+AC|ba^j-e|pVaMo*sN0B)}P$*3XILU zts`pCnEwEjHZhkpams8C6;U~)fE)n?@U9;_Jt@w7Ee<3WmW~(dN5!Iws+Hn1Chh~U z2a4W;W*R9OD9f-5NboBy^A%;)TlY%$ZhJt$A5y4=?k^x@f`G7?#A6xyP8?xq?FFAi zt-*{%A2R83;GLr-kE=^ASGvTw_|lCk)a%zN@$ONLGlkCzKZZ1KfO_cvj|EVo^c zn_!D1=Quq`QjLO1Hn<(R;dh;;SSU0@wB!^N(?d~HXojY>wGwXzXA_q+W7Py>g~mfr zd&bDtjo^cYwi8(*q>Xcf$1Soo-%cZLPeORgPHR}~9(c&Y*sU_uxzB&9%=vh%3=WCT zA;H)f3nLmaJ?30w=L-|#=Z=a@1;Cv6^hB*Ke>?>&v|=se+OkC%e3-~hsaF@ zvmE3Uux74hEEp&`|`afz+khT7)^-{$cNCS@)_KyXS$G>y2&QY*uuRcLr6hOGOGUq2N zw-({MM@1Gm$tKh8OB8IfLLmdMJgjC}A0gU!3eujIn9`S-Kycu-R8IU#{f8~{3IJ?wva~ydG zs*JTblMFZwQgm-Pbc5;g6lFoTc_K04$yq%gWNdtg0IamGWDR~sUU&g!oOhTfjjhRH zQo=@?T^NQ}ZQYUA$x0fi+#1%x3%vZ+H~Nsgo&&rV)vklmV069XVsvA64^pk!XwmMT z!zSUhx!A(mj+QYS!Fb8(=CWMs{W7K_8$fty+($xu*0Y&3Z*B(Q=Ws`w12mMbrl)Cs zHlGy;-X6f#V1eS5UwHW39PV@URu}<_2fGJzxC$USp)4+Yd3Jdo#f`AN(y%yVcSUt+ zEPW(~R(@5-V{)Z%r!-y5%b4c|?b<&ys9Ft_(NmcF;!AjN1EQc1x=M$L{b19a`xS#> zl3w}4)Ffx05UuolFc~CuL_4+($0M(jb>LA&ROg9voB@n`Rdqw0>E7eimDbjGH8)5a z+H)fV_c^hT&GKBebgY`Xq3mwfI!6|dA)XVb1?$OereY6#k3AY5ehhH?+!w9c;uL+6 zTUyhDS9qwO9>zr*KlK&m8hh2Gs3WP3-)opZfnKv!8>w_Aj;1^lO;J=w_mhwh!GTmm zjMgq=Z7w?--Z|}V;n%Kzg5mmQb~@GJw)h+RQs*Ave-+0cwPa!;(Mi8ZBvhC^Vedkg z+Q?$z5BEdwDuW$YL9EF6#vks7-d8{8g%}v%Bcd_F4_{&dA2f$4k?=x*lT#@KWdIx_ z2+9hekG*wT6~VtcXZ0V7&$wDH9) z&-AA&@q%_G$aBBDsjdFqkqe9)v1DG;PW2tpW-31cfE zZc(%#AVxw1c~im=3mE8;JyeJ(5!Z!4iZGrqk2L_Wgm|Zf0`W#sgmQp00KrX1phr)7pk$|&9OJ=1 zY5}N|%4C#}ngT<`VXXd_Kkc8rWy-}_{Vs3Ycix~e_#{b3kCHHeI-?y^=#2Hs1E}oU z4)ar)d@1Parax2<1MMMl*ZBmODM0AoNi zS01^?z+qy`*vA}(CkF$9JXGo@eM|Sq?at}Xf^?CxSCJ!J?l1#gj(!Uqt?uP}GmzI2 zpEL&jrkp*s5^deFqwPQ7t!rqSViB?|I~%e*QKjsaRo3dtn3{W_c9&y}ft-D-C3`BV z?eIw`+Q;w@iixJ_8924eE25%n-`$+Fk(`wm(YcKtusP@zjkL0&VB#>=w-0gf3$wAs zr%ffsma3JtbJW5G9bAuo>RAEZkQ(NLxB}l>IL>fZPSJvftN<&IpkpYu1u4S#XjdmAuY0N zu~6$}yHx3@1T`(Ex=@!$BrWRGfZ#`n;2*2nw11P;7P%NFxIW5s((d_Qt-hMn^kd*w50(Rnx;YbSFJ^Cd`IpJ0SDXYKR>) z4w^Rt8^-}HdV~}2 zR&9z_+e@<}n({DDMUuLm&Vne6UDT{Q9?~%2eWSpoY?@VQ&&~iQd9aJEHUNoVQxSG zamP75I6W7W>TMfcq3`upd*nWYUjc6T+bbmv4gtXKVJ$on(66F9X!%Xi3K^-i?YbH} zl#rT8ZU7HRjsi(@cY&R`Cj&k!<`YosoQ|4#Uo4Jr-uE5OKU(b4OD~p`<4G^6$E%Jn zF}OQz{TIN6Y|VFn4LrT%ZFYB`6|EI3_pmY9bH_lpCZ(s2T7eEsM~1oFC^Bd$o+gH4 zy_g_5cP4POZdBy@1Ckdb zJ!_MgZ@lf;uhDJq=Ae**tG+Hat5n z@hfusQ#B>ZN6zLs^L|j43=UQ`W$1HN0m1mj96GK09o4jxIQLDH4I|c0IyYRMHA`OB zwY@>=wW%$#%Tcm1xu>guRW$50(o9v;$1D5B$Fz8WwV5PuBm=j{m3LWfbQI2GnSgl& zg-|JjPBwHLY*HaZeEV9N~9YD(oA4n}qx-$h{&0{ml-Ozg%F>S?E{V`PlGYYqTD2>1xiKXDaAz1z%@ZoobzV5Xy%c|%yoI37+q;aJjH%w`_&=1kZ#>^H$($#ffA>Svn6O9i%`q>$%7hm82Ciz&E0jizf_B0QHK zzQv>}BxKS+GT;tI?tm<`hOg5a=rZxUmV@G#%`seBF<&fFTk0Ztk%Y+ZLw_`_dU#tU z!OWIV$>aw(c`FKdeLCkbsFY-K0Y9CUg1*}|q&|K-xegjniZ@P(m8)bcWu=C)qBua3 zot+$}_QzhzV;f6JZ>|_T6-@Fql?8_cxO0W<9h_;~R95+!JdO0}q7Gw^Mvo-ED0Oyu zEhMqi%~0af@G{o$t)iWg(bBcC2EC+`=MK4B`kviQY`;}QPWHM7df7a4;<@_C8p(uh zm%L^;1E4AIH%PoLS)QKREVVL9=v^f+2iC_KPI_ZOG%cxTa>Cur+&ZsQu}1oBA4KNF z)jhYp9$bh|SZG4mlrHp$WgWYau;lOyUHcj(I&^QLlVm)!eUG*lM zu~$$w;ZpMRBxU8T>ATAdG^>*zfx}y`vQqDGBXMprI)%-sr!LMNOtDNO3V)sMr|B<;CyI@CUu?%BeCf zOi=AFAp%!wYav9o0WQJhrTn8Rr_>UD8rV`Ym&~_K-h< z<_f;nCl^LD^de~cNmM0dL7*HEmna+&=76`>CpGZ@0CYa`s0USht9h=A`>*0v9x$Wo z1`&*u1a$}il#}}s`;Zbs0y>}qkxU?j1aXzqXtRNH{^Rjn(CE5d8`A0QgYi)Un-~bf zW5ESvLMN(VWeJaB0^Y_y$!=fVkG*U1w(LLAZhzd5y=s&Jo;|3_Ngx=LX*poH8iyYU{{Zsa_$)(&ATtV=`7%qm~~kKAwIgHs^=58Q9yv1c44PqhJ?(qE6w{lWMp z-^u1bbbbpEDd&U+PX13Z`=jttbBu>B6eLif2YF>+fIx0O5tf)jT?; zQaoNy+PxCk;YrzjOZpm%r^H7W{{Z%0Z=J_JYZg;(+5nU|^xPJsk9gwmGnKhSF&mxY z1CQDM3rHMcKzCi9>RBxm(or}EQ_AN!w2q{JKZ4c0)lVHkr<679F5MW<0C+0csVc0W zH&jT?(NsnyVFS#b75!eBma@Xzix^!md51SXx4~e(26e~8thQ2mMLkKC!IbLDo zf{wnDXKtFBoy?Y4nJA6#0mCh8oYw$PwWr*AEoi7@vDC!sxQLEXkkjMuT@AjL2GG<> z*oDSwsALe)KxJf*%gAH0BO$~LziUq;sT_qRE=w5uws*aiNl|^*QrFW^GKgC#Vyq@u z!=5VG!5n5F*0*4E@-_X~95sM*wNF*)K}Dzq4Mjt$E7Oe} z9UqOc2}#-R&fT;(e;B zI+rz}(?#mNxoIQG71KuAQy>j417^qS9$lf9VlH9LhRAWby{pk?1 z-(t4UQB=x0YLg^SfVQe3BL?A&fP6=R@LchtWG;Al8}X6sS?*BJYm!JLX$)h?$ruEz z!jtDlx>5N(tjQj0XB~SQ{so03bk$b80vj}AJQZu%e`S|0(eE{{^tRgU^f9_>>y*PS ziJ`7=Ye(lihMW!n0Fpoiu3OOLWiXyF_{+EhJQeAc`q*f$&uMH_GX_mkL+5K_b6oBN zo?KnQ%eDYoIyVP63m#>3R+8#m-Zka7Ng3?ALfkEk5ANSu=XaIW4jgkf8s>xg4cOl{ zgTVA%Rq}k+)=Eo}W~H)S^?j<1w=2Gup{;bcDkuv>i`*Q|k1Y;v=CRHrb4bV_nvTw{ z8WXjS#pwGsjuyqQW))XBt}@d{5p7dscF2UlZhKu02?rny^Om#@%8vg4skM#!M0T;J zXzHfdwmODcHAT6DY?3*RE$?LT40}7qxz6Fd56sGz*9gmp)bSe@zPMdn%YDLvlT%X4 zPTE@N#E;2n1G{%to(48x@^Q%vlhpce(BS@1*`$nH+bdZ;8>IZLD~yOm&=4u+aEVXo$ zm9K_6>^ts%_^c^t2I?B_?&yG&{4))fsUn=*YBL{)5;<_!N=LMia?;mraQ3eFJ<&+unx@Meqp4pV zIfh{V@OteXL0h#NdfX^mERwnxi1J3$*U58KlyfoSQqzW>Gv=)2)Du;*Sl_kV+sMaH za?gjtF}As7FLu~xYjmR$Qy$s+vf| zG;&W9W9sL(7#{9c5|WHurP$69l;ooxtgEA=bhLAGw;r&;@mjitlT6wt9$($^y+5^$ zId0Q(M_)`?MkjPovW6C6m=LtR{_^6ywye)zRKZRh_MRGi6vvi1Be@x5$1HN%`h}9y zSzO6o2rgqr90!8Dl9Ibm*;_lJ432IX^VF=GZr@XEY_fOV)XoK)PeQA;J<8!V7^R+2 z=;C*(&{n7Btd_qim-~Au{$on(eyFOYiLy9lqkcYMYP!f^YYcG{yCh>EFF>vAS6Ar^+|b^%-Bk@km=AMO=);=VbU59{Rs7#2 z-q-411xz|BwbbFClElz$EF**b;?}2btl06m#Yw79;B(!dO>MX9N+;i_Szm2-=7urY z4mjg0M^xQtZ5pQGQ*WhYR5JICik4Hjv>$5Rx^`8q^!?%+bxonPw+LJr+)G&oL*39H zU#}rvN%BYSe&H0ZmrD-y$JKO~I#%F)a;ThJ;q)?;XBF=!&qK;|-G;K|O>nD<6EqPW z(qQxpZrLj?SEy+)Bd6i6b4NkqSF7nilh*o$l-+4u=Ch7S5^>ag zR^a&|c9o}TsxH-ZwyG$p+GLVRZjj-P9Zmr&cIZ2jX04eMQE_~n7P_Oe)L3Jvwn6B! zQoCb;IdqNvBDu{YO4mqbnnD=xOIi*I!ul%J@;}#8(N;d=QK#dQp`oBjad9AfnHz$l zr}+!(qg-6;{+Wgbo*U_v2ahfwD;G4@-oZu1?(TRc;x^LUnE0d4=HwCLGvcnUHrMJ( zY8a_WWK4*;+mGiCtLPfvklv`dMGL=`I$oX8w`uk2-TwgY>cO8O?Lx{(!!^E~rehnl zy{xQvhbQ@g?N~OHQ|@M!$*#xA7HSUWwx+Gk61J76gV4L6U9Fz^Y^tWF3^EGFGl1WQS-%)y5(J`qNRMS zjtIfaTycyqKGpQ?jqH}5Qt<4x-qOA2SoT$B(ORCm!%=y?ReCPX^213CVf&cZ@tF^U zfLA@4{G4=$Xf$>6RNQayMN2ayV_4_`enB}tp~f&g`mIsT4ck-X>zRfIdduo`_u8Ds?p5j!*f0 zJAeJ$u4}ZPlg5P5nkJ%aOn1iD&ru8zzNX<6jmw>bmWW%PGvXFOTtmwTK)3JTCN9-%?UGrv74CRiPU)y=O1Q12gV z&)J<6x^|l51<{YT&m4f5>R4lV!0Crzt+J``nW8Ph+c>M`l6xIJduk(eMWAfm?L=%poZvpyDPIM)`klsZ! zS97mw+gsXY(q`>*eJZi}E!;7Z!S4E9RVQUI`d4V0O@w-ksbxd|0A;Rpr|b$_sw=Ox z6{YPF7~Evbz%DO-Km*5?=h=^P%@LeVsF+QBsyWC7tshfDsV56=?I@P(SnmTf{_?dJ zL<}hbk`z79Y>loz(#mffRDK&>`OT) z3(FD3CC~ez_pMiaR>h6jqq&duL+@IfXk_dId|>3T`5$WTE9&CxrkqpLIy#M0D>F4m4s8=i2WxycW5@V)sO>Ym{!=<_ zf+y8e!&4&~F^9Q;yY1)eSYXR1H5A*6x^J%)`g$9@Q$>n8X=RjS4DfMNjBje)y1KXT zD)*A4zIR-fLGoEm9R7|fJDEKkyI_}L*j?8fx@Xw1mK+y&4<+`6Z)F;Ub~Rg|X0lID zX|?Itudvp*v)8;e#(CSfJNbrJ!_%x`dM%$Ed}EIeg=|qMmssPrRsR4gb;BcT?tL18OHU&Rdn9SvT3gf*K^R#s)|%T5(yH5Xqn_V!rI5uvElg%PB6#Z8 zH1ZFyuXpVp)1)=MZ)#Io>=ibf)}FZfSI$njK3`1_tq{r|85>#aNx8c*fG~5_J#pCG zw*LUAb)Bxk9ZR)EuU9gNnwps>GSO7Kx{1y&B$FJ$pt$lxt;$$DIl5}-?LO=1x73?6 zYDF2Z?$2^3EiV!7>APqr9!j~)ATOfsS}#l8thF#{%XP-%K+SUvo*GIkgMq83Wh=M6 zz2Pyn?<{k-k?MzI=!_~|X{2=A8Ya`Lt=e|h*%_z}vP(s__btYrswYFvh7mTH1(#jw<6$9TQ`FLtlfOoR3s`q>@%xJ15b4B7jYJb%M9jbp^tS$57W+ zK=#qoOd{I=r(qrG<;CwL46p|BRk26S!MTUH$1kcM>=66oz6yf4wxJziaZBu2wvJO_K0+sW)@lvA47D$>Xx~+6_U>Q=aUx) z#>PbL+aq&>b{Dy>^>M(#&8KVn8%_TJ?7F)9ZHG=pa<|{(wt=RNS&k@Q6s|E$>DtjN z+GB6!8P}YiLRh2c-ybxy$w$Ac6(DDn^OBdr2nKKwpEQaO1yD2skWW0I0x~j!jHG&X zKs=*%62b^j4>>7?5EOvr03L^xim&=){{Xh{y=OdSVXOX^H|?Lj0f&I3JWx6#2mvsc z-hq?{VO;Z1*}bYWz?NFM{rN0l`(i84iu65b#?ff)H&2is^LUVtYvz&v0CU3g%w()e zSW~dJf>(d5KWWGDRk-KatWD8^)ap@8(KCn!qjCBpz&D^d#!s`uoYx;)-k?2J=U*Mm zN7_VEGB!|mQQ(eqv~sE)S5;GeY&7*v^mjNp&Ibd9MNc#4r+qWs`659ed4;(>wkvfN zH0-Ktq>;`piMib#F^uu!o-yQAcQwVnw|4w@_-(2NFxM! z<#*kK=}R`I?H=Q1t~=h@Sbxc@HV8Ypn&Bg6iJ^`zACMae=2GFnURg=njmAHlgjEcV zNE%>f2Rku`2M-YDk~;Is_P)cRsiUE&tYt$RT@a~-(akWHD8xX!b53@#o23U0ByNZi zoZ)e5AC(5|?7)_3sH3FqmsG!9-`_MmS>SP_wy}*ZYuLkwJP$V$pR5nf7~(ri)8@mV z>R_vz(fuk&*xjJCyoQND1L|+Fb=FF1Tb`q|!BFNl-5HK7lGx97-dy5vKrSW6oZz?* z1CF1exQtdVZeWZ@0--J!+;L zdmOd&F141$s^fE91nqxz*B0bsqQ$DOl3(a4VvcRbH)jWpt9L_L=pd%6tE{T0imD;H zHb^pFSJ6{YwvV5*=ff?vzKE;2P*&Y!o;{9vk&OeF0=Zo;UqfWW>f#k`drJ#5j48WS zTwhcvS(n9!Faqp@l(e0m-Ca*rQ*oR{G%gNgf2D)bY)K_Xt8bQS%`~4rF|FMdJ3y7O zyT~3Ewy|Yjrb25&*-@~PBp+21ve zV;p3Y3)Nkn!Hh5=;ScfQcBrlc*g`B{{V(PtLVPXExMEC z6WZOa9qZ3UwBbAp;#Yp7l~Kp1y}7;}gt8Q{C0EEV_f zW*xFnRn=PNucoAzP*;IK>gsxNyoNGKOI-f|q!>280H37c$0dBPQQ85}UB32++6aM$ zYwPC@Zvn26jC-;E(~9!-GMuqA`FtLxkwQ6B?0C;dL7vL6r4X~t?R@|KV zBn5z+IG&|_U23^HpIg#3@(~N-Yeqb_02j*^3l#NKrl?dlCNSd!@=EtPbyXbts@dqZ z9pr3=78yv#I~oc2<#X#3o36c0x<;|9>Coi6Poai?n&u4xmoE@Qq^Ur__rv(21+KcT-=gklIWV_ooYp+xlm5g*0&8C88hi%Sw9~DCC zdT&aZketZ(;ji$*_3eG~cDi;|L2Npju9}MBT}~!t6+h+ViKJvT_&90yFF&~UdwJ7O zpq{D#*4Dx*X^3!hWQ?3-5P0NZj-hsKb-dzJP3$V}^a6qAAv0!;tPab3{FlCbmMbVP zvbL495yPn{81_-k8vrvhPP_jANQ`lwi^Ex`>nq(@40voqd(V>Xdmg2lJI#G<4OG-l zqNLy7L~!8p;2Y1ya%y0wiL0ZxOE(P3)t;B~rH<978%*oeBH^fAPb4g%7!krrAC_@% z=VPE>J88W`O-Dr?Jn#p8Q+Dqa(Dsd0a=+`kxGUmd#l9}o>sRZtsFFYuFg#@Rr7Uke@9ar`>`xp2LZm-(JYNpqeNhxw$2OY*lpFV ze1tvhA=?}>+DPjP}byK7_uGUxM;YQC+elHCDt zY=)W_2DRb1^)uJ&SWn%rR%MUgP4-4j9bt)@hS=!{WQ>rOvA#j4mGxzR%J)!qO+%)% z`*ht6$_G-lvCky32bMPjbG#4uFBADUc6QF#duI8lR7-E5j*4oZNQr^REdXEx`%1&* zD9Rl>+Qk*Fd8e3LZ4HcfO=#cr`8hmzE_lWHo2Al@+2n)MEK&U1ujxr1g;m|rL}E6G);}LFg_ZMtr+?x z{?B6k)P{+*vfG3eP>7iu-eRbCcJ4v(=(;}48(C!378ez4n)HyIM8<-t!d&EyhTLF3cx$L*KtLMn*{myIt?viGE!= zpfS#Fm9sZ!1aEY_gW@?0({6MXh9gZiwvwWPI9(j#Vcj{ggPc9yz;YMM`tMxLUwpe( zeB39ce6E%`C}b>iBX0q&VasuVIsmz}lievTr)IrLMh|;_he1p7t-V<_ea}%-*sU_$ zE-WTZ&ymdojE0TCkD?ac$K}nbua?^CDeRUuwd~v7Ig+0Ac;J_>9)0rBb9DPE1-fT6 zPYY`(n&z;0g64yf^(}{5DP40Ok0qL>2f*p+wjS*7+t7oIjOBLJ5^A_S=RA^XPEED? zS3}e!`EP4jql!rF_8|eoTzwu=V?IanpK6q!Ej?>g>6jp}HdX@Wk42ORf#iG7E?1RM zS`BMyp^nECvDQDksuc6(tij>UAe?nj78z-vsC)GNWK!DPJGmO;nHpb=9I%IDIrkNo z=qE-B5^B#(s`+SZntFKVXtBBycFGw`AuM;0JN!pX`5z^UKbD5SsH%Mt7N)YICkP)M z8RWUAZ&#kZ50c=bwA<-wnoX9pQrxMVBcda`WJf0?WxN)Uec4(yR2I8-o^(ShL8v&Bd?mp$~_E}B)PKoAC#7>p`(&-NTY2-BxcsKiG`a^at?XUK*!d* zTW`wiv?tr-%#>_~Mh`=%o{APR$2j%3agGScJP^FjYCAnc>I-PDGRZX~T_a0a87RX} zaskAg=Z-ikb6G8{(WTF|T;y#fR>+ww&1~CTGU3EzZ3hRD)Rl^Gi>RYy0O>#$zuruo z&R0iqKgz4L=$Q91?`xs2iS7;po||LXMld)e5zmeYDh&Bk_LWZ@GFNJ9Ser9TgCL=Z zyZbJ0)J`1p~vDHy%nh{fG zqisxV5>vr1EQzmiE(3e>;tn!LD>=0quI$qow|ChZ_ukNNSBs4IyS>xXol;rePX#n% zaoM%KKn>xef&t^}R@z@xYj~{{F4Gr!p-C*QbhUK!tYaf~F^7k!kn z)?MwkdCb9pQV7}VwWlp{g{OBeX~E7hmC5Ql(D-OYMdq^Pc0JF3tV7Q8(7o9h0~yDH z#X)Z2bLwuET#g%MCtj9&J)>WCj?3jezU{0_#MKeR(?KYcdtC6`<+u&bamn02MSIW6 zimn)R9Xr@WN9Q&;5#Soh9sC0MBSYO_tnDj9TE!zI4XvQs`CR8p!enEQAc4+6IUWIh zH}a~o-7Z=lo@!c2<)x-{)iqKwGDIv9?GJa5%ZSglVUpY_n{mMg?;0}<5*&cY8P9pHE% zj2@mV#ih2|8K>!~McP-rv&}SEq&sDEn-g~O;kO%4I4h;BsI+PO{V$`@$yr}7cN=D& zKq8JfPj=(ofy+%n<@dK1duOS#0Zi;t8vR*U|R(weqCJx5}@UgVs}>vUoWWn^1g zcSPJZuMT%5tpJP_;IzlPs*T-txF3frwbxp%uJ@v}buCPelelec<9myaWSgE(U*^{p zK^c~Ob@g5MVPu_fL)H5uagS9^R0cf)(N#CNiel0BxGh#NG_mdgxq|`TL&xq`U7Izq zQ)@b`eG6%TAMYlOzi=v|rlqEevC+I9?XL$O1P+TdLxGXo+~*(8B&cVPVh01-d#J)kaX{prfh^06ZxD>P0Lg1iTZ% zS;}YxmqgOWQ~;g?VDKNUepIwQ>@c*=cwRDeoIISGOBKwDWd zDDHpUkG*P+t7gjZ+G~qXS41$iM^q^^Wh7+?Z&_~A*zUBo(0@Av!@$lmJV(|tKDF02 z&plPhUbKZ{OWf}9)Scs5H90*-4+XtP*~;MeW8979ry;}<(1pvF3!B{+Pp3;wd{OP_ zd_`%o`5B<)3*4#glx6D~Q>)_bH0gIqo%O#;MQf@zOFAR9B5L*c z@?lzCHa(EN&fe)tT@#a}xVK3pmA#cO3DZKx^M|bKf3ge2;N+np>V&cMw~ihYOP>0OfTaDb?3@n|!U8 zPD09V(3-n9kVcF!HY#cQ3{7;d#v+ik6Z^|cW@ za~&}UkQ__4w`+1q#&M4}&QX*HQqIyZ5maiKL#g*_Q=5S9xIg6$pVa5o_1Zl6;a=N2 zchy#naevj6lhZ|bzA{!-xIhellyh85T7HQRYh2UdS{Iyz^N(@^t1QL;nEt4XNREt+bSOD|I!t;4YNZbT+7>g|#~z z&hWGjP99Gr@*9k83(QOYr4r%S!6+V$KW?}AZ*-T4Xlyp?r8Kh4D(EcmLoAMxH^%<) z&2C-@d%l)==c;;Zx4m1W^!+WS-%&@hiLY%9T}1>FRnUk1Aq4MXd|Z-!QZPv)1$oAN zQIw!`Z`Ze%!>40?NFu>2=AbT(q_OXpzHxEKub1=dWpp-f-t~QsqT@6*baf`Sz4u2l zy4_Dy$ZFnl>7s0maEa&5nCLu$^P*5smkLlWY4u&!lT*j2;F8)F>EL6gjKwR=QLr$*%yAotBzR$Y_$b}g zP8@q9hvblxgGD#*p?KwFduRPq0r4;BV*4K&KVazqUPFu5Hp=uNP501e3C*)UeXS1ZHN3fhO44(Y%Qrmvwg; z&IUN5Pbeh_7|Jk=pdbUvN&f)W5Q_iMhZ#!r-G;u2gyIJ z7|LVnPzDnpYDtbz3E9C|>iDaee{B8hGe%Ad)}BuGc^~>tB>^1a9Z}U7<0u2EQUeM& z$ypg2rh8*=(s>OZS_7x;E~6%jpQoeJQp17lftmY3TpI1WS1lTzzBZhO)V-tJ(m?w# zxP^&8^0QJzW~hobw{x7}14oL((Ol^r(dsG*Sk^t!N!|v$Wv2r?kEuRHkK^V1Oju*sTpSs}X%6uNp*&}vo;Y52@`jkZP};5H zZuC;pnRbptT_e8+H{!2*GrwE3#cQf6;T3Oo3wINSob|vXKNaf|c5S2kK&z7ZC9mgF z*cxZ_Y3d^|#xMbUcM#lU41!0WRpjHGblQ#cPLb+h>WBL6Zu+jU;gTGT$BE@f;<@J~p}8 z2kCo0)y>sMJtchcxvz=A#1@`BSF>At54p_*b=J#nqTJ^k7;RM!A-)GKW)4*a{{Tuh zTKBf)b#*;#O%a&uU@=Gl=m>TS$x5=hOQ*?)i*^+l)!(ZAPBNMGP{kB-libKRhMsum zpRrj`*3;el$nJAZ6fnjNa#K^sE1GvS09xm>)#uMWIO?op(KFQ+h_3Vu1Z)m`n{qv+ zZCkr6xKZ4r54l?EU~^yI3VNow;h~NshWO(dYdKX*`5hYX-PAe_dTS;^+a4|^Ujsd`DfD90CBzB#<&k9i?>~TC(A&FZDywrMcavu3(b~yJ>TFH$Ef*kEp8cRoUf^ z)nbn2Nk{||H@cLU5Mhvx4;}>L>&Ke%`yQ^Aij|D@b~C_?@tl>$%S)m|d-NIU0P%!s3zC0fvHRxhc)8UQv=x}6|wHc>Rj;Dvx)P_)+H?~O2Uy^zdd{?Mw zYb6x)HcF@D#Ob7k#mABX@%XMcwYxpDOKY2jEsCyc>r;zlhIxms;Ng>#fu3{9-O+dV zyGpv3Buh`Cf;i zuZHM;A}`%apRdF8Jrb4r>e*eXp{=8}UK?(Rz9B0E<&pX4bI&B6cwSH1i*B*{J=V6@ z^Gi0ducK?S#z^quOMC`HLBhpJsjjeHpq4mesEQ_;PAZ(q9Mi*`=Ja>Ct)A0+S)#4% zmffQ1T`g5y*Ed~jWS9fp5Youl*LCC^pK`ivbNoYmbJdMbOn-(+TCR!z0QK}daraA` zJt?pUfjQ&@)GuS~YB=p0E%wjMP+j z=f_~qIr@fIX1WPuQE#g_v@@5OCa@AGlT?qMlQCaWY;{Jit~A3W)c%z7hGT@6%nF~DqO zZ*ClU<2hcEJ3?f-Vyc=)pG&A_TG9GA=%~AB(zC^oU&Cb8BqQ!rK2(@Y_VOB-At zu!i(L<;c|DSt)co+rYH&aQyb~`yAv{HadA9JKa?b)b{L%n%2nO$EI_G;-{x-nFYq$ zTH15n#@(_9VeN5uQa#HqrMMY>b&uv|x_;szB((dObB5$&rZeCbeXX|lYDUH&mftQZ z+1|_vXeG_y`_)iysjgWRe<}2*%NTcB>dgCnTXq6zBh+aVFiK`q_4q2LU!AMFB^ zSgI*LaC7|51AL=)P-?2VuUh9QTjG zZRxvp4`>~eRM_hSA6svEFbM|@kY~F-=VY+^$CBsGk>GKkZsyE5)@{(fdmlW$&sQ}x z73L~CrMf1XcT5|HIizqf4s-2X>%|$Sw7f-8OFJiY4gtaK4bM(+e0R4-A9E_YxBw@oG4jkX9cI@5B%fD|86fL#W(9q2t zJ;+A-YMvc5Wrv>*Gvuu*Ep=V>$?#ktbZ&%9*0!WD;Fg@=k;f{s?|Qe-CE7RJUl`oS zlMKXU{b1v&^cxRlG@6dZ4yCHCsI}Q@B5f=?*d~?1tqptjw2x3bjP)m=;bD6@W>n|I zPA;0=sj4HV^eU;OsB0tuK~x-EGB_A_Jyx~i(W@!xVx_B+uAXd3JKE{U9ou&E!N*cn zjeQi_My|5mLo{{KwX*D*o;PnGfb5V!a0RT$I^b6AtMb#*w^EXoF1ngR*s>$*HqEZ9Ch_FHM4By zS9k||R>c;mo`Umes)mY$Nch;s?uT$-?;Jf)PqV36G@i7U+j6OMDd^%8R2U^^avi!e zoy2a=4g#E~(9LaXte|ukifcq7cFg*Bw8%-^eO=fYKJZtsc8zAMQ z6i~(MLe{hfxbdD+`9N!Gh%a=MbkH{5Uqb^QNY7QRZ&rdDa7Y9%C%9hjcN23nO6g;Z zKC_7~$#7`NQD0gTr(Bw{Y3s9S>0^>6!TDKU=TykpfX>0maUNY3)xz6K$5KaAbAmG! zGd)P!=?mQATtFag{Nc`4wZ@`hYqmu@%R`}bgFZQt!zbXe&ux9u{Yg%Dy~IHt8q#)+ zj0-s5TQ$1rN2+#7W42Xx@24V#)4sYZqX>*SrZ%=F7annw^aqcDUs3#|E;3uYCe;jd z%!9t9ikrNUouhq3O*z0P7|Qt9vpUP(m)F$S>RNi5I*8?(NNVJV)NWeg#^6CDIIm&t zBeWaVvg~_NGU+p}Q%iBT(m>eZXn#HIjsP$W{{S}M1)=1Mnt|6HH0Op{)lz4P(Ai^> zZ9d1=(90ZTK496#Uei+clHB8P0iz&u>{iqjmivaXrfHSzsPt1bUy*Ni3%kh(adZ7& zRpXJI6-y0)^IYs8%@s7%kw#^soH{w0-R^$u5CQsKGoNDFvuTToyJ&$?JeX`js`MzvXTA zmXG&>o$^<@;{DHS@Wu_y>5YT!^Y*D&+q$DCz~|uCAK(_=DAU~ZogE>j(E6I7oO(r7 zEFVzs9)94tnp*Qwr0XYp7{`|w^2XzLPEVQjy89PNOa&)p5<56ESI++csgN`ewd<6T zhMPrFwEqBIA$R-4f9Y8epd6>dSVn!TAT$o4EafrOAq#C_b6*elL+>hwr-HoI-}i)H z-4DE~H5rh>Ml;F=LIMy9K=7NCGldFd38WK33kV@G;*sd61=8r>=I+1Tf6Td`1=8r> z=B~aUkpBRg3Q~@WUJ4;gJ_?wTJSUV9;+9aR)dfJOpVs;h1+MzmSF>w~@E$&Dzd2c% zQuY7{-qGRWgf>sJqK;NEQxW|Ar^&%S{w#ku)CBU8P&E;LvA=3YrZ3hv?LbeApq{D* zq7nULegz(t25yBMK&}57rNXQ8iHhw0sH!Dp}=Y^r(MW57ba{W-s?ar*xN!2z*^ zu(0}be^l>}_$c~t&+{(*O+lmM=@Wjkv1eUcab2iC$J?%p%@Dpk>F zNXxZ7E5}$_H2EGIB71@OfT2pwXKDGqXQR`0+S`33S4;isnyT$Brizu$Hp)2OK%c#1%N-klJ|3tXmFN0) zil*&xijIQX>dUKL-$d6vfw*rcEq4z0XOZT*Icn2K5^;;C7XC+V+4h@}x-VGjD~wII zLm*|gm5|e)QPPLV_eM$eTGp|-+DUM6Z#gT@E@o;gaI{>hMKhZ5GiHr|K1R8tY2Eql z$1Oh7j;rV`8)A}+v;9#8%ScqxvXWRUBQ=gKY?3^Nxs3n-8rK&%2D!Ow_LI=BD!5!D z)z^Jd1X84qSL_OE)e(G3v}`z_9j^DRv|jnVgpGalxQaO`Ek^u|V8jGi;#Rk~W` zreEJk8zn2^62nmogPQh`2+xS?}>rPbq%YN?+| zGHBm$d$nyuw#n)V+XEslmw*1x9fU(XxPEB4?;BW z4#y?Myg1+-xVW4T74dP^(pFVb$mxrm&sPNaAMsa?)Kk{Cxq|{jDe@e9lGmS zDW`nTVeTc6cayW$$#X%=ST>Q44qOkBI7KLaQ)HXT#Efq225ox<9p6jXsx5Rg%F*a` z<;;dy@^dU+v*g-14rty6avhEbIeXBy4#=RsdQE&vWBzbba$?+mKt}?D=*3e3y6Qa>^?ke14~zv>0PQH7b9n*z(pMFwg~Y{8TioU;;)#&RWFJR60FBL4U1?6wG@g@fvbs7~ zT<%d$BsF!i?0iHVw15a}fZ%X5wfB$!_X=sWOO2E+lIylI-ZA& zmG1hd({%=bsgpEr%QNp-G%ohO(SW=<8uPA=He^gS-Lbs`@<^mJbEaoVn@2@%|^`86m3K2Gn@r@_0?Z z(rkLLLoB4p3tIdi0&u-Iwdrea(b#Ub<`+d2Mb2Y~R}t$KXRI_;BvkcxQBL7r-K;*Z zAUEk}%L{ix)w26!o`#}0#z~RAqmtZ?Mn*>-Yv(1G&5Sdy99=)D^^wBx%6fOo zlC|_aLd6YDUa2$;sg^1ykoiM-!0J`}y^uES;Z;FUYlT52q7Pl(e?)SCuq%P?5o>`C0#|vf+Tf>nh zPWZ2i{fu=QMVA%wtD9bqzUPQr`#q$yT@6*%pol3*e{f^wM+3u1^7B_0tz%a;$_LcX z9POH2u{E)5Y;DE>0eYqLSJ?Yzwd{KX^)xX8+Qtk?D;hm!ya9(CbSIVMI)*BF^);rJ zm9kXH6N8-1y!8vwgfZ)6^2s#U8((ji%<^()%b<$Ds!Ck$UF);$$D!M3oA`9}^qRVp z=PEimd+KQ#eRdzw&*=LVZ)%s<+w}!3fs;dS?}W78uI7lz&H?jY!(-Dn;?U~g>Hz4! zEa_pxV__WRuOPl%D6D#|v~+c!I@+L+nB6;g1K0}R;yMg?r*o?+=425u+ydti z22Z@Nr`xG*FxiE=>)upaFBN;|G}vpRz~RmJaqnE+v$095Z`Co?OIdBV2&I~u;Vf8W zjSV{sUN|J2a1K{KEkm9i+jgk&_B|}vRy_L@x4CooI~(U=vUWptq@%JMedc%|idxC* z=o>pFml=BlpO6{g0085uRXWG9Z8fSpBmP%eu2!gZJ6S!#qR(z~+C3eQ4VkWq+3w#N zaK;WC-hpGN?8dsfqfb$3-FZje(NeY=+PBs@urhMzIJQDhTyfLG3(_<%$@1-Tu=b~^ z?U%b{y4Mw@)TRl+CcZ${Nm1S4`i;N?cR4Nt3yV*yl%Cnj{*-rWbczdnBVSwl9G}R` zU(o3beeX{`-7QT;`m!k7R>=TtKrrlyT|7 z!urqT)!7||-JRCz=SMsj8aml~hO$q|U`F1cf=|*uE5R;$D=e*-=H@udTm+1CCjdB? zHN^N6&KFX1Sto0Y;MFCko-A89bwhBmThJ-p|@jQJJP+bM2Q zUu5p?G*i+FxER>`*15oBcph@CM@}%@v}CyC`5dnU zevO2>(<81?Mqnl|Bx!CyIdf0P?OG=B&mPI6emr?2KQaw{(d-w##q{TBwkoUMhQ7r6 zw2-~ev)AzH-bP3n`Y%4hK|i9ta(#TXH8oHPWwh0LC~VO@9e{@b;pPrUOfJ%<`B9~7 zW0HnL9>cyF8J)5-KWg=>Pii`ISJE~+=9{-&9a9f=BTX|5-RE7w$JXb^1bMDl<;$j2 z*69AL`8P`*ElivIvi%?MeAQgbg-a@#3^CTV^z3ss2FOV}dGq+MeXF9^x(7nRX|-xf zdxWg0WHdA^bZ_op3?1WeMjA}7=y^Eyt^-~6V`;9{(o$(L==D>vwCuIi)VBrs7%@uY zfQQpEd{%t01qb~)9qPmGA!yle$5vKH-XD{ z-7~7v9hTiSe`;_~TFYgc^981=qNbWzfPz}Kw6X^JhXB3uN0$EpW!dpw4>iI#C@6Q) zQarFo24W2zM}u*b?_Q~4*OwhPWxdzY+ohV}T?X1JvRYdNO(Z?#q-2t082yk}lw7Y@ zAxBIc$VnnbxIUIM#z&i1W}P(!xZ}>#o7!*FU!#`gX5DV1FMC)Td$$e0lyKk!j;lgy zrm}`f;-RHs^qi76=Ow4da|=50k#Gc#9D2Na(An?PQ2zju(SXLx z($O6}W2;X+7f#jP9NV8}Yuv6?71xVltA+`l$Z5k%!-)+o1Ov}F&x({f!mf%M=SSQ% zryik&u5L?*I3V-wR<#i7fvByrN@Qbp(+54f1AdEgIQqh^Z8iEzDy{WqF@}&$Tx`MI zJa-Inl{Uq$QZnBp_-xd_U+%TA&^yv)e2;^E@W5O$_w+c(9>B2}@2}MHFq-*eZ0$HZ z%Xr7su-vKcPJ)+p&6w^^?bYo$JKz1Im2Vv@f0T6|k#vI$l2fpkyx=v*!MO1ASnW@a zg>rpa*8HA(HM{FwVO2>kZSG|pl8LJt;LL|GV}YN}UV-v~?B?yEyEhi2==Id~SBR>; zE|Jb8Ih67O!omUIJRJ1pRs53cY#Lu$-zzF_aK{<9h?^|zF|%=%rvoP>;itiMUn+fB zLs=$}k9n`R&tFkHC8DTk4%;0&I^glkx?rwJV~fhA-5v1FmN*_d_aDRipFKkzOggHP zos|yS%1GtI-InhZyRt#?U82Y$?-XM*0+qm@2R z0`m9Gb*p@lwiyhr-5Wpwr#yfM1L|9rA+?ep?7#m2Nd6^M$oD<&bGdOjS5{`?mPdbN z4~vidsu1pP?({y)Eb(+RGI@V~eV=m|J*WtR!)bsY>e}y!-TwfsbUmZA(`mgqOx~@a zrr>$Ibf5Zy=JlHmHB~J!;KLr$Jcz@8^sJXqbKVHYC;>B%6_JR&>5fQ30LdQoDQ&Aq z`Fvm9e-fzi%D&bI74ZK6bUyN^aH9Z(Apnm(5-rM?+L2L#kAErkNggVJrG)a4_aG0g zbXq~!j_Kvxek+^U(HI83*c@PLSCu_DEj+?F^>1OsuD&lywO!igE^fPp+G zga-4~OKO0K3GGknM=H=DTat~cfAy3pK*zNhSqMuCf&g%y6vIMfa)8DHV0iRW+L#Ch zgtn=H$_PM1X-3kg=8PaCgkzO2atZ13@)cY4(_V$P)-CE>3`oXkN*I~T(BAO zT~3va@!2&D?djb^2iE8B{{ZlmWk(rukj+~&kBP_Gg@!g4bPGLD2BuTty#4FbJ1VTJ zq;h#Y{sy@*BDAu|LX=T)! zZl=vl`TMrd98EN(=O=3(!dcK90D1+I$Js`*)V70JXtUScsN#srx}Ck|)@PC$2|r@k zpuaR_MAPXPO%3$wxHR6wIx3vNh%5!V=e*_O-5W00#h0cv+}M z4Y_k>zczUzwj5FG-I`n}sXHg4wbtF3YQ=1TS4mvwHjY!D$ea=*1*MMN$lUBWbjOU{ z>m<>T%WbEI8VjVrM^)VkJNB`^NAiw$B2at613<>lXkCtpzd>8s{O_r>M`e6BhO$vb z8G<%UA>A2s3tStL(#IcH0JM&ColSS8C%J2PRMzb?T;!4T^F4vGPf{@F#OH4f^8zbK#8@)}hWVHR>2yi$% zg7PcPjw+^#ssPN&TzwI){G&+5cC_;5WYLX^ju>bd+L(;bidh4AYs)hkaclQvl1Om|A;`*&IZ~q~ zB@34Bi>ImN6j3%dI60?(x4^48D&&R+PRH}n-oiSrw#^JWg5@210;M_7_g z@JCN}k8`utx6!T@6u3P1QkG}ndM=W)vASnxr~7Yu^}!)UlTh*l1`cj1Y1-F7Q6DLcUnF+U0}2oWE>QxV5ey zoJW$deb%M*G*Gm=X_=Y&j(>vYo(SDR@OER=%WchaeLdNZy`#{wNGqHjYjkanXjs5W z4F_p&BLoxI-@)jpHNR|@%dJUtaJC4;S_aH8z2BTjL)Rh;p33f zah?{gn9~y6XvNCsztO|CvjFWU8OC~N9N}tMqqgMDuQa#R5$}2@v}#M)Z3Q(8U`IZX>Da5mZ%d_ztP|WYueln@wo)F zj)Y~SgOS4MYWqmCRS{KbbLc7~2Q;+}Y|%&$1Pt;#dV}v6$>N?He(F2%=Sh3a=``DE zqPf((*1gpYna8=6yL-GIgOi@01?qA`WbGo$CZ4{xHB%5MYYAxXCGrIQ07)GPZgIE~ z$ncu2LuS6xnS)wp2P=dFfs;Ig`5*GjV8e1m~#1 z0Gtrv)GnK|YP#mtzTR~VHmE0-GczU)d!-|s4!eA^NM4`Y_Lic*X_zbFd{Wg?Md>hB zIk^=OesT44kXi%0!T%(!Reej(xON2#UxUnuo0f2gd7Ox)nLNNTP#G{!*yV{^T1bIS28KdT?5?oJZ7 zyOo)1gNAYJRn?cOo2--6HYnpVxRD$u4R5#-RXto6W=8jj<1Bg3UOi*(D<$FhG2)gu z-@VHZ#Wgu|MHo9g&`ii%< zHb%x`Hu+#LBec@iNdx4LIoM)uW_)hTT0Oo&Sm>MNvFZpasAqy{U@rBlm>$*w2qy#| zKE4ahkA&jrI*hhHypU)tV%|2cWYUixaq1C~8vDz-Z{2$Lfq`PT31UaXJ_M@^L zMGlwMFRrzUn^nGY*!=`b>0x;wgS?T@AGL4P;?Jec_?!y(<$C`BosNATqff8X{u5B^ zo*zno=Tv&fW;#x$p44kDaf*p1J0gZmxrMj{KL_8ARQ1h*k(R3ay?us)x|*7i(RYHK zjV~qKg652Z2a2|fwcA&F9CJq*xQ7rXVK|k|B!S(ZRCLBXRSn|DRV}`zx~Ag$MTWkN z&D~87+Jhm^V+`mAj_#N%;x$-x+TBCd<(Al|)0Io%@jk~#9WILk(_&kn(cAc+GHKl- z{Gp?{&-1G(A58xM@;W&qbBw^dkPm?a;=5W~-m9Uoy43~v&1=CC!1tc*kLVq=`F-nt zt5|4TIBZtEH63$eiL7-+W6Rwm{{T7OKzVWGxp})y`Nr*2MOPFSn=7fO9*0X#8rQtU z;TU!XPFzPm3-5gVnXok+Pi=Rv)4A~vOAK0=&UuoRt`++C>~m0FtF2R2Q`c1QrgW}! zhN2kRBODJ`7I5tSi`qLd*VPl~3hOOxV|4X3TjiEJg+xW>Xx>^jhK_cS-mB&IXw3-6U;(;T*0dqn{P**Y4FV)?4&ia;A!o`)P`XD7(TjBoYzph~*rCo^jJTUPe57 za;WTYzX!jYBguu!dXH{-{{YGKErV8Cbbn*7x6)myuJhJZ%M322^*h(NfzaTLaJVa1 zU^?~|Naxrp>!5W^az2_m$iFVY9myg6Mn_*Y%jq2{Zq-!JhRb?nQ?-P|E8_zk@ffFO$Ri)5L%BKdTJ#-&>v~G&%kwqD2%@Tr zN0QbPD;nXGknbS4J6CC%sNzbULwkZ3mqsUTM1`Ax$QjSHWqKZ&x#~?vX^wjRRZ%s( zHQ>AFBG(6wjdzI8n%wcu7TYw(G%{3DH9K&QU1hIotL$|KvW>LNrhL@R?;0d&Icdj; z%Hb9Wx|@9~YTYz#f~f5-C$8+^ z=goT6kL4MxbgeWN=iF)QVuSK;6|}L+@7``Vd)5%gnJ;>ow=?8!8(Vq0KGW=MN0?j(k_rGW@3L zD=8#p*F@=?gm3az@ zYVo6QO=L1zY9+s0p9`eMz$aiQmO4MqBOP0u`3{TIY;`v3+f`LH&XMmEMCOTEf?9Ew z;2dYx13dJ~=WSi2jU9i6H_$^QP&^SFjojA(;6_It;tJ7=CRn$%S<{vnq?Y$z71Z(x z>)Pq9(wa&_=p0xMX{xT! zt3G(rx^!M8scWRGrlf$rNSx9=eh%%V5D#8QnOx1oOx0Rx?*ieZE3NS0NfeU9JZ_MO z4^!3zX9S-Pi{8U5d0?f{pF5k)!#5tQVti% zm%m|QGjPo3mOIY_#dN*^kK3M%#H1o zyoWQ6jK_-0WS{kZf#!$&KT?acn=`FqxzBOS!vH598?(^+mj1qqqN+VL6j67Kj#=g< zyq{U|x1apA?tdrkFKA|)y-8CAzV07Y8(AlHyf+NV9mmxAdJnjyTUujBpnRl_j5b{{WPF?^y|GmL0YK0NLSo`+Fk3M>LYOO!`p40T>0Y zr!lyfd5oL^o`44LYe1{6sqTMYg&C}M< z133V+$imXX*W_c%ISWr#(oxXS)>N^(9w?g}WMd(&V;O_)$z4??*z;}=q>m%rxRue+ zMYh+U5?Ve0pZ#l{BsB24`yD&0B)3xa@9G-B2eh@H!9t_8*Hn$DzQIiC3{0UE(dDcT zKBP?qVDabGaJt5)((iIgM%}}9Gn{3_{{R)->Y4lfOLlnYHBy1@4j_HY&abj}mudZ& zZ@>kS4FCpHC`g?96#$!faLPe8{Uimxu^;8|{{VD8@~ARZ{;@yh@PBkZ@~Ao}2jGNt zLI6EFrT3tOeP{>QguqEO0zFbpgpVJJ09_uE<}0W7Kl3hg{<6AV95W5mKd9^b$bu>+@Dv)JB^-{`5gaS+>3f9-AZWmaLu)(sOze;&XWMk_n3X%d#p*mH7VfP4kc`>eV z_~A$SH3;im54tj;PJal(zdCxD59x0{=>Gu4A2Csn>*qe^H}ODF5s-t?bOOsk`pS}h z%^$@dG*CD7=iWjCpTaSP(2H#V9LFjD0B`&g^b31`sF42v^?$(u&j68bLg;-K-UswY zf9D^;1_}|!p@Z*`st$MrVRRkUCxsk;Tb=l$6wT>J2i6bbgeW%QA_uT8gM34!H9zhj z#V1P8c&C7Sqd;@`K?|YujC6_-{{Yh;f(2v8MnpgQZTt`%_S~ni_yh1=56&|I!C?OY z>9_Dl&NUyb$GM;(PJBf3(XRc}`-x9T-}|Ls8--8n-}M|Qn);LeaWC(XfTa|ZpXy}& z!lo_NF8-oV-74GDDm-c>Km9_FNtpGTL+^P&RtsHO=}#x_sS7Q0x>C#gs=&(g)$Ts> zqF&?TfYlZof&TzBlOE&?1;GCRn!oG9*d(5)Kw$n)S9LIf?{H7%3a`#Ex8EpP`h<@j zNJ9v*P02G;4}8r>%yM|tQ-9`#l$6F$rXQIcI@MGCPs)EX`1({|R!YoI2nj&OEX?t% zjDNE4!5=e4<56r|nNXpdf9OpXm~xbYt;A zw$cH_)93aF;-})6>EwXblGreNsYB0`hSId;W21R%np)x16>c5Q3XJeJrJGIz{{S`K zN6XxRs7UeA56c`u7$D$+j-?0y<14GQI%+)^T`O=QsHZ%-nb|L%|N59@8scH#;n29MS`DbCJ^Px;*H) zK2(Q$?XK&oDP2ujY}3M5iA0rfSt(+um%JmFJ**fl+5<~KEg8Y(XlY9MG^NS+n%iB} zwn|q?Zjvh5sNr*oEZ*m^2T06^Hw=4ncrNM{{*uz#iuXlRLr+9)^rlx!OC?oYQBzXR z-KIe9^{#&HrM{?ea3p6uuSmA`V}H0(*Z%-2=qoBJ+#g3xR_nYGM@>ABbf>$g+FIri z?3mf>6PFAUF&jxyoMR^MmdiVLYR6bvrgfjaZ9Ow-psJ?1v6D?38ymOEHh|Z#mW~~l zWDJr>E0pafmWt(~t(RKJT`cdB?2c#5*qU1YSY&svmtl@2q_BgYqYJH%Q|aw1QL6OT zn}w~Vb-U6fxV9apN|#F$9TYLU0yx86)ZmkodunwCa4 zF#(JR0$S%BXN|tMRnDNVbWzyyNylW}*~49?bv>2PTw`TTT_Xf>yS{ezwButz0D>dj zK@R8EXbu2jS~Ax)??BO9Dr%^xp`@MAckS7?W0|?Fdt(Lt>2P;e^v357GrwxFMOA04 zqpzZ*rk*B9^H()P!{=;(HM?8LA~!M4$i{PmO46&Ps

tM$%R?o(g%Sa3UV*^(~dH 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 Oc09MaV1W7G@BasREYfNK literal 0 HcmV?d00001 diff --git a/backend/public/home-step-vai-in-diretta.png b/backend/public/home-step-vai-in-diretta.png new file mode 100644 index 0000000000000000000000000000000000000000..e60637b29680290399d13449da217b124a75c5cb GIT binary patch literal 87442 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�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∾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 a, + .nav-panel .nav-link-item { + display: block; + width: 100%; + padding: 20px 0; + font-size: 1.35rem; + font-weight: 600; + color: #e8e8ee; + border-bottom: 1px solid #252530; + line-height: 1.3; + } + .nav-panel > a.nav-active { + color: #fff; + border-bottom-color: #e53935; + } + .nav-actions { + flex-direction: column; + align-items: stretch; + gap: 14px; + margin-top: 24px; + padding: 24px 0 0; + border-top: 1px solid #252530; + } + .nav-actions .nav-link-item { + display: block; + width: 100%; + padding: 20px 0; + font-size: 1.35rem; + font-weight: 600; + color: #e8e8ee; + text-align: left; + border-bottom: 1px solid #252530; + } + .nav-actions a.nav-link-item:hover { color: #fff; text-decoration: none; } + .nav-actions .btn { + display: block; + width: 100%; + text-align: center; + padding: 16px 20px !important; + font-size: 1.05rem !important; + border-radius: 10px; + } +} + +@media (min-width: 900px) { + .site-chrome { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + max-width: 1100px; + margin: 0 auto; + padding: 0 20px; + background: rgba(10, 10, 14, 0.92); + backdrop-filter: blur(8px); + border-bottom: 1px solid #252530; + } + .site-masthead { + flex: 0 0 auto; + background: transparent; + backdrop-filter: none; + border: none; + } + .mast-inner { + padding: 14px 0; + } + .mast-inner.wrap { + max-width: none; + margin: 0; + padding-left: 0; + padding-right: 0; + } + .nav { + display: flex; + justify-content: flex-end; + align-items: center; + position: static; + flex: 1 1 auto; + min-width: 0; + opacity: 1; + visibility: visible; + background: transparent; + pointer-events: auto; + overflow: visible; + } + .nav-panel { + justify-content: flex-end; + flex-wrap: nowrap; + gap: 8px 20px; + } + .nav-actions { + flex-wrap: nowrap; + gap: 8px 12px; + } + .nav-backdrop { display: none !important; } +} +.btn { display: inline-block; padding: 10px 18px; border-radius: 8px; font-weight: 700; font-size: 0.9rem; border: none; cursor: pointer; text-decoration: none; } +.btn-primary { background: #e53935; color: #fff; } +.btn-primary:hover { background: #c62828; text-decoration: none; color: #fff; } +.btn-secondary { background: #252530; color: #fff; } +.btn-secondary:hover { background: #333; text-decoration: none; color: #fff; } +.flash { padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; } +.flash.notice { background: #1b3d2a; color: #a5f0b8; } +.flash.alert { background: #3d1b1b; color: #ffb4b4; } +.hero-split { + padding: 40px 0 48px; + text-align: left; + overflow: hidden; +} +.hero-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 32px 40px; + align-items: center; +} +.hero-content { max-width: 560px; } +.hero-live-badge { + display: inline-flex; + align-items: center; + gap: 8px; + margin: 0 0 20px; + padding: 6px 14px; + border: 1px solid #3a3a48; + border-radius: 999px; + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.12em; + color: #fff; +} +.hero-live-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #e53935; + box-shadow: 0 0 10px rgba(229, 57, 53, 0.8); +} +.hero-split h1 { + font-size: clamp(1.85rem, 4.2vw, 3rem); + margin: 0 0 18px; + line-height: 1.12; + font-weight: 800; + letter-spacing: -0.02em; +} +.hero-accent { color: #e53935; } +.hero-split .tagline { + font-size: clamp(0.98rem, 1.6vw, 1.12rem); + color: #b8b8c4; + max-width: none; + margin: 0 0 28px; + line-height: 1.65; +} +.hero-split .hero-cta { + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: flex-start; + margin-bottom: 32px; +} +.hero-features { + list-style: none; + margin: 0; + padding: 8px 0 0; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + align-items: stretch; + gap: 0; + width: 100%; + max-width: 560px; +} +.hero-feature-item { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + gap: 12px; + padding: 12px 20px; + box-sizing: border-box; +} +.hero-feature-item:not(.hero-feature-item--last)::after { + content: ""; + position: absolute; + top: 8%; + bottom: 8%; + right: 0; + width: 1px; + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.08) 12%, + rgba(255, 255, 255, 0.32) 50%, + rgba(255, 255, 255, 0.08) 88%, + rgba(255, 255, 255, 0) 100% + ); + pointer-events: none; +} +.hero-feature-icon { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + flex-shrink: 0; + color: #e53935; + font-size: 1.4rem; + line-height: 1; +} +.hero-feature-icon i { + display: block; +} +.hero-feature-label { + display: block; + width: 100%; + max-width: 9.5rem; + margin: 0 auto; + text-align: center; + font-size: 0.82rem; + color: #ccc; + line-height: 1.4; +} +.hero-visual { + position: relative; + display: flex; + justify-content: center; + align-items: center; + min-height: 280px; +} +.hero-visual::before { + content: ""; + position: absolute; + width: min(420px, 90%); + aspect-ratio: 1; + border-radius: 50%; + background: radial-gradient(circle, rgba(229, 57, 53, 0.22) 0%, transparent 68%); + pointer-events: none; +} +.hero-devices-img { + position: relative; + z-index: 1; + width: 100%; + max-width: 520px; + height: auto; + object-fit: contain; + filter: drop-shadow(0 24px 48px rgba(0, 0, 0, 0.55)); +} + +@media (max-width: 899px) { + .hero-split { padding: 28px 0 36px; } + .hero-grid { + grid-template-columns: 1fr; + gap: 28px; + } + .hero-content { max-width: none; } + .hero-features { + max-width: none; + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .hero-feature-item { + padding: 16px 18px; + } + .hero-feature-item:not(.hero-feature-item--last)::after { + display: none; + } + .hero-feature-item:nth-child(odd):not(:nth-child(3))::after { + display: block; + } + .hero-feature-item:nth-child(1)::before, + .hero-feature-item:nth-child(2)::before { + content: ""; + position: absolute; + left: 10%; + right: 10%; + bottom: 0; + height: 1px; + background: linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.08) 12%, + rgba(255, 255, 255, 0.32) 50%, + rgba(255, 255, 255, 0.08) 88%, + rgba(255, 255, 255, 0) 100% + ); + pointer-events: none; + } + .hero-visual { min-height: 200px; } + .hero-devices-img { max-width: 100%; } +} + +@media (max-width: 480px) { + .hero-split .hero-cta { flex-direction: column; } + .hero-split .hero-cta .btn { width: 100%; text-align: center; } +} +.section { padding: 40px 0; } +.section h2 { font-size: 1.6rem; margin: 0 0 20px; text-align: center; } +.steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; } +.step { background: #14141c; border: 1px solid #2a2a36; border-radius: 12px; padding: 22px; } +.step--visual { padding: 0; overflow: hidden; display: flex; flex-direction: column; } +.step--visual .step-num, +.step--visual h3, +.step--visual p { padding-left: 22px; padding-right: 22px; } +.step--visual .step-num { padding-top: 18px; margin: 0; } +.step--visual h3 { margin: 8px 0 0; padding-bottom: 0; } +.step--visual p { margin: 8px 0 0; padding-bottom: 22px; color: #aaa; font-size: 0.92rem; } +.step-visual { + width: 100%; + background: #0a0a0e; + border-bottom: 1px solid #2a2a36; + line-height: 0; +} +.step-visual-img { + display: block; + width: 100%; + height: auto; + object-fit: cover; +} +.step-num { color: #e53935; font-weight: 800; font-size: 0.85rem; } +.plans-teaser { text-align: center; } +.plans-teaser-lead { + color: #aaa; + max-width: 520px; + margin: 0 auto 28px; +} +.plans-teaser-visual { + max-width: 920px; + margin: 0 auto 32px; + line-height: 0; +} +.plans-teaser-img { + display: block; + width: 100%; + height: auto; + object-fit: contain; + filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.5)); +} +.plans { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 20px; margin-top: 24px; } +.plan-card { background: #14141c; border: 1px solid #2a2a36; border-radius: 14px; padding: 24px; } +.plan-card.featured { border-color: #e53935; } +.plan-card h3 { margin: 0 0 8px; } +.plan-price { font-size: 1.8rem; font-weight: 800; margin: 12px 0; } +.plan-card ul { padding-left: 18px; color: #bbb; margin: 0 0 20px; } +.features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; } +.feature-card { background: #14141c; border-radius: 12px; padding: 20px; border: 1px solid #2a2a36; } +.feature-card h3 { margin: 0 0 8px; font-size: 1.05rem; } +.site-footer { border-top: 1px solid #252530; padding: 32px 0; margin-top: 40px; color: #888; font-size: 0.88rem; } +.site-footer .wrap { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 16px; } +.compare-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 0.9rem; } +.compare-table th, .compare-table td { padding: 10px 12px; border-bottom: 1px solid #2a2a36; text-align: left; } +.compare-table th { color: #aaa; font-weight: 600; } +.card { background: #14141c; border: 1px solid #2a2a36; border-radius: 12px; padding: 20px; margin-bottom: 16px; } +table.data { width: 100%; border-collapse: collapse; } +table.data th, table.data td { padding: 8px; border-bottom: 1px solid #2a2a36; text-align: left; } +input, select { width: 100%; padding: 10px 12px; margin-bottom: 12px; border-radius: 8px; border: 1px solid #333; background: #0a0a0e; color: #fff; } +label { display: block; margin-bottom: 4px; font-size: 0.88rem; color: #999; } +.auth-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: calc(100vh - 200px); + padding: 48px 20px 64px; + text-align: center; +} +.auth-page h1 { margin: 0 0 8px; } +.auth-page .auth-lead { color: #aaa; margin: 0 0 24px; max-width: 420px; } +.auth-page .card { + width: 100%; + max-width: 420px; + margin: 0; + text-align: left; +} +.auth-page .card-wide { max-width: 480px; } +.auth-page .auth-footer { margin-top: 16px; font-size: 0.92rem; color: #aaa; } diff --git a/backend/public/robots.txt b/backend/public/robots.txt new file mode 100644 index 0000000..c19f78a --- /dev/null +++ b/backend/public/robots.txt @@ -0,0 +1 @@ +# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/backend/spec/rails_helper.rb b/backend/spec/rails_helper.rb new file mode 100644 index 0000000..e90fdb0 --- /dev/null +++ b/backend/spec/rails_helper.rb @@ -0,0 +1,9 @@ +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rspec/rails" + +RSpec.configure do |config| + config.use_transactional_fixtures = true + config.infer_spec_type_from_file_location! + config.filter_rails_from_backtrace! +end diff --git a/backend/spec/requests/auth_spec.rb b/backend/spec/requests/auth_spec.rb new file mode 100644 index 0000000..ea29b2e --- /dev/null +++ b/backend/spec/requests/auth_spec.rb @@ -0,0 +1,16 @@ +require "rails_helper" + +RSpec.describe "Auth API", type: :request do + let!(:user) { User.create!(email: "test@example.com", name: "Test", password: "password123", role: "coach") } + + it "logs in with valid credentials" do + post "/api/v1/auth/login", params: { email: user.email, password: "password123" } + expect(response).to have_http_status(:ok) + expect(JSON.parse(response.body)).to have_key("access_token") + end + + it "rejects invalid credentials" do + post "/api/v1/auth/login", params: { email: user.email, password: "wrong" } + expect(response).to have_http_status(:unauthorized) + end +end diff --git a/backend/spec/services/teams/entitlements_spec.rb b/backend/spec/services/teams/entitlements_spec.rb new file mode 100644 index 0000000..011ac13 --- /dev/null +++ b/backend/spec/services/teams/entitlements_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +RSpec.describe Teams::Entitlements do + let(:team) { Team.create!(name: "Test Team", sport: "volleyball") } + + before do + load Rails.root.join("db/seeds/plans.rb") + Billing::AssignPlan.call(team: team, plan_slug: "free") + end + + subject(:ent) { described_class.new(team) } + + it "allows matchlivetv on free" do + expect(ent.can_stream_on?("matchlivetv")).to be true + end + + it "denies youtube on free" do + expect { ent.assert_can_stream_on!("youtube") }.to raise_error(Teams::EntitlementError) + end + + it "allows youtube on premium full" do + Billing::AssignPlan.call(team: team, plan_slug: "premium_full") + expect(ent.can_stream_on?("youtube")).to be true + end + + it "enforces transmission staff limit on free" do + team.team_invitations.create!( + email: "camera@test.com", + token_digest: SecureRandom.hex(32), + role: "member", + staff_kind: "transmission", + expires_at: 7.days.from_now + ) + expect { ent.assert_can_invite!(staff_kind: "transmission") }.to raise_error(Teams::EntitlementError) do |e| + expect(e.code).to eq("staff_limit_reached") + end + end + + it "allows regia invite when only transmission slot is used on free" do + team.team_invitations.create!( + email: "camera@test.com", + token_digest: SecureRandom.hex(32), + role: "member", + staff_kind: "transmission", + expires_at: 7.days.from_now + ) + expect { ent.assert_can_invite!(staff_kind: "regia") }.not_to raise_error + end +end diff --git a/backend/spec/services/webhooks/mediamtx_handler_spec.rb b/backend/spec/services/webhooks/mediamtx_handler_spec.rb new file mode 100644 index 0000000..b16125f --- /dev/null +++ b/backend/spec/services/webhooks/mediamtx_handler_spec.rb @@ -0,0 +1,24 @@ +require "rails_helper" + +RSpec.describe Webhooks::MediamtxHandler do + let(:user) { User.create!(email: "c@test.com", name: "C", password: "password123", role: "coach") } + let(:team) { Team.create!(name: "T") } + let!(:match) { team.matches.create!(opponent_name: "Away") } + let!(:session) do + StreamSession.create!( + match: match, user: user, status: "live", platform: "youtube", + publish_token: "tok", started_at: Time.current + ) + end + + it "transitions to reconnecting on disconnect" do + described_class.new(event: "disconnect", session_id: session.id).call + expect(session.reload.status).to eq("reconnecting") + end + + it "returns to live on reconnect" do + session.update!(status: "reconnecting") + described_class.new(event: "connect", session_id: session.id).call + expect(session.reload.status).to eq("live") + end +end diff --git a/backend/spec/spec_helper.rb b/backend/spec/spec_helper.rb new file mode 100644 index 0000000..1f6769b --- /dev/null +++ b/backend/spec/spec_helper.rb @@ -0,0 +1,5 @@ +RSpec.configure do |config| + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/backend/test_write b/backend/test_write new file mode 100644 index 0000000..e69de29 diff --git a/backend/vendor/.keep b/backend/vendor/.keep new file mode 100644 index 0000000..e69de29 diff --git a/docs/E2E.md b/docs/E2E.md new file mode 100644 index 0000000..d12ea59 --- /dev/null +++ b/docs/E2E.md @@ -0,0 +1,34 @@ +# Test end-to-end Match Live TV + +## Prerequisiti + +```bash +cd infra && docker compose up -d +``` + +## 1. API smoke test + +```bash +./scripts/e2e_api.sh +``` + +## 2. Flusso sessione + +1. Login → crea sessione su partita seed +2. MediaMTX path `match_{uuid}` con alwaysAvailable +3. Simula webhook disconnect → status `reconnecting` +4. Simula webhook connect → status `live` + +## 3. Stream RTMP (device) + +1. Avvia app Flutter in Camera Mode +2. `startStream` con `rtmp_ingest_url` dalla API +3. Verifica path online: `curl http://localhost:9997/v3/paths/list` +4. Airplane mode 30s → slate su YouTube +5. Ripristina rete → reconnect automatico + +## 4. Regia + +1. Secondo device in Controller Mode +2. Scan QR pairing +3. +1 punto → overlay aggiornato su camera via Action Cable diff --git a/docs/PRODUCT_PREMIUM.md b/docs/PRODUCT_PREMIUM.md new file mode 100644 index 0000000..c424991 --- /dev/null +++ b/docs/PRODUCT_PREMIUM.md @@ -0,0 +1,35 @@ +# Match Live TV — Piani commerciali + +## Piani + +| | Free | Premium Light | Premium Full | +|---|------|---------------|----------------| +| Staff trasmissione / anno | 1 | 5 | Illimitato | +| Staff regia / anno | 1 | 5 | Illimitato | +| Partite in contemporanea | 1 | 3 | 10 | +| Live Match Live TV | Sì | Sì | Sì | +| YouTube | No | Match TV Light | Canale società | +| Replay server | No | 30 giorni | 90 giorni | +| Download telefono | No | Sì | Sì | +| Prezzo | €0 | €40/anno o €5/mese | €200/anno o €20/mese | +| Embed sito società | No | No | In arrivo | + +Esigenze custom: contatto `info@matchlivetv.eminux.it`. + +## Sito + +- `/` — Landing +- `/funzionalita` — Feature +- `/prezzi` — Prezzi (statici in pagina) +- `/live` — Dirette pubbliche +- `/signup` — Registrazione → squadra → dashboard + +## Stripe + +```bash +STRIPE_PREMIUM_LIGHT_PRICE_ID=price_... +STRIPE_PREMIUM_FULL_PRICE_ID=price_... +STRIPE_WEBHOOK_SECRET=whsec_... +``` + +Checkout: `GET /teams/:id/checkout?plan=premium_light|premium_full` diff --git a/docs/infrastructure/SERVER_DEPLOYMENT.md b/docs/infrastructure/SERVER_DEPLOYMENT.md new file mode 100644 index 0000000..6020dc5 --- /dev/null +++ b/docs/infrastructure/SERVER_DEPLOYMENT.md @@ -0,0 +1,253 @@ +# Deploy server — Match Live TV Backend + +**Server:** `eminux@192.168.1.146` (Debian 13) +**Path deploy:** `~/matchlivetv` (dopo bootstrap opzionale: `/opt/matchlivetv`) +**Ultimo aggiornamento:** 2026-05-25 + +--- + +## Architettura rete + +``` +Internet + │ + ├──► Router WAN :443 ──► Nginx Proxy Manager (LAN) ──► http://192.168.1.146:3000 (Rails API + Action Cable) + │ + └──► Router WAN :1935 ──► MediaMTX RTMP (telefoni, ingest video diretto) +``` + +Il flusso video **non passa da Rails**: telefono → RTMP :1935 → MediaMTX → relay ffmpeg → YouTube. + +--- + +## Scelte tecniche + +| Scelta | Motivazione | +|--------|-------------| +| Docker + Compose | Riproducibilità, stesso stack in dev e prod | +| `docker-compose.prod.yml` separato | Dev monta volumi sorgente; prod usa immagine buildata | +| Rails esposto su `:3000` solo LAN | NPM termina HTTPS; non esporre 3000 su WAN | +| MediaMTX `:1935` su WAN | RTMP non supporta facilmente reverse proxy TLS su NPM | +| API MediaMTX `:9997` non pubblicata | Solo rete Docker interna | +| Postgres/Redis senza porte host | Sicurezza | +| Kamal non usato in questa fase | Compose sufficiente per singolo nodo; Kamal in roadmap Gitea | + +--- + +## Porte router (port forwarding verso 192.168.1.146) + +### Aprire su Internet + +| Porta | Protocollo | Servizio | +|-------|------------|----------| +| **1935** | TCP | RTMP ingest (obbligatorio per streaming da 4G) | + +### Opzionale + +| Porta | Protocollo | Servizio | +|-------|------------|----------| +| **8888** | TCP | HLS playback registrazioni | + +### Non aprire + +| Porta | Motivo | +|-------|--------| +| 443 | Gestito da NPM (altro host LAN), non da questo server | +| 3000 | API raggiungibile via NPM sulla LAN | +| 5432, 6379 | Database interni | +| 9997 | API MediaMTX | +| 8554 | RTSP non usato MVP | + +--- + +## Nginx Proxy Manager (HTTPS) + +1. **Proxy Host** → Forward Hostname: `http://192.168.1.146:3000` (dominio es. `matchlivetv.eminux.it`) +2. **Websockets:** ON (obbligatorio per `/cable`) +3. **Custom location HLS** (spettatori): + - Location: `/hls` + - Forward: `http://192.168.1.146:8888` + - Scheme: http + - Così l'HLS è `https://matchlivetv.eminux.it/hls/match_{uuid}/index.m3u8` +4. SSL Let's Encrypt su NPM +5. Aggiorna `infra/.env` sul server: + - `APP_PUBLIC_URL=https://matchlivetv.eminux.it` + - `HLS_PUBLIC_URL=https://matchlivetv.eminux.it/hls` + - `CORS_ORIGINS=https://matchlivetv.eminux.it` + - `ALLOWED_HOSTS=matchlivetv.eminux.it,192.168.1.146` + - `MEDIAMTX_RTMP_URL=rtmp://matchlivetv.eminux.it:1935` + - `YOUTUBE_REDIRECT_URI=https://matchlivetv.eminux.it/api/v1/youtube/callback` (solo tier premium) + +--- + +## Installazione iniziale (una tantum) + +### Prerequisito: sudo senza password + +L'utente `eminux` deve poter eseguire sudo senza password da SSH, oppure eseguire il bootstrap da console Proxmox: + +```bash +echo 'eminux ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/eminux-nopasswd +sudo chmod 440 /etc/sudoers.d/eminux-nopasswd +``` + +### 1. Bootstrap server (Docker, UFW, /opt) + +Sul server: + +```bash +sudo bash ~/matchlivetv/scripts/deploy/server_bootstrap.sh +``` + +Installa: Docker Engine, Compose plugin, UFW (22, 1935, 8888, 3000 da LAN), crea `/opt/matchlivetv`. + +**Riavviare la sessione SSH** dopo bootstrap per il gruppo `docker`. + +### 2. Spostare deploy in /opt (opzionale) + +```bash +sudo mv ~/matchlivetv /opt/matchlivetv +sudo chown -R eminux:eminux /opt/matchlivetv +``` + +### 3. Configurare secret + +```bash +cd /opt/matchlivetv/infra # o ~/matchlivetv/infra +nano .env # verificare POSTGRES_PASSWORD, SECRET_KEY_BASE, MEDIAMTX_RTMP_URL, ecc. +``` + +### 4. Avviare stack + +```bash +bash /opt/matchlivetv/scripts/deploy/post_bootstrap_deploy.sh +``` + +Oppure manualmente: + +```bash +cd /opt/matchlivetv/infra +docker compose -f docker-compose.prod.yml --env-file .env up -d --build +``` + +### 5. Seed dati demo (una tantum) + +```bash +docker compose -f docker-compose.prod.yml exec rails bundle exec rails db:seed +``` + +Credenziali demo: `coach@matchlivetv.test` / `password123` + +### 6. Verifica + +```bash +curl http://192.168.1.146:3000/up +curl -X POST http://192.168.1.146:3000/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"email":"coach@matchlivetv.test","password":"password123"}' +``` + +--- + +## Aggiornamento codice (pre-Gitea) + +Dalla macchina di sviluppo: + +```bash +# Tar + scp (rsync non installato sul server) +tar -C /path/to/MatchLiveTV/src -czf /tmp/matchlivetv-deploy.tar.gz \ + --exclude='./mobile/build' --exclude='./backend/log' --exclude='./.git' . +scp /tmp/matchlivetv-deploy.tar.gz eminux@192.168.1.146:~/ +ssh eminux@192.168.1.146 'tar -xzf ~/matchlivetv-deploy.tar.gz -C ~/matchlivetv' + +# Sul server +cd ~/matchlivetv/infra && docker compose -f docker-compose.prod.yml up -d --build +``` + +Script: [`scripts/deploy/sync_to_server.sh`](../../scripts/deploy/sync_to_server.sh) (richiede rsync sul server). + +--- + +## Operazioni quotidiane + +```bash +cd /opt/matchlivetv/infra + +# Stato +docker compose -f docker-compose.prod.yml ps + +# Log +docker compose -f docker-compose.prod.yml logs -f rails +docker compose -f docker-compose.prod.yml logs -f mediamtx + +# Restart +docker compose -f docker-compose.prod.yml restart rails sidekiq + +# Stop +docker compose -f docker-compose.prod.yml down +``` + +### Backup volumi + +- `postgres_data` — database +- `recordings` — HLS MediaMTX + +```bash +docker run --rm -v infra_postgres_data:/data -v $(pwd):/backup alpine \ + tar czf /backup/postgres_backup_$(date +%F).tar.gz -C /data . +``` + +--- + +## Variabili ambiente (`infra/.env`) + +| Variabile | Descrizione | +|-----------|-------------| +| `POSTGRES_PASSWORD` | Password DB | +| `SECRET_KEY_BASE` | Rails secret | +| `JWT_SECRET` | Token API mobile | +| `MEDIAMTX_WEBHOOK_SECRET` | HMAC webhook | +| `MEDIAMTX_RTMP_URL` | URL pubblico RTMP per telefoni | +| `APP_PUBLIC_URL` | Base HTTPS per link watch (`/live/:id`) | +| `HLS_PUBLIC_URL` | Base URL HLS (es. `https://dominio/hls`) | +| `CORS_ORIGINS` | Origini app (dominio HTTPS) | +| `ALLOWED_HOSTS` | Host header Rails | +| `YOUTUBE_*` | OAuth YouTube (tier premium) | + +Template: [`infra/.env.production.example`](../../infra/.env.production.example) + +--- + +## Roadmap Gitea / CI (fase 2) + +1. Repository Gitea con mirror monorepo +2. Pipeline su push `main`: + - `bundle exec rspec` (backend) + - `docker build` immagine `matchlivetv/backend:$CI_COMMIT_SHA` + - Push registry (Gitea packages o Docker Hub privato) +3. Deploy job SSH: + ```bash + docker compose -f docker-compose.prod.yml pull + docker compose -f docker-compose.prod.yml up -d + ``` +4. Secret in Gitea: tutte le variabili `.env` + +--- + +## Stato deploy 2026-05-25 + +| Step | Stato | +|------|-------| +| Codice sincronizzato in `~/matchlivetv` | Completato | +| `infra/.env` con secret generati | Completato (sul server) | +| Docker installato | **In attesa** — richiede `sudo bash server_bootstrap.sh` | +| Stack avviato | **In attesa** — dopo Docker | + +--- + +## Informazioni ancora da definire + +- IP pubblico WAN o DDNS per `MEDIAMTX_RTMP_URL` da internet +- Dominio per NPM e certificato SSL +- Credenziali YouTube OAuth produzione +- URL repository Gitea diff --git a/infra/.env.example b/infra/.env.example new file mode 100644 index 0000000..254a499 --- /dev/null +++ b/infra/.env.example @@ -0,0 +1,7 @@ +POSTGRES_PASSWORD=matchlivetv_dev +SECRET_KEY_BASE=change_me_to_a_long_random_string_in_production +MEDIAMTX_WEBHOOK_SECRET=mediamtx_webhook_dev_secret +JWT_SECRET=matchlivetv_jwt_dev_secret_change_in_prod +YOUTUBE_CLIENT_ID= +YOUTUBE_CLIENT_SECRET= +YOUTUBE_REDIRECT_URI=http://localhost:3000/api/v1/youtube/callback diff --git a/infra/.env.production.example b/infra/.env.production.example new file mode 100644 index 0000000..3beda68 --- /dev/null +++ b/infra/.env.production.example @@ -0,0 +1,30 @@ +# Copia come .env sul server: cp .env.production.example .env +# Genera secret: openssl rand -hex 32 + +POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD +SECRET_KEY_BASE=CHANGE_ME_openssl_rand_hex_64 +JWT_SECRET=CHANGE_ME_openssl_rand_hex_32 +MEDIAMTX_WEBHOOK_SECRET=CHANGE_ME_openssl_rand_hex_32 + +# RTMP pubblico per telefoni (IP WAN o DDNS + porta 1935) +MEDIAMTX_RTMP_URL=rtmp://YOUR_PUBLIC_IP_OR_DOMAIN:1935 + +# Dominio pubblico (Nginx Proxy Manager → Rails :3000) +APP_PUBLIC_URL=https://matchlivetv.yourdomain.com +HLS_PUBLIC_URL=https://matchlivetv.yourdomain.com/hls + +CORS_ORIGINS=https://matchlivetv.yourdomain.com +ALLOWED_HOSTS=matchlivetv.yourdomain.com,192.168.1.146 +YOUTUBE_REDIRECT_URI=https://matchlivetv.yourdomain.com/api/v1/youtube/callback + +# YouTube OAuth (opzionale) +YOUTUBE_CLIENT_ID= +YOUTUBE_CLIENT_SECRET= + +# Stripe (abbonamento Premium squadra) +STRIPE_SECRET_KEY= +STRIPE_WEBHOOK_SECRET= +STRIPE_PREMIUM_LIGHT_PRICE_ID= +STRIPE_PREMIUM_FULL_PRICE_ID= + +RAILS_LOG_LEVEL=info diff --git a/infra/docker-compose.prod.yml b/infra/docker-compose.prod.yml new file mode 100644 index 0000000..3b24fb1 --- /dev/null +++ b/infra/docker-compose.prod.yml @@ -0,0 +1,117 @@ +# Produzione — Match Live TV +# Uso: docker compose -f docker-compose.prod.yml --env-file .env up -d --build + +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_DB: matchlivetv + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + restart: unless-stopped + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + + mediamtx: + image: bluenviron/mediamtx:latest + restart: unless-stopped + ports: + - "1935:1935" # RTMP ingest (telefoni) — esporre su router + - "8888:8888" # HLS playback (opzionale) + volumes: + - ./mediamtx.yml:/mediamtx.yml:ro + - ./slates:/slates:ro + - recordings:/recordings + command: /mediamtx.yml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:9997/v3/config/global/get"] + interval: 15s + timeout: 5s + retries: 3 + + rails: + build: + context: ../backend + dockerfile: Dockerfile + restart: unless-stopped + command: > + bash -c "bundle exec rails db:prepare && + bundle exec rails server -b 0.0.0.0 -p 3000 -e production" + environment: + RAILS_ENV: production + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/matchlivetv + REDIS_URL: redis://redis:6379/0 + MEDIAMTX_API_URL: http://mediamtx:9997 + MEDIAMTX_RTMP_URL: ${MEDIAMTX_RTMP_URL:-rtmp://mediamtx:1935} + MEDIAMTX_WEBHOOK_SECRET: ${MEDIAMTX_WEBHOOK_SECRET} + RAILS_WEBHOOK_URL: http://rails:3000 + SECRET_KEY_BASE: ${SECRET_KEY_BASE} + JWT_SECRET: ${JWT_SECRET} + CORS_ORIGINS: ${CORS_ORIGINS:-*} + ALLOWED_HOSTS: ${ALLOWED_HOSTS:-*} + YOUTUBE_CLIENT_ID: ${YOUTUBE_CLIENT_ID:-} + YOUTUBE_CLIENT_SECRET: ${YOUTUBE_CLIENT_SECRET:-} + YOUTUBE_REDIRECT_URI: ${YOUTUBE_REDIRECT_URI:-} + HLS_PUBLIC_URL: ${HLS_PUBLIC_URL:-http://localhost:8888} + APP_PUBLIC_URL: ${APP_PUBLIC_URL:-http://localhost:3000} + RAILS_LOG_TO_STDOUT: "true" + RAILS_LOG_LEVEL: ${RAILS_LOG_LEVEL:-info} + ports: + - "3000:3000" # Solo LAN — NPM proxy HTTPS + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + mediamtx: + condition: service_started + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/up"] + interval: 15s + timeout: 5s + retries: 10 + start_period: 90s + + sidekiq: + build: + context: ../backend + dockerfile: Dockerfile + restart: unless-stopped + command: bundle exec sidekiq -C config/sidekiq.yml -e production + environment: + RAILS_ENV: production + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/matchlivetv + REDIS_URL: redis://redis:6379/0 + MEDIAMTX_API_URL: http://mediamtx:9997 + MEDIAMTX_WEBHOOK_SECRET: ${MEDIAMTX_WEBHOOK_SECRET} + RAILS_WEBHOOK_URL: http://rails:3000 + SECRET_KEY_BASE: ${SECRET_KEY_BASE} + JWT_SECRET: ${JWT_SECRET} + YOUTUBE_CLIENT_ID: ${YOUTUBE_CLIENT_ID:-} + YOUTUBE_CLIENT_SECRET: ${YOUTUBE_CLIENT_SECRET:-} + HLS_PUBLIC_URL: ${HLS_PUBLIC_URL:-http://localhost:8888} + APP_PUBLIC_URL: ${APP_PUBLIC_URL:-http://localhost:3000} + depends_on: + rails: + condition: service_healthy + +volumes: + postgres_data: + redis_data: + recordings: diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml new file mode 100644 index 0000000..62066ae --- /dev/null +++ b/infra/docker-compose.yml @@ -0,0 +1,119 @@ +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: matchlivetv + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-matchlivetv_dev} + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + volumes: + - redis_data:/data + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + + mediamtx: + image: bluenviron/mediamtx:latest + ports: + - "1935:1935" + - "8554:8554" + - "8888:8888" + - "9997:9997" + volumes: + - ./mediamtx.yml:/mediamtx.yml:ro + - ./slates:/slates:ro + - recordings:/recordings + command: /mediamtx.yml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:9997/v3/config/global/get"] + interval: 10s + timeout: 5s + retries: 3 + + rails: + build: + context: ../backend + dockerfile: Dockerfile + command: bash -c "bundle exec rails db:prepare db:seed && bundle exec rails server -b 0.0.0.0 -p 3000" + environment: + RAILS_ENV: development + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-matchlivetv_dev}@postgres:5432/matchlivetv + REDIS_URL: redis://redis:6379/0 + MEDIAMTX_API_URL: http://mediamtx:9997 + MEDIAMTX_RTMP_URL: rtmp://mediamtx:1935 + MEDIAMTX_WEBHOOK_SECRET: ${MEDIAMTX_WEBHOOK_SECRET:-mediamtx_webhook_dev_secret} + RAILS_WEBHOOK_URL: http://rails:3000 + SECRET_KEY_BASE: ${SECRET_KEY_BASE:-dev_secret_key_base_change_me_32chars_min} + JWT_SECRET: ${JWT_SECRET:-matchlivetv_jwt_dev_secret} + YOUTUBE_CLIENT_ID: ${YOUTUBE_CLIENT_ID:-} + YOUTUBE_CLIENT_SECRET: ${YOUTUBE_CLIENT_SECRET:-} + YOUTUBE_REDIRECT_URI: ${YOUTUBE_REDIRECT_URI:-http://localhost:3000/api/v1/youtube/callback} + RAILS_LOG_TO_STDOUT: "true" + ports: + - "3000:3000" + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + mediamtx: + condition: service_started + volumes: + - ../backend:/app + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/up"] + interval: 15s + timeout: 5s + retries: 10 + start_period: 60s + + sidekiq: + build: + context: ../backend + dockerfile: Dockerfile + command: bundle exec sidekiq -C config/sidekiq.yml + environment: + RAILS_ENV: development + DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-matchlivetv_dev}@postgres:5432/matchlivetv + REDIS_URL: redis://redis:6379/0 + MEDIAMTX_API_URL: http://mediamtx:9997 + MEDIAMTX_WEBHOOK_SECRET: ${MEDIAMTX_WEBHOOK_SECRET:-mediamtx_webhook_dev_secret} + RAILS_WEBHOOK_URL: http://rails:3000 + SECRET_KEY_BASE: ${SECRET_KEY_BASE:-dev_secret_key_base_change_me_32chars_min} + JWT_SECRET: ${JWT_SECRET:-matchlivetv_jwt_dev_secret} + YOUTUBE_CLIENT_ID: ${YOUTUBE_CLIENT_ID:-} + YOUTUBE_CLIENT_SECRET: ${YOUTUBE_CLIENT_SECRET:-} + depends_on: + rails: + condition: service_healthy + volumes: + - ../backend:/app + + nginx: + image: nginx:alpine + ports: + - "8080:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - rails + +volumes: + postgres_data: + redis_data: + recordings: diff --git a/infra/mediamtx.yml b/infra/mediamtx.yml new file mode 100644 index 0000000..2959ce8 --- /dev/null +++ b/infra/mediamtx.yml @@ -0,0 +1,45 @@ +# MediaMTX configuration for Match Live TV +# Docs: https://mediamtx.org/docs/references/configuration-file + +logLevel: info + +# Allow API from Docker network (Rails container) +authInternalUsers: + - user: any + pass: + ips: [] + permissions: + - action: api + - action: metrics + - action: pprof + - action: publish + - action: read + +api: yes +apiAddress: :9997 +rtmp: yes +rtmpAddress: :1935 +hls: yes +hlsAddress: :8888 +hlsVariant: mpegts + +pathDefaults: + source: publisher + overridePublisher: yes + record: yes + recordPath: /recordings/%path/%Y-%m-%d_%H-%M-%S + recordSegmentDuration: 60s + recordDeleteAfter: 48h + +paths: + # Dynamic session paths are created via MediaMTX HTTP API from Rails. + # Template for reference (Rails sets alwaysAvailable per path): + # match_{uuid}: + # alwaysAvailable: true + # alwaysAvailableFile: /slates/offline.mp4 + # alwaysAvailableTracks: + # - codec: H264 + # - codec: MPEG4Audio + # sampleRate: 48000 + # channelCount: 2 + all_others: diff --git a/infra/nginx.conf b/infra/nginx.conf new file mode 100644 index 0000000..ec70999 --- /dev/null +++ b/infra/nginx.conf @@ -0,0 +1,34 @@ +events { + worker_connections 1024; +} + +http { + upstream rails { + server rails:3000; + } + + server { + listen 80; + server_name localhost; + + client_max_body_size 10M; + + location /cable { + proxy_pass http://rails; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 86400; + } + + location / { + proxy_pass http://rails; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} diff --git a/infra/scripts/generate_slate.sh b/infra/scripts/generate_slate.sh new file mode 100755 index 0000000..ec873b9 --- /dev/null +++ b/infra/scripts/generate_slate.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Generate offline slate MP4 for MediaMTX alwaysAvailable +set -e +OUT_DIR="$(dirname "$0")/../slates" +OUT="$OUT_DIR/offline.mp4" +mkdir -p "$OUT_DIR" +docker run --rm -v "$OUT_DIR:/out" linuxserver/ffmpeg:latest \ + -f lavfi -i color=c=0x1a1a1a:s=1280x720:r=30 \ + -f lavfi -i sine=frequency=440:sample_rate=48000 \ + -t 30 -c:v libx264 -pix_fmt yuv420p -g 30 -preset fast \ + -c:a aac -b:a 128k -y /out/offline.mp4 +echo "Created $OUT" diff --git a/infra/slates/README.md b/infra/slates/README.md new file mode 100644 index 0000000..69a6ec3 --- /dev/null +++ b/infra/slates/README.md @@ -0,0 +1,11 @@ +# Slate video (offline) + +Place `offline.mp4` here — H.264 + AAC, 1280x720, 30fps, 48kHz stereo. +Must match phone encoder settings for seamless `alwaysAvailable` merge. + +Generate a test slate with ffmpeg: + +```bash +ffmpeg -f lavfi -i color=c=0x1a1a1a:s=1280x720:r=30 -f lavfi -i sine=frequency=440:sample_rate=48000 \ + -t 10 -c:v libx264 -pix_fmt yuv420p -g 30 -c:a aac -b:a 128k offline.mp4 +``` diff --git a/infra/slates/offline.mp4 b/infra/slates/offline.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c870254f671003651e990e3a5cdb26e14360f8e9 GIT binary patch literal 555574 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�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

=GNBfy9IAu6&xV+Y>1#@_;7uIkj4e^%0AlbrmmTiEm?f)nY_|LZwt|<_ggWPC>Rii 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 literal 0 HcmV?d00001 diff --git a/mobile/.gitignore b/mobile/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/mobile/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# 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-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# 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 diff --git a/mobile/.metadata b/mobile/.metadata new file mode 100644 index 0000000..a906de7 --- /dev/null +++ b/mobile/.metadata @@ -0,0 +1,45 @@ +# 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: "559ffa3f75e7402d65a8def9c28389a9b2e6fe42" + channel: "[user-branch]" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: android + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: ios + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: linux + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: macos + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: web + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + - platform: windows + create_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + base_revision: 559ffa3f75e7402d65a8def9c28389a9b2e6fe42 + + # 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' diff --git a/mobile/README.md b/mobile/README.md new file mode 100644 index 0000000..15db89a --- /dev/null +++ b/mobile/README.md @@ -0,0 +1,17 @@ +# match_live_tv + +A new Flutter project. + +## 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: + +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) + +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. diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/mobile/analysis_options.yaml @@ -0,0 +1,28 @@ +# 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 diff --git a/mobile/android/.gitignore b/mobile/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/mobile/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/mobile/android/app/build.gradle.kts b/mobile/android/app/build.gradle.kts new file mode 100644 index 0000000..46895e5 --- /dev/null +++ b/mobile/android/app/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.matchlivetv.match_live_tv" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.matchlivetv.match_live_tv" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = maxOf(flutter.minSdkVersion, 21) + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} + +dependencies { + val cameraxVersion = "1.4.1" + + implementation("androidx.camera:camera-core:$cameraxVersion") + implementation("androidx.camera:camera-camera2:$cameraxVersion") + implementation("androidx.camera:camera-lifecycle:$cameraxVersion") + implementation("androidx.camera:camera-view:$cameraxVersion") + implementation("com.github.pedroSG94.RootEncoder:library:2.5.5") +} diff --git a/mobile/android/app/proguard-rules.pro b/mobile/android/app/proguard-rules.pro new file mode 100644 index 0000000..57c43b6 --- /dev/null +++ b/mobile/android/app/proguard-rules.pro @@ -0,0 +1,2 @@ +# RootEncoder / SLF4J (R8 release) +-dontwarn org.slf4j.impl.StaticLoggerBinder diff --git a/mobile/android/app/src/debug/AndroidManifest.xml b/mobile/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ee2d458 --- /dev/null +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/MainActivity.kt b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/MainActivity.kt new file mode 100644 index 0000000..16c544b --- /dev/null +++ b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/MainActivity.kt @@ -0,0 +1,21 @@ +package com.matchlivetv.match_live_tv + +import android.os.Bundle +import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.engine.FlutterEngine + +class MainActivity : FlutterFragmentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // Termina eventuale diretta rimasta attiva da sessione precedente + StreamingEngineHolder.engine?.stopStream() + StreamingEngineHolder.release() + startService(StreamingForegroundService.stopIntent(this)) + } + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + flutterEngine.plugins.add(StreamingPlugin()) + } +} diff --git a/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamPreviewHolder.kt b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamPreviewHolder.kt new file mode 100644 index 0000000..c93c24a --- /dev/null +++ b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamPreviewHolder.kt @@ -0,0 +1,9 @@ +package com.matchlivetv.match_live_tv + +import com.pedro.library.view.OpenGlView + +/** Riferimento condiviso alla surface di preview tra PlatformView e StreamingEngine. */ +object StreamPreviewHolder { + @Volatile + var openGlView: OpenGlView? = null +} diff --git a/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingEngine.kt b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingEngine.kt new file mode 100644 index 0000000..33c9d48 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingEngine.kt @@ -0,0 +1,476 @@ +package com.matchlivetv.match_live_tv + +import android.content.Context +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import androidx.annotation.RequiresApi +import com.pedro.common.ConnectChecker +import com.pedro.library.generic.GenericStream +import com.pedro.library.util.BitrateAdapter +import com.pedro.library.view.OpenGlView +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +data class StreamingConfig( + val rtmpUrl: String, + val width: Int = 1280, + val height: Int = 720, + val videoBitrate: Int = 2_500_000, + val audioBitrate: Int = 128_000, + val fps: Int = 30, + val rotation: Int = 0, + val sampleRate: Int = 44_100, + val stereo: Boolean = true, + val maxReconnectAttempts: Int = 10, + val reconnectDelayMs: Long = 5_000L, +) + +data class StreamingMetrics( + val isStreaming: Boolean, + val isPreviewActive: Boolean, + val isConnected: Boolean, + val state: String, + val bitrateKbps: Long, + val fps: Int, + val durationMs: Long, + val reconnectAttempts: Int, + val lastError: String?, +) { + fun toMap(): Map = mapOf( + "isStreaming" to isStreaming, + "isPreviewActive" to isPreviewActive, + "isConnected" to isConnected, + "state" to state, + "bitrateKbps" to bitrateKbps, + "fps" to fps, + "durationMs" to durationMs, + "reconnectAttempts" to reconnectAttempts, + "lastError" to lastError, + ) +} + +enum class StreamingState { + IDLE, + PREVIEWING, + CONNECTING, + STREAMING, + RECONNECTING, + STOPPING, + ERROR, +} + +interface StreamingEngineListener { + fun onStateChanged(state: StreamingState, message: String? = null) + fun onMetricsUpdated(metrics: StreamingMetrics) + fun onError(message: String) +} + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +class StreamingEngine( + private val appContext: Context, +) : ConnectChecker { + + private val mainHandler = Handler(Looper.getMainLooper()) + private val listeners = mutableSetOf() + + private var genericStream: GenericStream? = null + private var previewSurface: OpenGlView? = null + + private var config: StreamingConfig? = null + private var state: StreamingState = StreamingState.IDLE + private var streamStartedAtMs: Long = 0L + private var currentBitrateKbps: Long = 0L + private var currentFps: Int = 0 + private var lastError: String? = null + private var lastVideoFrameCount: Long = 0L + private var lastFpsSampleAtMs: Long = 0L + private val reconnectAttempts = AtomicInteger(0) + private val isReleased = AtomicBoolean(false) + + private val bitrateAdapter = BitrateAdapter { adaptedBitrate -> + genericStream?.setVideoBitrateOnFly(adaptedBitrate) + } + + private val metricsRunnable = object : Runnable { + override fun run() { + emitMetrics() + if (state == StreamingState.STREAMING || + state == StreamingState.CONNECTING || + state == StreamingState.RECONNECTING || + state == StreamingState.PREVIEWING + ) { + mainHandler.postDelayed(this, METRICS_INTERVAL_MS) + } + } + } + + fun addListener(listener: StreamingEngineListener) { + listeners.add(listener) + } + + fun removeListener(listener: StreamingEngineListener) { + listeners.remove(listener) + } + + fun bindStreamPreview(glView: OpenGlView) { + ensureMainThread() + val surfaceChanged = previewSurface != null && previewSurface !== glView + previewSurface = glView + + if (surfaceChanged) { + genericStream?.let { stream -> + if (stream.isOnPreview) { + stream.stopPreview() + } + } + } + + if (state == StreamingState.STREAMING || + state == StreamingState.CONNECTING || + state == StreamingState.RECONNECTING + ) { + attachPreviewToStream(forceRebind = surfaceChanged) + return + } + + val cfg = config ?: previewConfig() + val stream = genericStream ?: obtainGenericStream(cfg) + genericStream = stream + + if (!stream.isOnPreview) { + stopPreviewAndStreamForPrepare(stream) + val prepared = try { + stream.prepareVideo( + cfg.width, + cfg.height, + cfg.videoBitrate, + cfg.fps, + rotation = cfg.rotation, + ) && stream.prepareAudio( + cfg.sampleRate, + cfg.stereo, + cfg.audioBitrate, + ) + } catch (_: Exception) { + false + } + if (!prepared) { + notifyError("Impossibile preparare la preview camera") + return + } + } + + attachPreviewToStream() + updateState(StreamingState.PREVIEWING) + startMetricsLoop() + } + + fun unbindStreamPreview() { + ensureMainThread() + previewSurface = null + genericStream?.let { stream -> + if (stream.isOnPreview) { + stream.stopPreview() + } + } + if (state == StreamingState.PREVIEWING) { + updateState(StreamingState.IDLE) + } + } + + fun startStream(streamConfig: StreamingConfig) { + ensureMainThread() + if (isReleased.get()) { + notifyError("StreamingEngine rilasciato") + return + } + if (state == StreamingState.STREAMING || + state == StreamingState.CONNECTING || + state == StreamingState.RECONNECTING + ) { + return + } + + config = streamConfig + lastError = null + reconnectAttempts.set(0) + + val stream = genericStream ?: obtainGenericStream(streamConfig) + genericStream = stream + stopPreviewAndStreamForPrepare(stream) + + val prepared = try { + stream.prepareVideo( + streamConfig.width, + streamConfig.height, + streamConfig.videoBitrate, + streamConfig.fps, + rotation = streamConfig.rotation, + ) && stream.prepareAudio( + streamConfig.sampleRate, + streamConfig.stereo, + streamConfig.audioBitrate, + ) + } catch (exception: IllegalArgumentException) { + false + } catch (exception: IllegalStateException) { + false + } + + if (!prepared) { + notifyError("Configurazione audio/video non valida") + updateState(StreamingState.ERROR, "prepare failed") + return + } + + attachPreviewToStream() + + stream.getStreamClient().setReTries(streamConfig.maxReconnectAttempts) + bitrateAdapter.setMaxBitrate(streamConfig.videoBitrate + streamConfig.audioBitrate) + streamStartedAtMs = SystemClock.elapsedRealtime() + updateState(StreamingState.CONNECTING) + stream.startStream(streamConfig.rtmpUrl) + startMetricsLoop() + } + + fun stopStream() { + ensureMainThread() + if (state == StreamingState.IDLE || state == StreamingState.STOPPING) { + return + } + + updateState(StreamingState.STOPPING) + stopMetricsLoop() + + genericStream?.let { stream -> + if (stream.isStreaming) { + stream.stopStream() + } + if (stream.isOnPreview) { + stream.stopPreview() + } + stream.release() + } + genericStream = null + + streamStartedAtMs = 0L + currentBitrateKbps = 0L + currentFps = 0 + reconnectAttempts.set(0) + updateState(StreamingState.IDLE) + emitMetrics() + } + + fun release() { + ensureMainThread() + if (!isReleased.compareAndSet(false, true)) { + return + } + stopStream() + unbindStreamPreview() + listeners.clear() + } + + fun getMetrics(): StreamingMetrics = buildMetrics() + + override fun onConnectionStarted(url: String) { + postMain { + updateState(StreamingState.CONNECTING, url) + } + } + + override fun onConnectionSuccess() { + postMain { + reconnectAttempts.set(0) + lastError = null + updateState(StreamingState.STREAMING) + } + } + + override fun onConnectionFailed(reason: String) { + postMain { + lastError = reason + val stream = genericStream ?: return@postMain + val currentConfig = config ?: return@postMain + + updateState(StreamingState.RECONNECTING, reason) + val attempt = reconnectAttempts.incrementAndGet() + val retryScheduled = stream.getStreamClient().reTry( + currentConfig.reconnectDelayMs, + reason, + null, + ) + + if (retryScheduled) { + emitMetrics() + } else { + updateState(StreamingState.ERROR, reason) + notifyError("Connessione RTMP fallita: $reason") + stream.stopStream() + } + } + } + + override fun onNewBitrate(bitrate: Long) { + postMain { + currentBitrateKbps = bitrate / 1000 + genericStream?.let { stream -> + bitrateAdapter.adaptBitrate( + bitrate, + stream.getStreamClient().hasCongestion(), + ) + } + emitMetrics() + } + } + + override fun onDisconnect() { + postMain { + if (state == StreamingState.STREAMING || state == StreamingState.RECONNECTING) { + updateState(StreamingState.RECONNECTING, "disconnected") + } + } + } + + override fun onAuthError() { + postMain { + lastError = "auth_error" + updateState(StreamingState.ERROR, "auth_error") + genericStream?.stopStream() + notifyError("Autenticazione RTMP fallita") + } + } + + override fun onAuthSuccess() { + postMain { + updateState(StreamingState.STREAMING) + } + } + + private fun stopPreviewAndStreamForPrepare(stream: GenericStream) { + if (stream.isStreaming) { + stream.stopStream() + } + if (stream.isOnPreview) { + stream.stopPreview() + } + } + + private fun attachPreviewToStream(forceRebind: Boolean = false) { + val glView = previewSurface ?: StreamPreviewHolder.openGlView ?: return + previewSurface = glView + val stream = genericStream ?: return + if (forceRebind && stream.isOnPreview) { + stream.stopPreview() + } + if (!stream.isOnPreview) { + stream.startPreview(glView, autoHandle = true) + } + } + + private fun previewConfig(): StreamingConfig { + return config ?: StreamingConfig( + rtmpUrl = "rtmp://127.0.0.1/preview", + width = 1280, + height = 720, + ) + } + + private fun obtainGenericStream(streamConfig: StreamingConfig): GenericStream { + return GenericStream( + appContext, + this, + ).apply { + getGlInterface().autoHandleOrientation = true + getStreamClient().setBitrateExponentialFactor(0.5f) + getStreamClient().setReTries(streamConfig.maxReconnectAttempts) + } + } + + private fun buildMetrics(): StreamingMetrics { + val durationMs = if (streamStartedAtMs > 0L) { + SystemClock.elapsedRealtime() - streamStartedAtMs + } else { + 0L + } + + return StreamingMetrics( + isStreaming = state == StreamingState.STREAMING || + state == StreamingState.RECONNECTING || + state == StreamingState.CONNECTING, + isPreviewActive = state == StreamingState.PREVIEWING || + (genericStream?.isOnPreview == true), + isConnected = state == StreamingState.STREAMING, + state = state.name.lowercase(), + bitrateKbps = currentBitrateKbps, + fps = currentFps, + durationMs = durationMs, + reconnectAttempts = reconnectAttempts.get(), + lastError = lastError, + ) + } + + private fun updateState(newState: StreamingState, message: String? = null) { + state = newState + listeners.forEach { it.onStateChanged(newState, message) } + emitMetrics() + } + + private fun emitMetrics() { + updateFpsEstimate() + val metrics = buildMetrics() + listeners.forEach { it.onMetricsUpdated(metrics) } + } + + private fun updateFpsEstimate() { + val stream = genericStream ?: return + val now = SystemClock.elapsedRealtime() + val sentFrames = stream.getStreamClient().getSentVideoFrames() + if (lastFpsSampleAtMs == 0L) { + lastFpsSampleAtMs = now + lastVideoFrameCount = sentFrames + return + } + + val elapsedMs = now - lastFpsSampleAtMs + if (elapsedMs >= 1_000L) { + val frameDelta = sentFrames - lastVideoFrameCount + currentFps = ((frameDelta * 1000L) / elapsedMs).toInt() + lastVideoFrameCount = sentFrames + lastFpsSampleAtMs = now + } + } + + private fun notifyError(message: String) { + listeners.forEach { it.onError(message) } + } + + private fun startMetricsLoop() { + mainHandler.removeCallbacks(metricsRunnable) + mainHandler.post(metricsRunnable) + } + + private fun stopMetricsLoop() { + mainHandler.removeCallbacks(metricsRunnable) + } + + private fun postMain(block: () -> Unit) { + if (Looper.myLooper() == Looper.getMainLooper()) { + block() + } else { + mainHandler.post(block) + } + } + + private fun ensureMainThread() { + check(Looper.myLooper() == Looper.getMainLooper()) { + "StreamingEngine must be used on the main thread" + } + } + + companion object { + private const val METRICS_INTERVAL_MS = 1_000L + } +} diff --git a/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingEngineHolder.kt b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingEngineHolder.kt new file mode 100644 index 0000000..ea2bcf1 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingEngineHolder.kt @@ -0,0 +1,22 @@ +package com.matchlivetv.match_live_tv + +import android.content.Context + +/** Motore streaming condiviso tra preview UI e RTMP (deve restare legato all'Activity). */ +object StreamingEngineHolder { + @Volatile + var engine: StreamingEngine? = null + + fun getOrCreate(context: Context): StreamingEngine { + val existing = engine + if (existing != null) { + return existing + } + return StreamingEngine(context.applicationContext).also { engine = it } + } + + fun release() { + engine?.release() + engine = null + } +} diff --git a/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingForegroundService.kt b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingForegroundService.kt new file mode 100644 index 0000000..b3e9ef3 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingForegroundService.kt @@ -0,0 +1,243 @@ +package com.matchlivetv.match_live_tv + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.pm.ServiceInfo +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.os.PowerManager +import android.os.SystemClock +import androidx.core.app.NotificationCompat +import androidx.core.app.ServiceCompat + +/** Mantiene in foreground la diretta (notifica + wake lock). Il motore RTMP vive nel plugin. */ +class StreamingForegroundService : Service() { + + private val binder = LocalBinder() + private val mainHandler = Handler(Looper.getMainLooper()) + + private var wakeLock: PowerManager.WakeLock? = null + private var sessionStartedAtMs: Long = 0L + private var autoStopTriggered = false + + private val sessionTimeoutRunnable = Runnable { + if (!autoStopTriggered) { + autoStopTriggered = true + ServiceEventBus.emit( + mapOf( + "type" to "stopped", + "reason" to "session_timeout", + "durationMs" to elapsedMs(), + ), + ) + stopForegroundInternal("session_timeout") + } + } + + inner class LocalBinder : Binder() { + fun getService(): StreamingForegroundService = this@StreamingForegroundService + } + + override fun onCreate() { + super.onCreate() + createNotificationChannel() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.action) { + ACTION_START -> startForegroundSession() + ACTION_STOP -> stopForegroundInternal("user_stop") + } + return START_STICKY + } + + override fun onBind(intent: Intent?): IBinder = binder + + override fun onDestroy() { + stopForegroundInternal("service_destroy") + super.onDestroy() + } + + fun getMetrics(): Map = + StreamingEngineHolder.engine?.getMetrics()?.toMap() ?: emptyMap() + + private fun startForegroundSession() { + autoStopTriggered = false + sessionStartedAtMs = SystemClock.elapsedRealtime() + acquireWakeLock() + promoteToForeground(getString(R.string.streaming_notification_active)) + mainHandler.removeCallbacks(sessionTimeoutRunnable) + mainHandler.postDelayed(sessionTimeoutRunnable, SESSION_MAX_MS) + } + + private fun stopForegroundInternal(reason: String) { + mainHandler.removeCallbacks(sessionTimeoutRunnable) + releaseWakeLock() + if (sessionStartedAtMs > 0L) { + ServiceEventBus.emit( + mapOf( + "type" to "stopped", + "reason" to reason, + "durationMs" to elapsedMs(), + ), + ) + } + sessionStartedAtMs = 0L + stopForeground(STOP_FOREGROUND_REMOVE) + stopSelf() + } + + private fun elapsedMs(): Long { + return if (sessionStartedAtMs > 0L) { + SystemClock.elapsedRealtime() - sessionStartedAtMs + } else { + 0L + } + } + + private fun promoteToForeground(content: String) { + val notification = buildNotification(content) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ServiceCompat.startForeground( + this, + NOTIFICATION_ID, + notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA or + ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE, + ) + } else { + startForeground(NOTIFICATION_ID, notification) + } + } + + fun updateNotificationForState(state: StreamingState) { + val content = when (state) { + StreamingState.CONNECTING -> getString(R.string.streaming_notification_connecting) + StreamingState.STREAMING -> getString(R.string.streaming_notification_streaming) + StreamingState.RECONNECTING -> getString(R.string.streaming_notification_reconnecting) + StreamingState.ERROR -> getString(R.string.streaming_notification_error) + else -> getString(R.string.streaming_notification_active) + } + val manager = getSystemService(NotificationManager::class.java) + manager.notify(NOTIFICATION_ID, buildNotification(content)) + } + + private fun buildNotification(content: String): Notification { + val launchIntent = packageManager.getLaunchIntentForPackage(packageName) + val contentIntent = PendingIntent.getActivity( + this, + 0, + launchIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + val stopIntent = PendingIntent.getService( + this, + 1, + Companion.stopIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + + return NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(getString(R.string.streaming_notification_title)) + .setContentText(content) + .setSmallIcon(R.mipmap.ic_launcher) + .setOngoing(true) + .setContentIntent(contentIntent) + .addAction( + android.R.drawable.ic_media_pause, + getString(R.string.streaming_notification_stop), + stopIntent, + ) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setPriority(NotificationCompat.PRIORITY_LOW) + .build() + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return + } + val manager = getSystemService(NotificationManager::class.java) + val channel = NotificationChannel( + CHANNEL_ID, + getString(R.string.streaming_notification_channel), + NotificationManager.IMPORTANCE_LOW, + ).apply { + description = getString(R.string.streaming_notification_channel_desc) + } + manager.createNotificationChannel(channel) + } + + private fun acquireWakeLock() { + if (wakeLock?.isHeld == true) { + return + } + val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + wakeLock = powerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + "$packageName:StreamingWakeLock", + ).apply { + setReferenceCounted(false) + acquire(SESSION_MAX_MS + 60_000L) + } + } + + private fun releaseWakeLock() { + wakeLock?.let { lock -> + if (lock.isHeld) { + lock.release() + } + } + wakeLock = null + } + + companion object { + const val ACTION_START = "com.matchlivetv.match_live_tv.action.START_STREAM" + const val ACTION_STOP = "com.matchlivetv.match_live_tv.action.STOP_STREAM" + + private const val CHANNEL_ID = "match_live_tv_streaming" + private const val NOTIFICATION_ID = 1001 + private val SESSION_MAX_MS = 90L * 60L * 1000L + + fun startIntent(context: Context): Intent { + return Intent(context, StreamingForegroundService::class.java).apply { + action = ACTION_START + } + } + + fun stopIntent(context: Context): Intent { + return Intent(context, StreamingForegroundService::class.java).apply { + action = ACTION_STOP + } + } + } +} + +object ServiceEventBus { + private val listeners = mutableSetOf<(Map) -> Unit>() + + fun addListener(listener: (Map) -> Unit) { + synchronized(listeners) { + listeners.add(listener) + } + } + + fun removeListener(listener: (Map) -> Unit) { + synchronized(listeners) { + listeners.remove(listener) + } + } + + fun emit(event: Map) { + val snapshot = synchronized(listeners) { listeners.toList() } + snapshot.forEach { it(event) } + } +} diff --git a/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingPlugin.kt b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingPlugin.kt new file mode 100644 index 0000000..fba0655 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingPlugin.kt @@ -0,0 +1,286 @@ +package com.matchlivetv.match_live_tv + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.Build +import android.os.IBinder +import com.pedro.library.view.OpenGlView +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +class StreamingPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, EventChannel.StreamHandler { + + private lateinit var appContext: Context + private lateinit var methodChannel: MethodChannel + private lateinit var eventChannel: EventChannel + + private var activityBinding: ActivityPluginBinding? = null + private var eventSink: EventChannel.EventSink? = null + private var boundService: StreamingForegroundService? = null + private var serviceBound = false + + private val engineListener = object : StreamingEngineListener { + override fun onStateChanged(state: StreamingState, message: String?) { + ServiceEventBus.emit( + mapOf( + "type" to "state", + "state" to state.name.lowercase(), + "message" to message, + ), + ) + boundService?.updateNotificationForState(state) + } + + override fun onMetricsUpdated(metrics: StreamingMetrics) { + ServiceEventBus.emit( + mapOf( + "type" to "metrics", + "metrics" to metrics.toMap(), + ), + ) + } + + override fun onError(message: String) { + ServiceEventBus.emit( + mapOf( + "type" to "error", + "message" to message, + ), + ) + } + } + + private val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + val binder = service as? StreamingForegroundService.LocalBinder ?: return + boundService = binder.getService() + serviceBound = true + } + + override fun onServiceDisconnected(name: ComponentName?) { + boundService = null + serviceBound = false + } + } + + private val busListener: (Map) -> Unit = { event -> + eventSink?.success(event) + } + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + appContext = binding.applicationContext + methodChannel = MethodChannel(binding.binaryMessenger, METHOD_CHANNEL) + methodChannel.setMethodCallHandler(this) + + eventChannel = EventChannel(binding.binaryMessenger, EVENT_CHANNEL) + eventChannel.setStreamHandler(this) + + binding.platformViewRegistry.registerViewFactory( + PREVIEW_VIEW_TYPE, + StreamingPreviewFactory { mainHandlerRebindPreview() }, + ) + + ServiceEventBus.addListener(busListener) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + methodChannel.setMethodCallHandler(null) + eventChannel.setStreamHandler(null) + ServiceEventBus.removeListener(busListener) + releaseEngine() + unbindServiceIfNeeded() + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activityBinding = binding + mainHandlerRebindPreview() + } + + override fun onDetachedFromActivityForConfigChanges() { + activityBinding = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activityBinding = binding + mainHandlerRebindPreview() + } + + private fun mainHandlerRebindPreview() { + android.os.Handler(android.os.Looper.getMainLooper()).post { + attachPreviewIfPossible() + } + } + + override fun onDetachedFromActivity() { + activityBinding = null + unbindServiceIfNeeded() + } + + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "startStream" -> startStream(call, result) + "stopStream" -> stopStream(result) + "getMetrics" -> getMetrics(result) + "startPreview" -> startPreview(result) + "stopPreview" -> stopPreview(result) + "bindPreview" -> bindPreview(result) + "unbindPreview" -> unbindPreview(result) + else -> result.notImplemented() + } + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + eventSink = events + } + + override fun onCancel(arguments: Any?) { + eventSink = null + } + + private fun startStream(call: MethodCall, result: Result) { + if (activityBinding?.activity == null) { + result.error("no_activity", "Activity non disponibile per lo streaming", null) + return + } + + val args = call.arguments as? Map<*, *> + val rtmpUrl = args?.get("rtmpUrl") as? String + if (rtmpUrl.isNullOrBlank()) { + result.error("invalid_args", "rtmpUrl is required", null) + return + } + + val config = StreamingConfig( + rtmpUrl = rtmpUrl, + width = (args["width"] as? Number)?.toInt() ?: 1280, + height = (args["height"] as? Number)?.toInt() ?: 720, + videoBitrate = (args["videoBitrate"] as? Number)?.toInt() ?: 2_500_000, + audioBitrate = (args["audioBitrate"] as? Number)?.toInt() ?: 128_000, + fps = (args["fps"] as? Number)?.toInt() ?: 30, + rotation = (args["rotation"] as? Number)?.toInt() ?: 0, + maxReconnectAttempts = (args["maxReconnectAttempts"] as? Number)?.toInt() ?: 10, + reconnectDelayMs = (args["reconnectDelayMs"] as? Number)?.toLong() ?: 5_000L, + ) + + val engine = getOrCreateEngine() + engine.removeListener(engineListener) + engine.addListener(engineListener) + + attachPreviewIfPossible() + + val intent = StreamingForegroundService.startIntent(appContext) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + appContext.startForegroundService(intent) + } else { + appContext.startService(intent) + } + bindServiceIfNeeded() + + try { + engine.startStream(config) + result.success(true) + } catch (exception: Exception) { + appContext.startService(StreamingForegroundService.stopIntent(appContext)) + result.error("start_failed", exception.message, null) + } + } + + private fun stopStream(result: Result) { + appContext.startService(StreamingForegroundService.stopIntent(appContext)) + getEngine()?.let { engine -> + engine.removeListener(engineListener) + engine.stopStream() + } + unbindServiceIfNeeded() + result.success(true) + } + + private fun getMetrics(result: Result) { + val metrics = boundService?.getMetrics() + ?: getEngine()?.getMetrics()?.toMap() + ?: emptyMap() + result.success(metrics) + } + + private fun bindPreview(result: Result) { + attachPreviewIfPossible() + result.success(StreamPreviewHolder.openGlView != null) + } + + private fun unbindPreview(result: Result) { + getEngine()?.unbindStreamPreview() + result.success(true) + } + + private fun startPreview(result: Result) { + attachPreviewIfPossible() + result.success(StreamPreviewHolder.openGlView != null) + } + + private fun stopPreview(result: Result) { + getEngine()?.unbindStreamPreview() + result.success(true) + } + + private fun getOrCreateEngine(): StreamingEngine { + val context = activityBinding?.activity ?: appContext + return StreamingEngineHolder.getOrCreate(context).also { engine -> + engine.removeListener(engineListener) + engine.addListener(engineListener) + } + } + + private fun getEngine(): StreamingEngine? = StreamingEngineHolder.engine + + private fun releaseEngine() { + getEngine()?.let { engine -> + engine.removeListener(engineListener) + engine.stopStream() + } + StreamingEngineHolder.release() + } + + private fun attachPreviewIfPossible() { + val glView = StreamPreviewHolder.openGlView ?: return + val engine = getOrCreateEngine() + engine.bindStreamPreview(glView) + } + + private fun bindServiceIfNeeded() { + if (serviceBound) { + return + } + val intent = Intent(appContext, StreamingForegroundService::class.java) + appContext.bindService( + intent, + serviceConnection, + Context.BIND_AUTO_CREATE, + ) + } + + private fun unbindServiceIfNeeded() { + if (!serviceBound) { + return + } + try { + appContext.unbindService(serviceConnection) + } catch (_: IllegalArgumentException) { + } + serviceBound = false + boundService = null + } + + companion object { + private const val METHOD_CHANNEL = "com.matchlivetv.match_live_tv/streaming" + private const val EVENT_CHANNEL = "com.matchlivetv.match_live_tv/streaming_events" + const val PREVIEW_VIEW_TYPE = "match_live_tv/streaming_preview" + } +} diff --git a/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingPreviewPlatformView.kt b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingPreviewPlatformView.kt new file mode 100644 index 0000000..2c3417a --- /dev/null +++ b/mobile/android/app/src/main/kotlin/com/matchlivetv/match_live_tv/StreamingPreviewPlatformView.kt @@ -0,0 +1,42 @@ +package com.matchlivetv.match_live_tv + +import android.content.Context +import android.view.View +import android.widget.FrameLayout +import com.pedro.library.view.OpenGlView +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + +class StreamingPreviewPlatformView( + context: Context, + private val onPreviewReady: (OpenGlView) -> Unit, +) : PlatformView { + + private val openGlView = OpenGlView(context).apply { + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + ) + } + + init { + StreamPreviewHolder.openGlView = openGlView + onPreviewReady(openGlView) + } + + override fun getView(): View = openGlView + + override fun dispose() { + // Non azzerare il holder: la rotazione ricrea la PlatformView e il plugin riaggancia. + } +} + +class StreamingPreviewFactory( + private val onPreviewReady: (OpenGlView) -> Unit, +) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + + override fun create(context: Context, viewId: Int, args: Any?): PlatformView { + return StreamingPreviewPlatformView(context, onPreviewReady) + } +} diff --git a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/mobile/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/android/app/src/main/res/drawable/launch_background.xml b/mobile/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/mobile/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/mobile/android/app/src/main/res/values-night/styles.xml b/mobile/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/mobile/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/android/app/src/main/res/values/strings.xml b/mobile/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..425b3bb --- /dev/null +++ b/mobile/android/app/src/main/res/values/strings.xml @@ -0,0 +1,12 @@ + + + Streaming live + Notifiche durante lo streaming Match Live TV + Match Live TV + Streaming attivo + Connessione al server RTMP… + Trasmissione in corso + Riconnessione in corso… + Errore di streaming + Stop + diff --git a/mobile/android/app/src/main/res/values/styles.xml b/mobile/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/mobile/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile/android/app/src/main/res/xml/network_security_config.xml b/mobile/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..82f15b7 --- /dev/null +++ b/mobile/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + 10.0.2.2 + localhost + 127.0.0.1 + + diff --git a/mobile/android/app/src/profile/AndroidManifest.xml b/mobile/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/mobile/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile/android/build.gradle.kts b/mobile/android/build.gradle.kts new file mode 100644 index 0000000..d08ad63 --- /dev/null +++ b/mobile/android/build.gradle.kts @@ -0,0 +1,25 @@ +allprojects { + repositories { + google() + mavenCentral() + maven { url = uri("https://jitpack.io") } + } +} + +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") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/mobile/android/gradle.properties b/mobile/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/mobile/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d428bf --- /dev/null +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/mobile/android/settings.gradle.kts b/mobile/android/settings.gradle.kts new file mode 100644 index 0000000..2786c72 --- /dev/null +++ b/mobile/android/settings.gradle.kts @@ -0,0 +1,35 @@ +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 "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_PROJECT) + repositories { + google() + mavenCentral() + maven { url = uri("https://jitpack.io") } + } +} + +include(":app") diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/mobile/ios/.gitignore @@ -0,0 +1,34 @@ +**/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 diff --git a/mobile/ios/Flutter/AppFrameworkInfo.plist b/mobile/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/mobile/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/mobile/ios/Flutter/Debug.xcconfig b/mobile/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/mobile/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile/ios/Flutter/Release.xcconfig b/mobile/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/mobile/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..abfdfb5 --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,644 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 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 */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 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 = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 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 */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + 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 = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + 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; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + 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 */ + 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"; + }; +/* 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 */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift 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 = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* 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 = 13.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)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.matchLiveTv; + 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; + 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.matchlivetv.matchLiveTv.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; + 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.matchlivetv.matchLiveTv.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; + 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.matchlivetv.matchLiveTv.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 = 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; + 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 = 13.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 = 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 = 13.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)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.matchLiveTv; + 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)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.matchLiveTv; + 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 */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c3fedb2 --- /dev/null +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/mobile/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "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" : "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" : "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" + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "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" + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# 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. \ No newline at end of file diff --git a/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner/Base.lproj/Main.storyboard b/mobile/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/mobile/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist new file mode 100644 index 0000000..58e4999 --- /dev/null +++ b/mobile/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Match Live Tv + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + match_live_tv + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/mobile/ios/Runner/Runner-Bridging-Header.h b/mobile/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/mobile/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/mobile/ios/Runner/SceneDelegate.swift b/mobile/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/mobile/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/mobile/ios/RunnerTests/RunnerTests.swift b/mobile/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/mobile/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +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. + } + +} diff --git a/mobile/lib/app/router.dart b/mobile/lib/app/router.dart new file mode 100644 index 0000000..566306b --- /dev/null +++ b/mobile/lib/app/router.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../features/auth/login_screen.dart'; +import '../features/auth/splash_screen.dart'; +import '../features/camera_mode/camera_screen.dart'; +import '../features/controller_mode/controller_screen.dart'; +import '../features/archive/archive_screen.dart'; +import '../features/matches/matches_screen.dart'; +import '../features/setup_wizard/wizard_shell.dart'; +import '../providers/auth_provider.dart'; + +/// Notifica GoRouter solo su cambi auth rilevanti (non su ogni loading tick). +class _AuthRefreshNotifier extends ChangeNotifier { + _AuthRefreshNotifier(this._ref) { + _ref.listen(authProvider, (prev, next) { + final authChanged = prev?.isAuthenticated != next.isAuthenticated; + if (authChanged) notifyListeners(); + }); + } + + final Ref _ref; +} + +final routerProvider = Provider((ref) { + final refresh = _AuthRefreshNotifier(ref); + + return GoRouter( + initialLocation: '/', + refreshListenable: refresh, + redirect: (context, state) { + final auth = ref.read(authProvider); + final loggingIn = state.matchedLocation == '/login'; + final onSplash = state.matchedLocation == '/'; + + if (onSplash) return null; + + if (!auth.isAuthenticated && !loggingIn) { + return '/login'; + } + + if (auth.isAuthenticated && loggingIn) { + return '/matches'; + } + + return null; + }, + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const SplashScreen(), + ), + GoRoute( + path: '/login', + builder: (context, state) => const LoginScreen(), + ), + GoRoute( + path: '/matches', + builder: (context, state) => const MatchesScreen(), + ), + GoRoute( + path: '/archive', + builder: (context, state) => const ArchiveScreen(), + ), + GoRoute( + path: '/setup/:matchId/:step', + builder: (context, state) { + final matchId = state.pathParameters['matchId']!; + final step = int.tryParse(state.pathParameters['step'] ?? '1') ?? 1; + return WizardShell(matchId: matchId, step: step); + }, + ), + GoRoute( + path: '/session/:id/camera', + builder: (context, state) { + final sessionId = state.pathParameters['id']!; + return CameraScreen(sessionId: sessionId); + }, + ), + GoRoute( + path: '/session/:id/controller', + builder: (context, state) { + final sessionId = state.pathParameters['id']!; + return ControllerScreen(sessionId: sessionId); + }, + ), + ], + ); +}); diff --git a/mobile/lib/app/theme.dart b/mobile/lib/app/theme.dart new file mode 100644 index 0000000..64bd13c --- /dev/null +++ b/mobile/lib/app/theme.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +/// Brand Match Live TV — dark theme, rosso #FF2D2D, Barlow Condensed. +class AppTheme { + AppTheme._(); + + static const Color primaryRed = Color(0xFFFF2D2D); + static const Color background = Color(0xFF0A0A0A); + static const Color surface = Color(0xFF1E1E1E); + static const Color surfaceElevated = Color(0xFF2A2A2A); + static const Color accentYellow = Color(0xFFF5C518); + static const Color successGreen = Color(0xFF22C55E); + static const Color textSecondary = Color(0xFF9CA3AF); + + static TextTheme get _textTheme { + final base = GoogleFonts.barlowCondensedTextTheme( + ThemeData.dark().textTheme, + ); + return base.copyWith( + displayLarge: base.displayLarge?.copyWith( + fontWeight: FontWeight.w900, + letterSpacing: -0.5, + ), + displayMedium: base.displayMedium?.copyWith(fontWeight: FontWeight.w900), + displaySmall: base.displaySmall?.copyWith(fontWeight: FontWeight.w900), + headlineLarge: base.headlineLarge?.copyWith(fontWeight: FontWeight.w900), + headlineMedium: base.headlineMedium?.copyWith(fontWeight: FontWeight.w800), + headlineSmall: base.headlineSmall?.copyWith(fontWeight: FontWeight.w800), + titleLarge: base.titleLarge?.copyWith(fontWeight: FontWeight.w700), + titleMedium: base.titleMedium?.copyWith(fontWeight: FontWeight.w700), + labelLarge: base.labelLarge?.copyWith( + fontWeight: FontWeight.w800, + letterSpacing: 1.2, + ), + bodyLarge: base.bodyLarge?.copyWith(fontWeight: FontWeight.w500), + bodyMedium: base.bodyMedium?.copyWith( + fontWeight: FontWeight.w500, + color: textSecondary, + ), + ); + } + + static ThemeData get dark { + return ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + scaffoldBackgroundColor: background, + colorScheme: const ColorScheme.dark( + primary: primaryRed, + secondary: accentYellow, + surface: surface, + error: primaryRed, + onPrimary: Colors.white, + onSecondary: Colors.black, + onSurface: Colors.white, + ), + textTheme: _textTheme, + appBarTheme: AppBarTheme( + backgroundColor: background, + elevation: 0, + centerTitle: true, + titleTextStyle: _textTheme.titleLarge?.copyWith(color: Colors.white), + iconTheme: const IconThemeData(color: Colors.white), + ), + cardTheme: CardThemeData( + color: surface, + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: surfaceElevated, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + labelStyle: _textTheme.bodyMedium, + hintStyle: _textTheme.bodyMedium, + ), + segmentedButtonTheme: SegmentedButtonThemeData( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) return primaryRed; + return surfaceElevated; + }), + foregroundColor: WidgetStateProperty.all(Colors.white), + ), + ), + dividerColor: surfaceElevated, + ); + } +} diff --git a/mobile/lib/core/auth_storage.dart b/mobile/lib/core/auth_storage.dart new file mode 100644 index 0000000..ed379dc --- /dev/null +++ b/mobile/lib/core/auth_storage.dart @@ -0,0 +1,64 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +import '../shared/models/user.dart'; + +class AuthStorage { + static const _keyAccess = 'auth_access_token'; + static const _keyRefresh = 'auth_refresh_token'; + static const _keyUserId = 'auth_user_id'; + static const _keyUserEmail = 'auth_user_email'; + static const _keyUserName = 'auth_user_name'; + static const _keyUserRole = 'auth_user_role'; + + static Future<({User user, String accessToken, String refreshToken})?> load() async { + final prefs = await SharedPreferences.getInstance(); + final access = prefs.getString(_keyAccess); + final refresh = prefs.getString(_keyRefresh); + final userId = prefs.getString(_keyUserId); + final email = prefs.getString(_keyUserEmail); + final name = prefs.getString(_keyUserName); + if (access == null || + access.isEmpty || + refresh == null || + refresh.isEmpty || + userId == null || + email == null || + name == null) { + return null; + } + return ( + user: User( + id: userId, + email: email, + name: name, + role: prefs.getString(_keyUserRole) ?? 'coach', + ), + accessToken: access, + refreshToken: refresh, + ); + } + + static Future save({ + required User user, + required String accessToken, + required String refreshToken, + }) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_keyAccess, accessToken); + await prefs.setString(_keyRefresh, refreshToken); + await prefs.setString(_keyUserId, user.id); + await prefs.setString(_keyUserEmail, user.email); + await prefs.setString(_keyUserName, user.name); + await prefs.setString(_keyUserRole, user.role); + } + + static Future clear() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_keyAccess); + await prefs.remove(_keyRefresh); + await prefs.remove(_keyUserId); + await prefs.remove(_keyUserEmail); + await prefs.remove(_keyUserName); + await prefs.remove(_keyUserRole); + } +} diff --git a/mobile/lib/core/config.dart b/mobile/lib/core/config.dart new file mode 100644 index 0000000..035886f --- /dev/null +++ b/mobile/lib/core/config.dart @@ -0,0 +1,24 @@ +/// Configurazione globale dell'app Match Live TV. +class AppConfig { + AppConfig._(); + + /// URL base API Rails. Default emulatore Android → host localhost. + static const String apiBaseUrl = String.fromEnvironment( + 'API_BASE_URL', + defaultValue: 'http://10.0.2.2:3000', + ); + + static String get apiV1 => '$apiBaseUrl/api/v1'; + + static String get cableUrl { + final uri = Uri.parse(apiBaseUrl); + final wsScheme = uri.scheme == 'https' ? 'wss' : 'ws'; + final defaultPort = uri.scheme == 'https' ? 443 : 80; + final port = uri.hasPort ? uri.port : defaultPort; + // Evita :443/:80 espliciti — alcuni client WebSocket/NPM li gestiscono male + if (port == defaultPort) { + return '$wsScheme://${uri.host}/cable'; + } + return '$wsScheme://${uri.host}:$port/cable'; + } +} diff --git a/mobile/lib/features/archive/archive_screen.dart b/mobile/lib/features/archive/archive_screen.dart new file mode 100644 index 0000000..6a38c52 --- /dev/null +++ b/mobile/lib/features/archive/archive_screen.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../app/theme.dart'; +import '../../shared/premium_dialog.dart'; +import '../matches/team_providers.dart'; + +class ArchiveScreen extends ConsumerWidget { + const ArchiveScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final teamAsync = ref.watch(primaryTeamProvider); + final theme = Theme.of(context); + + return Scaffold( + appBar: AppBar( + title: const Text('Archivio gare'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => context.go('/matches'), + ), + ), + body: teamAsync.when( + loading: () => const Center( + child: CircularProgressIndicator(color: AppTheme.primaryRed), + ), + error: (e, _) => Center(child: Text('Errore: $e')), + data: (team) { + if (team == null) { + return const Center(child: Text('Nessuna squadra')); + } + if (!team.canUseRecordings) { + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Archivio replay con Premium Light o Full', + style: theme.textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + FilledButton( + onPressed: () => showPremiumRequiredDialog( + context, + message: 'Salva e rivedi le gare per 90 giorni con il piano Premium.', + billingUrl: team.billingUrl, + ), + style: FilledButton.styleFrom( + backgroundColor: AppTheme.primaryRed, + ), + child: const Text('Scopri Premium'), + ), + ], + ), + ), + ); + } + + final recsAsync = ref.watch(recordingsProvider(team.id)); + return recsAsync.when( + loading: () => const Center( + child: CircularProgressIndicator(color: AppTheme.primaryRed), + ), + error: (e, _) => Center(child: Text('$e')), + data: (recs) { + if (recs.isEmpty) { + return const Center(child: Text('Nessuna gara in archivio')); + } + final df = DateFormat('d MMM yyyy · HH:mm', 'it_IT'); + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: recs.length, + itemBuilder: (context, i) { + final r = recs[i]; + return Card( + color: AppTheme.surface, + child: ListTile( + title: Text('${r.teamName} vs ${r.opponentName}'), + subtitle: Text( + r.endedAt != null ? df.format(r.endedAt!.toLocal()) : '—', + ), + trailing: const Icon(Icons.play_circle_outline), + onTap: () async { + final url = r.replayUrl; + if (url == null) return; + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl( + uri, + mode: team.canDownloadOnPhone + ? LaunchMode.externalApplication + : LaunchMode.inAppBrowserView, + ); + } + }, + ), + ); + }, + ); + }, + ); + }, + ), + ); + } +} diff --git a/mobile/lib/features/auth/login_screen.dart b/mobile/lib/features/auth/login_screen.dart new file mode 100644 index 0000000..291404e --- /dev/null +++ b/mobile/lib/features/auth/login_screen.dart @@ -0,0 +1,157 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../app/theme.dart'; +import '../../features/matches/matches_screen.dart'; +import '../../features/matches/team_providers.dart'; +import '../../providers/auth_provider.dart'; +import '../../shared/api_client.dart'; +import '../../shared/widgets/match_live_wordmark.dart'; +import '../../shared/widgets/primary_cta.dart'; +import '../../shared/widgets/screen_insets.dart'; + +class LoginScreen extends ConsumerStatefulWidget { + const LoginScreen({super.key}); + + @override + ConsumerState createState() => _LoginScreenState(); +} + +class _LoginScreenState extends ConsumerState { + final _formKey = GlobalKey(); + final _emailController = TextEditingController(text: 'coach@matchlivetv.test'); + final _passwordController = TextEditingController(text: 'password123'); + bool _obscure = true; + + @override + void dispose() { + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + Future _login() async { + if (!_formKey.currentState!.validate()) return; + + final connectivity = await Connectivity().checkConnectivity(); + if (connectivity.contains(ConnectivityResult.none)) { + ref.read(authProvider.notifier).setError( + 'Nessuna connessione. Disattiva la modalità aereo e verifica Wi‑Fi.', + ); + return; + } + + ref.read(authProvider.notifier).setLoading(true); + try { + final client = ApiClient(); + final result = await client.login( + email: _emailController.text.trim(), + password: _passwordController.text, + ); + ref.read(authProvider.notifier).setSession( + user: result.user, + accessToken: result.tokens.accessToken, + refreshToken: result.tokens.refreshToken, + ); + ref.invalidate(teamsProvider); + ref.invalidate(matchesProvider); + ref.read(authProvider.notifier).setLoading(false); + if (mounted) context.go('/matches'); + return; + } on DioException catch (e) { + final message = switch (e.type) { + DioExceptionType.connectionTimeout || + DioExceptionType.receiveTimeout || + DioExceptionType.connectionError => + 'Server non raggiungibile. Verifica che il backend sia avviato (docker compose up).', + DioExceptionType.badResponse when e.response?.statusCode == 401 => + 'Email o password non corretti', + _ => 'Errore di rete: ${e.message}', + }; + ref.read(authProvider.notifier).setError(message); + } catch (e) { + ref.read(authProvider.notifier).setError('Errore imprevisto: $e'); + } + ref.read(authProvider.notifier).setLoading(false); + } + + @override + Widget build(BuildContext context) { + final auth = ref.watch(authProvider); + final theme = Theme.of(context); + + return Scaffold( + body: SingleChildScrollView( + padding: ScreenInsets.contentPadding(context, horizontal: 24, vertical: 16), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 32), + const Center(child: MatchLiveWordmark(showSlogan: true)), + const SizedBox(height: 48), + Text( + 'ACCEDI', + style: theme.textTheme.headlineMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + 'Gestisci le dirette della tua squadra', + style: theme.textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + TextFormField( + controller: _emailController, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration( + labelText: 'Email', + hintText: 'coach@squadra.it', + ), + validator: (v) => + v == null || v.isEmpty ? 'Inserisci l\'email' : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: _passwordController, + obscureText: _obscure, + decoration: InputDecoration( + labelText: 'Password', + suffixIcon: IconButton( + icon: Icon( + _obscure ? Icons.visibility : Icons.visibility_off, + ), + onPressed: () => setState(() => _obscure = !_obscure), + ), + ), + validator: (v) => + v == null || v.isEmpty ? 'Inserisci la password' : null, + ), + if (auth.error != null) ...[ + const SizedBox(height: 12), + Text( + auth.error!, + style: theme.textTheme.bodyMedium?.copyWith( + color: AppTheme.primaryRed, + ), + textAlign: TextAlign.center, + ), + ], + const SizedBox(height: 32), + PrimaryCta( + label: 'ACCEDI', + loading: auth.loading, + onPressed: _login, + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/auth/splash_screen.dart b/mobile/lib/features/auth/splash_screen.dart new file mode 100644 index 0000000..a591a31 --- /dev/null +++ b/mobile/lib/features/auth/splash_screen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../app/theme.dart'; +import '../../providers/auth_provider.dart'; +import '../../shared/widgets/match_live_wordmark.dart'; +import '../../shared/widgets/screen_insets.dart'; + +/// Splash con wordmark e slogan. +class SplashScreen extends ConsumerStatefulWidget { + const SplashScreen({super.key}); + + @override + ConsumerState createState() => _SplashScreenState(); +} + +class _SplashScreenState extends ConsumerState { + @override + void initState() { + super.initState(); + _bootstrap(); + } + + Future _bootstrap() async { + await ref.read(authProvider.notifier).restoreSession(); + await Future.delayed(const Duration(milliseconds: 800)); + if (!mounted) return; + final auth = ref.read(authProvider); + if (auth.isAuthenticated) { + context.go('/matches'); + } else { + context.go('/login'); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppTheme.background, + body: AppSafeArea( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const MatchLiveWordmark(showSlogan: true), + const SizedBox(height: 48), + const CircularProgressIndicator(color: AppTheme.primaryRed), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/camera_mode/camera_screen.dart b/mobile/lib/features/camera_mode/camera_screen.dart new file mode 100644 index 0000000..0c5e8e9 --- /dev/null +++ b/mobile/lib/features/camera_mode/camera_screen.dart @@ -0,0 +1,652 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:battery_plus/battery_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; + +import '../../app/theme.dart'; +import '../../platform/streaming_channel.dart'; +import '../../platform/streaming_preview.dart'; +import '../../providers/match_rules_provider.dart'; +import '../../providers/score_provider.dart'; +import '../shared/live_score_actions.dart'; +import '../../shared/api_client.dart'; +import '../../shared/models/score_state.dart'; +import '../../providers/session_provider.dart'; +import '../../shared/websocket_service.dart'; +import '../../shared/widgets/camera_compact_metrics.dart'; +import '../../shared/widgets/camera_score_controls.dart'; +import '../../shared/widgets/destructive_cta.dart'; +import '../../shared/widgets/end_stream_dialog.dart'; +import '../../shared/widgets/live_badge.dart'; +import '../../shared/widgets/screen_insets.dart'; + +class CameraScreen extends ConsumerStatefulWidget { + const CameraScreen({super.key, required this.sessionId}); + + final String sessionId; + + @override + ConsumerState createState() => _CameraScreenState(); +} + +class _CameraScreenState extends ConsumerState { + static const _previewKey = ValueKey('camera_streaming_preview'); + + Timer? _elapsedTimer; + Timer? _telemetryTimer; + Timer? _statsTimer; + int _batteryLevel = 100; + int? _lastDelta; + int _prevHomePoints = 0; + int _prevAwayPoints = 0; + + double _bitrateMbps = 0; + int _fps = 0; + String _networkType = '4G'; + int _viewerCount = 0; + + StreamSubscription>? _metricsSub; + Orientation? _lastOrientation; + + @override + void initState() { + super.initState(); + WakelockPlus.enable(); + _lockOrientations(); + _bootstrap(); + _readBattery(); + _elapsedTimer = Timer.periodic(const Duration(seconds: 1), (_) { + final session = ref.read(sessionProvider).session; + if (session?.startedAt != null) { + ref.read(sessionProvider.notifier).setElapsed( + DateTime.now().difference(session!.startedAt!), + ); + } + }); + _telemetryTimer = Timer.periodic(const Duration(seconds: 10), (_) => _sendTelemetry()); + _statsTimer = Timer.periodic(const Duration(seconds: 30), (_) => _fetchStats()); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final orientation = MediaQuery.orientationOf(context); + if (_lastOrientation != null && _lastOrientation != orientation) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + await StreamingChannel.bindPreview(); + }); + } + _lastOrientation = orientation; + } + + Future _ensurePermissions() async { + final camera = await Permission.camera.request(); + final mic = await Permission.microphone.request(); + if (camera.isGranted && mic.isGranted) return true; + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Servono permessi fotocamera e microfono per la diretta'), + ), + ); + } + return false; + } + + Future _bootstrap() async { + try { + if (!await _ensurePermissions()) return; + + final client = ref.read(apiClientProvider); + var session = await client.fetchSession(widget.sessionId); + + if (session.isTerminal) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Questa diretta è già terminata'), + ), + ); + context.go('/matches'); + } + return; + } + + final match = await client.fetchMatch(session.matchId); + ref.read(sessionProvider.notifier).setSession(session, match: match); + ref.read(sessionProvider.notifier).setDeviceRole(DeviceRole.camera); + + if (session.score != null) { + ref.read(scoreProvider.notifier).reset(session.score!); + } + + // Ripresa dopo crash: la sessione resta connecting/live, riallineiamo lo stato. + if (session.status == 'idle' || session.status == 'paused') { + session = await client.startSession(widget.sessionId); + ref.read(sessionProvider.notifier).setSession(session, match: match); + } + + final ws = ref.read(websocketServiceProvider); + await ws.connect(sessionId: widget.sessionId, deviceRole: 'camera'); + + await Future.delayed(const Duration(milliseconds: 300)); + await StreamingChannel.bindPreview(); + + final fresh = await client.fetchSession(widget.sessionId); + ref.read(sessionProvider.notifier).setSession(fresh, match: match); + + if (fresh.rtmpIngestUrl != null) { + await Future.delayed(const Duration(milliseconds: 500)); + await StreamingChannel.startStream( + rtmpUrl: fresh.rtmpIngestUrl!, + targetBitrate: fresh.targetBitrate, + targetFps: 30, + ); + if (mounted && fresh.canResumeBroadcast) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Diretta ripresa — stai di nuovo in onda'), + duration: Duration(seconds: 3), + ), + ); + } + } + + _metricsSub = StreamingChannel.metricsStream + .receiveBroadcastStream() + .map((e) => Map.from(e as Map)) + .listen((event) { + if (event['type'] != 'metrics') return; + final metrics = Map.from( + (event['metrics'] as Map?)?.map((k, v) => MapEntry(k.toString(), v)) ?? {}, + ); + setState(() { + _bitrateMbps = (metrics['bitrateKbps'] as num? ?? 0) / 1000; + _fps = (metrics['fps'] as num?)?.toInt() ?? 0; + }); + }); + } on PlatformException catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Errore streaming: ${e.message ?? e.code}')), + ); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Errore avvio camera: $e')), + ); + } + } + } + + Future _readBattery() async { + final level = await Battery().batteryLevel; + if (mounted) setState(() => _batteryLevel = level); + } + + Future _sendTelemetry() async { + try { + await ref.read(apiClientProvider).postTelemetry( + sessionId: widget.sessionId, + deviceRole: 'camera', + batteryLevel: _batteryLevel, + networkType: _networkType, + currentBitrate: (_bitrateMbps * 1_000_000).round(), + fps: _fps, + ); + } catch (_) {} + } + + Future _fetchStats() async { + try { + final stats = await ref.read(apiClientProvider).youtubeStats(widget.sessionId); + if (mounted) setState(() => _viewerCount = stats.concurrentViewers); + } catch (_) {} + } + + Future _lockOrientations() async { + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + DeviceOrientation.portraitUp, + ]); + } + + Future _unlockOrientation() async { + await SystemChrome.setPreferredOrientations(DeviceOrientation.values); + } + + void _syncScore() { + ref.read(websocketServiceProvider).sendScoreUpdate(ref.read(scoreProvider)); + } + + Future _homePoint() async { + ref.read(scoreProvider.notifier).incrementHome(); + _syncScore(); + final match = ref.read(sessionProvider).match; + await LiveScoreActions(ref).afterPointChange( + context, + homeName: match?.teamName ?? 'Casa', + awayName: match?.opponentName ?? 'Ospiti', + onCloseSetConfirmed: () => _closeSetConfirmed(), + ); + } + + Future _awayPoint() async { + ref.read(scoreProvider.notifier).incrementAway(); + _syncScore(); + final match = ref.read(sessionProvider).match; + await LiveScoreActions(ref).afterPointChange( + context, + homeName: match?.teamName ?? 'Casa', + awayName: match?.opponentName ?? 'Ospiti', + onCloseSetConfirmed: () => _closeSetConfirmed(), + ); + } + + void _homeMinus() { + ref.read(scoreProvider.notifier).decrementHome(); + _syncScore(); + } + + void _awayMinus() { + ref.read(scoreProvider.notifier).decrementAway(); + _syncScore(); + } + + Future _closeSet() async { + await LiveScoreActions(ref).requestCloseSet( + context, + onCloseSetConfirmed: () => _closeSetConfirmed(), + ); + } + + Future _closeSetConfirmed() async { + ref.read(scoreProvider.notifier).closeSet(); + _syncScore(); + final match = ref.read(sessionProvider).match; + await LiveScoreActions(ref).afterCloseSet( + context, + homeName: match?.teamName ?? 'Casa', + awayName: match?.opponentName ?? 'Ospiti', + onStopStream: _closeStreamPermanently, + ); + } + + Future _leaveSession() async { + try { + await StreamingChannel.stopStream(); + } catch (_) {} + try { + await ref.read(websocketServiceProvider).disconnect(); + } catch (_) {} + if (mounted) context.go('/matches'); + } + + /// Pausa temporanea: si può riprendere la stessa sessione. + Future _interruptStream() async { + try { + await StreamingChannel.stopStream(); + } catch (_) {} + try { + await ref.read(apiClientProvider).pauseSession(widget.sessionId); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Pausa sessione: $e')), + ); + } + } + await _leaveSession(); + } + + /// Chiusura definitiva: sessione ended, pagina web senza player. + Future _closeStreamPermanently() async { + if (!mounted) return; + final ok = await confirmEndStream(context); + if (!ok || !mounted) return; + + try { + await StreamingChannel.stopStream(); + } catch (_) {} + try { + ref.read(websocketServiceProvider).sendCommand('stop_stream'); + } catch (_) {} + try { + await ref.read(apiClientProvider).stopSession(widget.sessionId); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Chiusura sessione: $e')), + ); + } + } + await _leaveSession(); + } + + void _trackScoreDelta(ScoreState score) { + if (score.homePoints != _prevHomePoints) { + _lastDelta = score.homePoints - _prevHomePoints; + } else if (score.awayPoints != _prevAwayPoints) { + _lastDelta = score.awayPoints - _prevAwayPoints; + } + _prevHomePoints = score.homePoints; + _prevAwayPoints = score.awayPoints; + } + + @override + void dispose() { + _elapsedTimer?.cancel(); + _telemetryTimer?.cancel(); + _statsTimer?.cancel(); + _metricsSub?.cancel(); + WakelockPlus.disable(); + _unlockOrientation(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final sessionState = ref.watch(sessionProvider); + final score = ref.watch(scoreProvider); + _trackScoreDelta(score); + + final match = sessionState.match; + final homeName = match?.teamName ?? 'HOME'; + final awayName = match?.opponentName ?? 'AWAY'; + final isLandscape = + MediaQuery.orientationOf(context) == Orientation.landscape; + final rules = ref.watch(matchScoringRulesProvider); + final pointsTarget = rules.pointsTarget(score.currentSet); + // Preview unica: non viene mai smontata al cambio orientamento. + return Scaffold( + backgroundColor: isLandscape ? Colors.black : AppTheme.background, + body: Stack( + fit: StackFit.expand, + children: [ + const Positioned.fill( + child: NativeStreamingPreview( + key: _previewKey, + showGrid: true, + platformLabel: 'LIVE', + ), + ), + if (isLandscape) + _LandscapeOverlay( + elapsed: sessionState.elapsed, + batteryLevel: _batteryLevel, + homeName: homeName, + awayName: awayName, + score: score, + lastDelta: _lastDelta, + networkType: _networkType, + bitrateMbps: _bitrateMbps, + fps: _fps, + viewerCount: _viewerCount, + onHomePoint: () => _homePoint(), + onAwayPoint: () => _awayPoint(), + onHomeMinus: _homeMinus, + onAwayMinus: _awayMinus, + onCloseSet: () => _closeSet(), + onInterrupt: _interruptStream, + onCloseStream: _closeStreamPermanently, + pointsTarget: pointsTarget, + ) + else + _PortraitOverlay( + elapsed: sessionState.elapsed, + batteryLevel: _batteryLevel, + homeName: homeName, + awayName: awayName, + score: score, + networkType: _networkType, + bitrateMbps: _bitrateMbps, + fps: _fps, + viewerCount: _viewerCount, + onHomePoint: () => _homePoint(), + onAwayPoint: () => _awayPoint(), + onHomeMinus: _homeMinus, + onAwayMinus: _awayMinus, + onCloseSet: () => _closeSet(), + onInterrupt: _interruptStream, + onCloseStream: _closeStreamPermanently, + pointsTarget: pointsTarget, + ), + ], + ), + ); + } +} + +class _PortraitOverlay extends StatelessWidget { + const _PortraitOverlay({ + required this.elapsed, + required this.batteryLevel, + required this.homeName, + required this.awayName, + required this.score, + required this.networkType, + required this.bitrateMbps, + required this.fps, + required this.viewerCount, + required this.onHomePoint, + required this.onAwayPoint, + required this.onHomeMinus, + required this.onAwayMinus, + required this.onCloseSet, + required this.onInterrupt, + required this.onCloseStream, + required this.pointsTarget, + }); + + final Duration elapsed; + final int batteryLevel; + final String homeName; + final String awayName; + final ScoreState score; + final String networkType; + final double bitrateMbps; + final int fps; + final int viewerCount; + final VoidCallback onHomePoint; + final VoidCallback onAwayPoint; + final VoidCallback onHomeMinus; + final VoidCallback onAwayMinus; + final VoidCallback onCloseSet; + final VoidCallback onInterrupt; + final Future Function() onCloseStream; + final int pointsTarget; + + @override + Widget build(BuildContext context) { + final inset = ScreenInsets.cameraOverlay(context); + + return Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(inset.left, inset.top, inset.right, 6), + child: Row( + children: [ + LiveBadge(elapsed: elapsed), + const Spacer(), + CameraCompactMetrics( + networkType: networkType, + bitrateMbps: bitrateMbps, + fps: fps, + batteryLevel: batteryLevel, + viewerCount: viewerCount > 0 ? viewerCount : null, + ), + ], + ), + ), + const Expanded(child: SizedBox.expand()), + Container( + color: AppTheme.background, + padding: EdgeInsets.fromLTRB( + inset.left, + 12, + inset.right, + math.max(inset.bottom, 16), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CameraScoreControls( + homeName: homeName, + awayName: awayName, + homePoints: score.homePoints, + awayPoints: score.awayPoints, + currentSet: score.currentSet, + homeSets: score.homeSets, + awaySets: score.awaySets, + pointsTarget: pointsTarget, + onHomePoint: onHomePoint, + onAwayPoint: onAwayPoint, + onHomeMinus: onHomeMinus, + onAwayMinus: onAwayMinus, + onCloseSet: onCloseSet, + ), + const SizedBox(height: 10), + OutlinedButton( + onPressed: onInterrupt, + child: const Text('Interrompi (riprendibile)'), + ), + const SizedBox(height: 8), + DestructiveCta( + label: 'Chiudi diretta', + onPressed: () => onCloseStream(), + ), + ], + ), + ), + ], + ); + } +} + +class _LandscapeOverlay extends StatelessWidget { + const _LandscapeOverlay({ + required this.elapsed, + required this.batteryLevel, + required this.homeName, + required this.awayName, + required this.score, + required this.lastDelta, + required this.networkType, + required this.bitrateMbps, + required this.fps, + required this.viewerCount, + required this.onHomePoint, + required this.onAwayPoint, + required this.onHomeMinus, + required this.onAwayMinus, + required this.onCloseSet, + required this.onInterrupt, + required this.onCloseStream, + required this.pointsTarget, + }); + + final Duration elapsed; + final int batteryLevel; + final String homeName; + final String awayName; + final ScoreState score; + final int? lastDelta; + final String networkType; + final double bitrateMbps; + final int fps; + final int viewerCount; + final VoidCallback onHomePoint; + final VoidCallback onAwayPoint; + final VoidCallback onHomeMinus; + final VoidCallback onAwayMinus; + final VoidCallback onCloseSet; + final VoidCallback onInterrupt; + final Future Function() onCloseStream; + final int pointsTarget; + + @override + Widget build(BuildContext context) { + final inset = ScreenInsets.cameraOverlay(context); + + return Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(inset.left, inset.top, inset.right, 6), + child: Row( + children: [ + LiveBadge(elapsed: elapsed, compact: true), + const Spacer(), + CameraCompactMetrics( + networkType: networkType, + bitrateMbps: bitrateMbps, + fps: fps, + batteryLevel: batteryLevel, + viewerCount: viewerCount > 0 ? viewerCount : null, + light: true, + ), + ], + ), + ), + const Expanded(child: SizedBox.expand()), + Container( + color: Colors.black.withValues(alpha: 0.82), + padding: EdgeInsets.fromLTRB( + inset.left, + 10, + inset.right, + math.max(inset.bottom, 12), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CameraScoreControls( + compact: true, + homeName: homeName, + awayName: awayName, + homePoints: score.homePoints, + awayPoints: score.awayPoints, + currentSet: score.currentSet, + homeSets: score.homeSets, + awaySets: score.awaySets, + lastDelta: lastDelta, + pointsTarget: pointsTarget, + onHomePoint: onHomePoint, + onAwayPoint: onAwayPoint, + onHomeMinus: onHomeMinus, + onAwayMinus: onAwayMinus, + onCloseSet: onCloseSet, + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: onInterrupt, + child: const Text('Interrompi', style: TextStyle(fontSize: 11)), + ), + ), + const SizedBox(width: 8), + Expanded( + child: DestructiveCta( + label: 'Chiudi', + compact: true, + onPressed: () => onCloseStream(), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/mobile/lib/features/camera_mode/camera_screen_landscape.dart b/mobile/lib/features/camera_mode/camera_screen_landscape.dart new file mode 100644 index 0000000..7cd3320 --- /dev/null +++ b/mobile/lib/features/camera_mode/camera_screen_landscape.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; + +import '../../shared/models/score_state.dart'; +import '../../shared/widgets/destructive_cta.dart'; +import '../../shared/widgets/live_badge.dart'; +import '../../shared/widgets/metric_card.dart'; +import '../../shared/widgets/score_overlay_bar.dart'; +import '../../platform/streaming_preview.dart'; + +class CameraScreenLandscape extends StatelessWidget { + const CameraScreenLandscape({ + super.key, + required this.elapsed, + required this.batteryLevel, + required this.homeName, + required this.awayName, + required this.score, + required this.lastDelta, + required this.networkType, + required this.bitrateMbps, + required this.fps, + required this.viewerCount, + this.platformLabel = 'LIVE', + required this.onStop, + }); + + final Duration elapsed; + final int batteryLevel; + final String homeName; + final String awayName; + final ScoreState score; + final int? lastDelta; + final String networkType; + final double bitrateMbps; + final int fps; + final int viewerCount; + final String platformLabel; + final VoidCallback onStop; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: Stack( + fit: StackFit.expand, + children: [ + NativeStreamingPreview(showGrid: false, platformLabel: platformLabel), + Positioned( + top: 12, + left: 12, + child: LiveBadge(elapsed: elapsed, compact: true), + ), + Positioned( + top: 12, + right: 12, + child: Row( + children: [ + Icon(Icons.battery_std, size: 16, color: Colors.white70), + const SizedBox(width: 4), + Text( + '$batteryLevel%', + style: const TextStyle(color: Colors.white70, fontSize: 12), + ), + ], + ), + ), + Positioned( + bottom: 16, + left: 16, + child: ScoreOverlayBar( + homeName: homeName, + awayName: awayName, + homePoints: score.homePoints, + awayPoints: score.awayPoints, + currentSet: score.currentSet, + homeSets: score.homeSets, + awaySets: score.awaySets, + compact: true, + lastDelta: lastDelta, + ), + ), + Positioned( + bottom: 16, + left: 0, + right: 100, + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + MetricCard( + label: 'Segnale', + value: networkType, + icon: Icons.signal_cellular_alt, + expand: false, + ), + const SizedBox(width: 6), + MetricCard( + label: 'Mbps', + value: bitrateMbps.toStringAsFixed(1), + icon: Icons.speed, + expand: false, + ), + const SizedBox(width: 6), + MetricCard( + label: 'fps', + value: '$fps', + icon: Icons.movie, + expand: false, + ), + const SizedBox(width: 6), + MetricCard( + label: 'Spett.', + value: viewerCount > 0 ? '$viewerCount' : 'LIVE', + icon: Icons.visibility, + expand: false, + ), + ], + ), + ), + ), + Positioned( + bottom: 16, + right: 16, + child: DestructiveCta( + label: 'INTERROMPI', + compact: true, + onPressed: onStop, + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/features/camera_mode/camera_screen_portrait.dart b/mobile/lib/features/camera_mode/camera_screen_portrait.dart new file mode 100644 index 0000000..a76c41a --- /dev/null +++ b/mobile/lib/features/camera_mode/camera_screen_portrait.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; +import '../../shared/models/score_state.dart'; +import '../../shared/widgets/destructive_cta.dart'; +import '../../shared/widgets/live_badge.dart'; +import '../../shared/widgets/metric_card.dart'; +import '../../shared/widgets/score_overlay_bar.dart'; +import '../../platform/streaming_preview.dart'; + +class CameraScreenPortrait extends StatelessWidget { + const CameraScreenPortrait({ + super.key, + required this.elapsed, + required this.batteryLevel, + required this.homeName, + required this.awayName, + required this.score, + required this.networkType, + required this.bitrateMbps, + required this.fps, + required this.viewerCount, + required this.sessionId, + this.platformLabel = 'LIVE', + required this.onStop, + }); + + final Duration elapsed; + final int batteryLevel; + final String homeName; + final String awayName; + final ScoreState score; + final String networkType; + final double bitrateMbps; + final int fps; + final int viewerCount; + final String sessionId; + final String platformLabel; + final VoidCallback onStop; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + backgroundColor: AppTheme.background, + body: SafeArea( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + LiveBadge(elapsed: elapsed), + const Spacer(), + const Icon(Icons.battery_std, size: 18, color: AppTheme.textSecondary), + const SizedBox(width: 4), + Text('$batteryLevel%', style: theme.textTheme.labelLarge), + ], + ), + ), + Expanded( + flex: 3, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: NativeStreamingPreview( + showGrid: true, + platformLabel: platformLabel, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ScoreOverlayBar( + homeName: homeName, + awayName: awayName, + homePoints: score.homePoints, + awayPoints: score.awayPoints, + currentSet: score.currentSet, + homeSets: score.homeSets, + awaySets: score.awaySets, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + MetricCard( + label: 'Segnale', + value: networkType, + icon: Icons.signal_cellular_alt, + ), + const SizedBox(width: 8), + MetricCard( + label: 'Mbps', + value: bitrateMbps.toStringAsFixed(1), + icon: Icons.speed, + ), + const SizedBox(width: 8), + MetricCard(label: 'fps', value: '$fps', icon: Icons.movie), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(16), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppTheme.surface, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + children: [ + const Icon(Icons.play_circle, color: AppTheme.primaryRed), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(platformLabel, style: theme.textTheme.titleMedium), + Text( + viewerCount > 0 + ? '$viewerCount spettatori' + : 'IN DIRETTA', + style: theme.textTheme.bodyMedium, + ), + ], + ), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Text( + 'Registrazione locale attiva: /sd/match_$sessionId', + style: theme.textTheme.bodyMedium?.copyWith(fontSize: 11), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.all(16), + child: DestructiveCta(label: 'INTERROMPI', onPressed: onStop), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/features/controller_mode/controller_landscape.dart b/mobile/lib/features/controller_mode/controller_landscape.dart new file mode 100644 index 0000000..4791039 --- /dev/null +++ b/mobile/lib/features/controller_mode/controller_landscape.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; +import '../../providers/session_provider.dart'; +import '../../shared/models/score_state.dart'; +import '../../shared/widgets/connected_badge.dart'; +import '../../shared/widgets/destructive_cta.dart'; +import 'controller_screen.dart'; + +/// Layout gamepad a 3 colonne per uso landscape. +class ControllerLandscape extends StatelessWidget { + const ControllerLandscape({ + super.key, + required this.homeName, + required this.awayName, + required this.score, + required this.sessionState, + required this.onHomePoint, + required this.onAwayPoint, + required this.onHomeMinus, + required this.onAwayMinus, + required this.onCloseSet, + required this.onInterrupt, + required this.onCloseStream, + required this.pointsTarget, + }); + + final String homeName; + final String awayName; + final ScoreState score; + final SessionState sessionState; + final VoidCallback onHomePoint; + final VoidCallback onAwayPoint; + final VoidCallback onHomeMinus; + final VoidCallback onAwayMinus; + final VoidCallback onCloseSet; + final VoidCallback onInterrupt; + final Future Function() onCloseStream; + final int pointsTarget; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return SingleChildScrollView( + padding: const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: _TeamColumn( + teamLabel: 'CASA', + teamName: homeName, + sets: score.homeSets, + points: score.homePoints, + onPoint: onHomePoint, + onMinus: onHomeMinus, + ), + ), + Expanded( + flex: 2, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('REGIA', style: theme.textTheme.titleMedium), + const SizedBox(width: 12), + ConnectedBadge(connected: sessionState.connected), + ], + ), + const SizedBox(height: 8), + Text( + 'SET ${score.currentSet} → $pointsTarget pt', + style: theme.textTheme.headlineMedium?.copyWith( + color: AppTheme.accentYellow, + ), + ), + const SizedBox(height: 16), + YellowActionButton(label: 'CHIUDI SET', onPressed: onCloseSet), + const SizedBox(height: 16), + Text('ULTIME AZIONI', style: theme.textTheme.labelLarge), + const SizedBox(height: 8), + StreamMetricsFooter( + elapsed: sessionState.elapsed, + connected: sessionState.connected, + cameraDevice: sessionState.cameraDevice, + viewerCount: sessionState.viewerCount, + compact: true, + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: onInterrupt, + child: const Text('Interrompi'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: DestructiveCta( + label: 'Chiudi', + compact: true, + onPressed: () => onCloseStream(), + ), + ), + ], + ), + ], + ), + ), + ), + Expanded( + child: _TeamColumn( + teamLabel: 'OSPITI', + teamName: awayName, + sets: score.awaySets, + points: score.awayPoints, + onPoint: onAwayPoint, + onMinus: onAwayMinus, + alignRight: true, + ), + ), + ], + ), + ); + } +} + +class _TeamColumn extends StatelessWidget { + const _TeamColumn({ + required this.teamLabel, + required this.teamName, + required this.sets, + required this.points, + required this.onPoint, + required this.onMinus, + this.alignRight = false, + }); + + final String teamLabel; + final String teamName; + final int sets; + final int points; + final VoidCallback onPoint; + final VoidCallback onMinus; + final bool alignRight; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final crossAlign = + alignRight ? CrossAxisAlignment.end : CrossAxisAlignment.start; + + return Column( + crossAxisAlignment: crossAlign, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(teamLabel, style: theme.textTheme.labelLarge), + Text( + teamName.toUpperCase(), + style: theme.textTheme.titleLarge, + textAlign: alignRight ? TextAlign.right : TextAlign.left, + ), + const SizedBox(height: 8), + Text('SETS $sets', style: theme.textTheme.bodyMedium), + Text( + '$points', + style: theme.textTheme.displayLarge?.copyWith( + color: AppTheme.primaryRed, + fontSize: 56, + ), + ), + const SizedBox(height: 16), + ScoreTapButton(label: '+1', onTap: onPoint, large: true), + const SizedBox(height: 8), + OutlinedButton( + onPressed: points > 0 ? onMinus : null, + child: const Text('-1'), + ), + ], + ); + } +} diff --git a/mobile/lib/features/controller_mode/controller_portrait.dart b/mobile/lib/features/controller_mode/controller_portrait.dart new file mode 100644 index 0000000..72c611a --- /dev/null +++ b/mobile/lib/features/controller_mode/controller_portrait.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; +import '../../providers/session_provider.dart'; +import '../../shared/models/score_state.dart'; +import '../../shared/widgets/destructive_cta.dart'; +import '../../shared/widgets/screen_insets.dart'; +import 'controller_screen.dart'; + +class ControllerPortrait extends StatelessWidget { + const ControllerPortrait({ + super.key, + required this.homeName, + required this.awayName, + required this.score, + required this.sessionState, + required this.onHomePoint, + required this.onAwayPoint, + required this.onHomeMinus, + required this.onAwayMinus, + required this.onCloseSet, + required this.onInterrupt, + required this.onCloseStream, + required this.pointsTarget, + }); + + final String homeName; + final String awayName; + final ScoreState score; + final SessionState sessionState; + final VoidCallback onHomePoint; + final VoidCallback onAwayPoint; + final VoidCallback onHomeMinus; + final VoidCallback onAwayMinus; + final VoidCallback onCloseSet; + final VoidCallback onInterrupt; + final Future Function() onCloseStream; + final int pointsTarget; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final match = sessionState.match; + + return SingleChildScrollView( + padding: ScreenInsets.contentPadding(context, horizontal: 16, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (match != null) + Text( + '${match.sport.toUpperCase()} · ${match.category ?? ''} · ${match.phase ?? ''} · SET ${score.currentSet}', + style: theme.textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + _Scoreboard( + homeName: homeName, + awayName: awayName, + score: score, + pointsTarget: pointsTarget, + onHomePoint: onHomePoint, + onAwayPoint: onAwayPoint, + onHomeMinus: onHomeMinus, + onAwayMinus: onAwayMinus, + ), + const SizedBox(height: 12), + YellowActionButton(label: 'CHIUDI SET', onPressed: onCloseSet), + const SizedBox(height: 16), + StreamMetricsFooter( + elapsed: sessionState.elapsed, + connected: sessionState.connected, + cameraDevice: sessionState.cameraDevice, + viewerCount: sessionState.viewerCount, + ), + const SizedBox(height: 16), + OutlinedButton( + onPressed: onInterrupt, + child: const Text('Interrompi (riprendibile)'), + ), + const SizedBox(height: 8), + DestructiveCta( + label: 'Chiudi diretta', + onPressed: () => onCloseStream(), + ), + ], + ), + ); + } +} + +class _Scoreboard extends StatelessWidget { + const _Scoreboard({ + required this.homeName, + required this.awayName, + required this.score, + required this.pointsTarget, + required this.onHomePoint, + required this.onAwayPoint, + required this.onHomeMinus, + required this.onAwayMinus, + }); + + final String homeName; + final String awayName; + final ScoreState score; + final int pointsTarget; + final VoidCallback onHomePoint; + final VoidCallback onAwayPoint; + final VoidCallback onHomeMinus; + final VoidCallback onAwayMinus; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.surface, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Column( + children: [ + Text(homeName.toUpperCase(), style: theme.textTheme.labelLarge), + Text( + 'SETS ${score.homeSets}', + style: theme.textTheme.bodyMedium, + ), + Text( + '${score.homePoints}', + style: theme.textTheme.displayMedium?.copyWith( + color: AppTheme.primaryRed, + ), + ), + Text('→ $pointsTarget pt', style: theme.textTheme.bodySmall), + const SizedBox(height: 8), + ScoreTapButton(label: '+1', onTap: onHomePoint), + const SizedBox(height: 6), + OutlinedButton( + onPressed: score.homePoints > 0 ? onHomeMinus : null, + child: const Text('-1'), + ), + ], + ), + ), + Text( + '-', + style: theme.textTheme.headlineLarge?.copyWith( + color: AppTheme.textSecondary, + ), + ), + Expanded( + child: Column( + children: [ + Text(awayName.toUpperCase(), style: theme.textTheme.labelLarge), + Text( + 'SETS ${score.awaySets}', + style: theme.textTheme.bodyMedium, + ), + Text( + '${score.awayPoints}', + style: theme.textTheme.displayMedium?.copyWith( + color: AppTheme.primaryRed, + ), + ), + const SizedBox(height: 8), + ScoreTapButton(label: '+1', onTap: onAwayPoint), + const SizedBox(height: 6), + OutlinedButton( + onPressed: score.awayPoints > 0 ? onAwayMinus : null, + child: const Text('-1'), + ), + ], + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/mobile/lib/features/controller_mode/controller_screen.dart b/mobile/lib/features/controller_mode/controller_screen.dart new file mode 100644 index 0000000..ffb86bb --- /dev/null +++ b/mobile/lib/features/controller_mode/controller_screen.dart @@ -0,0 +1,424 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../app/theme.dart'; +import '../../providers/match_rules_provider.dart'; +import '../../providers/score_provider.dart'; +import '../../providers/session_provider.dart'; +import '../shared/live_score_actions.dart'; +import '../../shared/widgets/end_stream_dialog.dart'; +import '../../shared/api_client.dart'; +import '../../shared/websocket_service.dart'; +import '../../shared/widgets/connected_badge.dart'; +import '../../shared/widgets/match_live_wordmark.dart'; +import '../../shared/widgets/screen_insets.dart'; +import 'controller_landscape.dart'; +import 'controller_portrait.dart'; + +class ControllerScreen extends ConsumerStatefulWidget { + const ControllerScreen({super.key, required this.sessionId}); + + final String sessionId; + + @override + ConsumerState createState() => _ControllerScreenState(); +} + +class _ControllerScreenState extends ConsumerState { + Timer? _elapsedTimer; + Timer? _statsTimer; + + @override + void initState() { + super.initState(); + _lockLandscape(); + _bootstrap(); + _elapsedTimer = Timer.periodic(const Duration(seconds: 1), (_) { + final session = ref.read(sessionProvider).session; + if (session?.startedAt != null) { + ref.read(sessionProvider.notifier).setElapsed( + DateTime.now().difference(session!.startedAt!), + ); + } + }); + _statsTimer = Timer.periodic(const Duration(seconds: 30), (_) => _fetchStats()); + } + + Future _bootstrap() async { + try { + final client = ref.read(apiClientProvider); + final session = await client.fetchSession(widget.sessionId); + ref.read(sessionProvider.notifier).setSession(session); + if (session.score != null) { + ref.read(scoreProvider.notifier).reset(session.score!); + } + final ws = ref.read(websocketServiceProvider); + await ws.connect(sessionId: widget.sessionId, deviceRole: 'controller'); + } catch (_) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Errore connessione sessione')), + ); + } + } + } + + Future _fetchStats() async { + try { + final client = ref.read(apiClientProvider); + final stats = await client.youtubeStats(widget.sessionId); + ref.read(sessionProvider.notifier).setViewerCount(stats.concurrentViewers); + } catch (_) {} + } + + Future _lockLandscape() async { + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + DeviceOrientation.portraitUp, + ]); + } + + Future _unlockOrientation() async { + await SystemChrome.setPreferredOrientations(DeviceOrientation.values); + } + + Future _leaveSession() async { + try { + await ref.read(websocketServiceProvider).disconnect(); + } catch (_) {} + if (mounted) context.go('/matches'); + } + + Future _interruptStream() async { + try { + ref.read(websocketServiceProvider).sendCommand('pause_stream'); + await ref.read(apiClientProvider).pauseSession(widget.sessionId); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Pausa: $e')), + ); + } + } + await _leaveSession(); + } + + Future _closeStreamPermanently() async { + if (!mounted) return; + final ok = await confirmEndStream(context); + if (!ok || !mounted) return; + + try { + ref.read(websocketServiceProvider).sendCommand('stop_stream'); + } catch (_) {} + try { + await ref.read(apiClientProvider).stopSession(widget.sessionId); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Chiusura: $e')), + ); + } + } + await _leaveSession(); + } + + @override + void dispose() { + _elapsedTimer?.cancel(); + _statsTimer?.cancel(); + _unlockOrientation(); + super.dispose(); + } + + void _syncScore() { + ref.read(websocketServiceProvider).sendScoreUpdate(ref.read(scoreProvider)); + } + + Future _homePoint() async { + ref.read(scoreProvider.notifier).incrementHome(); + _syncScore(); + final match = ref.read(sessionProvider).match; + await LiveScoreActions(ref).afterPointChange( + context, + homeName: match?.teamName ?? 'Casa', + awayName: match?.opponentName ?? 'Ospiti', + onCloseSetConfirmed: () => _closeSetConfirmed(), + ); + } + + Future _awayPoint() async { + ref.read(scoreProvider.notifier).incrementAway(); + _syncScore(); + final match = ref.read(sessionProvider).match; + await LiveScoreActions(ref).afterPointChange( + context, + homeName: match?.teamName ?? 'Casa', + awayName: match?.opponentName ?? 'Ospiti', + onCloseSetConfirmed: () => _closeSetConfirmed(), + ); + } + + void _homeMinus() { + ref.read(scoreProvider.notifier).decrementHome(); + _syncScore(); + } + + void _awayMinus() { + ref.read(scoreProvider.notifier).decrementAway(); + _syncScore(); + } + + Future _closeSet() async { + await LiveScoreActions(ref).requestCloseSet( + context, + onCloseSetConfirmed: () => _closeSetConfirmed(), + ); + } + + Future _closeSetConfirmed() async { + ref.read(scoreProvider.notifier).closeSet(); + _syncScore(); + final match = ref.read(sessionProvider).match; + await LiveScoreActions(ref).afterCloseSet( + context, + homeName: match?.teamName ?? 'Casa', + awayName: match?.opponentName ?? 'Ospiti', + onStopStream: _closeStreamPermanently, + ); + } + + @override + Widget build(BuildContext context) { + final sessionState = ref.watch(sessionProvider); + final score = ref.watch(scoreProvider); + final match = sessionState.match; + final isLandscape = + MediaQuery.orientationOf(context) == Orientation.landscape; + + final homeName = match?.teamName ?? 'CASA'; + final awayName = match?.opponentName ?? 'OSPITI'; + final rules = ref.watch(matchScoringRulesProvider); + final pointsTarget = rules.pointsTarget(score.currentSet); + + final body = isLandscape + ? ControllerLandscape( + homeName: homeName, + awayName: awayName, + score: score, + sessionState: sessionState, + pointsTarget: pointsTarget, + onHomePoint: () => _homePoint(), + onAwayPoint: () => _awayPoint(), + onHomeMinus: _homeMinus, + onAwayMinus: _awayMinus, + onCloseSet: () => _closeSet(), + onInterrupt: _interruptStream, + onCloseStream: _closeStreamPermanently, + ) + : ControllerPortrait( + homeName: homeName, + awayName: awayName, + score: score, + sessionState: sessionState, + pointsTarget: pointsTarget, + onHomePoint: () => _homePoint(), + onAwayPoint: () => _awayPoint(), + onHomeMinus: _homeMinus, + onAwayMinus: _awayMinus, + onCloseSet: () => _closeSet(), + onInterrupt: _interruptStream, + onCloseStream: _closeStreamPermanently, + ); + + final inset = ScreenInsets.of(context); + + return Scaffold( + backgroundColor: AppTheme.background, + body: Column( + children: [ + if (!isLandscape) + Padding( + padding: EdgeInsets.fromLTRB( + inset.left, + inset.top, + inset.right, + 8, + ), + child: Row( + children: [ + const MatchLiveWordmark(compact: true), + const Spacer(), + ConnectedBadge(connected: sessionState.connected), + ], + ), + ), + Expanded( + child: Padding( + padding: EdgeInsets.only( + left: inset.left, + right: inset.right, + top: isLandscape ? inset.top : 0, + bottom: inset.bottom, + ), + child: body, + ), + ), + ], + ), + ); + } +} + +/// Pulsante score +1 con stile gamepad. +class ScoreTapButton extends StatelessWidget { + const ScoreTapButton({ + super.key, + required this.label, + required this.onTap, + this.large = false, + }); + + final String label; + final VoidCallback onTap; + final bool large; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Material( + color: AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(large ? 16 : 12), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(large ? 16 : 12), + child: Container( + width: large ? double.infinity : null, + padding: EdgeInsets.symmetric( + vertical: large ? 20 : 12, + horizontal: large ? 24 : 16, + ), + child: Text( + label, + textAlign: TextAlign.center, + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w900, + fontSize: large ? 28 : 18, + ), + ), + ), + ), + ); + } +} + +/// CTA gialla per azioni secondarie (PAUSA, CHIUDI SET). +class YellowActionButton extends StatelessWidget { + const YellowActionButton({ + super.key, + required this.label, + required this.onPressed, + }); + + final String label; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.accentYellow, + foregroundColor: Colors.black, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + child: Text( + label, + style: theme.textTheme.labelLarge?.copyWith(color: Colors.black), + ), + ), + ); + } +} + +/// Footer metriche stream (timer, segnale, Mbps, batteria). +class StreamMetricsFooter extends StatelessWidget { + const StreamMetricsFooter({ + super.key, + required this.elapsed, + required this.connected, + this.cameraDevice, + this.viewerCount, + this.compact = false, + }); + + final Duration elapsed; + final bool connected; + final dynamic cameraDevice; + final int? viewerCount; + final bool compact; + + String _format(Duration d) { + final m = d.inMinutes.remainder(60).toString().padLeft(2, '0'); + final s = d.inSeconds.remainder(60).toString().padLeft(2, '0'); + return '$m:$s'; + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final network = cameraDevice?.networkType ?? '—'; + final mbps = cameraDevice?.bitrateMbps != null + ? cameraDevice.bitrateMbps.toStringAsFixed(1) + : '—'; + final battery = cameraDevice?.batteryLevel?.toString() ?? '—'; + + return Container( + padding: EdgeInsets.all(compact ? 8 : 12), + decoration: BoxDecoration( + color: AppTheme.surface, + borderRadius: BorderRadius.circular(compact ? 8 : 10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _MetricChip(icon: Icons.timer, label: _format(elapsed)), + _MetricChip(icon: Icons.signal_cellular_alt, label: network), + _MetricChip(icon: Icons.speed, label: '$mbps Mbps'), + _MetricChip(icon: Icons.battery_std, label: '$battery%'), + if (viewerCount != null) + _MetricChip(icon: Icons.visibility, label: '$viewerCount'), + ConnectedBadge(connected: connected, label: compact ? null : 'COLLEGATO'), + ], + ), + ); + } +} + +class _MetricChip extends StatelessWidget { + const _MetricChip({required this.icon, required this.label}); + + final IconData icon; + final String label; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 14, color: AppTheme.textSecondary), + const SizedBox(width: 4), + Text(label, style: Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 11)), + ], + ); + } +} diff --git a/mobile/lib/features/matches/matches_screen.dart b/mobile/lib/features/matches/matches_screen.dart new file mode 100644 index 0000000..b4f2214 --- /dev/null +++ b/mobile/lib/features/matches/matches_screen.dart @@ -0,0 +1,401 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../app/theme.dart'; +import '../../providers/auth_provider.dart'; +import '../../shared/api_client.dart'; +import '../../shared/models/match.dart'; +import '../../shared/widgets/match_live_wordmark.dart'; +import '../../shared/widgets/primary_cta.dart'; +import '../../shared/widgets/screen_insets.dart'; +import 'resume_session_sheet.dart'; +import 'no_team_onboarding.dart'; +import 'team_providers.dart'; + +final matchesProvider = FutureProvider>((ref) async { + final auth = ref.watch(authProvider); + if (!auth.isAuthenticated) return []; + final teams = await ref.read(teamsProvider.future); + if (teams.isEmpty) return []; + final client = ref.read(apiClientProvider); + return client.fetchMatches(teams.first.id); +}); + +class MatchesScreen extends ConsumerWidget { + const MatchesScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final auth = ref.watch(authProvider); + final teamsAsync = ref.watch(teamsProvider); + final matchesAsync = ref.watch(matchesProvider); + final theme = Theme.of(context); + final dateFormat = DateFormat('EEE d MMM · HH:mm', 'it_IT'); + final bottomInset = ScreenInsets.of(context).bottom; + + return Scaffold( + appBar: AppBar( + title: const MatchLiveWordmark(compact: true), + actions: [ + IconButton( + icon: const Icon(Icons.group_outlined), + tooltip: 'Gestisci staff', + onPressed: () async { + final team = await ref.read(primaryTeamProvider.future); + final url = team?.staffManageUrl ?? team?.billingUrl; + if (url != null && await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); + } + }, + ), + IconButton( + icon: const Icon(Icons.video_library_outlined), + tooltip: 'Archivio', + onPressed: () => context.push('/archive'), + ), + IconButton( + icon: const Icon(Icons.logout), + tooltip: 'Esci', + onPressed: () { + ref.read(authProvider.notifier).logout(); + context.go('/login'); + }, + ), + ], + ), + body: teamsAsync.when( + loading: () => const Center( + child: CircularProgressIndicator(color: AppTheme.primaryRed), + ), + error: (e, _) => Center(child: Text('Errore squadre: $e')), + data: (teams) { + if (teams.isEmpty) { + return NoTeamOnboarding(theme: theme); + } + return matchesAsync.when( + loading: () => const Center( + child: CircularProgressIndicator(color: AppTheme.primaryRed), + ), + error: (e, _) => Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Errore nel caricamento', style: theme.textTheme.titleMedium), + const SizedBox(height: 8), + Text( + e.toString(), + style: theme.textTheme.bodySmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + TextButton( + onPressed: () { + ref.invalidate(teamsProvider); + ref.invalidate(matchesProvider); + }, + child: const Text('RIPROVA'), + ), + ], + ), + ), + ), + data: (matches) { + MatchModel? activeMatch; + for (final m in matches) { + if (m.hasActiveSession) { + activeMatch = m; + break; + } + } + + return RefreshIndicator( + color: AppTheme.primaryRed, + onRefresh: () async { + ref.invalidate(matchesProvider); + await ref.read(matchesProvider.future); + }, + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 8, 20, 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Ciao, ${auth.user?.name ?? ''}', + style: theme.textTheme.headlineSmall, + ), + const SizedBox(height: 4), + Text( + 'Le tue partite', + style: theme.textTheme.bodyMedium, + ), + if (activeMatch != null) ...[ + const SizedBox(height: 12), + Material( + color: AppTheme.primaryRed.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(12), + child: InkWell( + onTap: () => showResumeSessionSheet( + context, + ref, + match: activeMatch!, + ), + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + children: [ + const Icon( + Icons.videocam, + color: AppTheme.primaryRed, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Riprendi diretta in corso', + style: theme.textTheme.titleSmall + ?.copyWith( + color: AppTheme.primaryRed, + fontWeight: FontWeight.w700, + ), + ), + Text( + '${activeMatch.teamName} vs ${activeMatch.opponentName}', + style: theme.textTheme.bodySmall, + ), + ], + ), + ), + const Icon( + Icons.chevron_right, + color: AppTheme.primaryRed, + ), + ], + ), + ), + ), + ), + ], + ], + ), + ), + ), + if (matches.isEmpty) + SliverFillRemaining( + hasScrollBody: false, + child: Center( + child: Text( + 'Nessuna partita programmata', + style: theme.textTheme.bodyMedium, + ), + ), + ) + else + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final match = matches[index]; + return _MatchCard( + match: match, + dateFormat: dateFormat, + onTap: () { + if (match.hasActiveSession) { + showResumeSessionSheet(context, ref, match: match); + } else { + context.push('/setup/${match.id}/1'); + } + }, + onDelete: match.canResumeCamera + ? null + : () => _deleteMatch(context, ref, match), + ); + }, + childCount: matches.length, + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.fromLTRB(20, 20, 20, 20 + bottomInset), + child: PrimaryCta( + label: 'NUOVA PARTITA', + icon: Icons.add, + onPressed: () => _createMatch(context, ref), + ), + ), + ), + ], + ), + ); + }, + ); + }, + ), + ); + } + + Future _deleteMatch( + BuildContext context, + WidgetRef ref, + MatchModel match, + ) async { + final confirmed = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: AppTheme.surface, + title: const Text('Elimina partita'), + content: Text( + 'Eliminare «${match.teamName} vs ${match.opponentName}»?\n\n' + 'L\'operazione non si può annullare.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Annulla'), + ), + FilledButton( + onPressed: () => Navigator.pop(ctx, true), + style: FilledButton.styleFrom(backgroundColor: AppTheme.primaryRed), + child: const Text('Elimina'), + ), + ], + ), + ); + if (confirmed != true || !context.mounted) return; + + try { + await ref.read(apiClientProvider).deleteMatch(match.id); + ref.invalidate(matchesProvider); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Partita eliminata')), + ); + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Impossibile eliminare: $e')), + ); + } + } + } + + Future _createMatch(BuildContext context, WidgetRef ref) async { + final teams = await ref.read(teamsProvider.future); + if (teams.isEmpty) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Nessuna squadra disponibile')), + ); + } + return; + } + + final client = ref.read(apiClientProvider); + final match = await client.createMatch(teams.first.id, { + 'opponent_name': 'Avversario', + 'sport': 'volleyball', + 'sets_to_win': 3, + }); + if (context.mounted) context.push('/setup/${match.id}/1'); + } +} + +class _MatchCard extends StatelessWidget { + const _MatchCard({ + required this.match, + required this.dateFormat, + required this.onTap, + this.onDelete, + }); + + final MatchModel match; + final DateFormat dateFormat; + final VoidCallback onTap; + final VoidCallback? onDelete; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final hasSession = match.hasActiveSession; + final canResume = match.canResumeCamera; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6), + child: Material( + color: AppTheme.surface, + borderRadius: BorderRadius.circular(12), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${match.teamName} vs ${match.opponentName}', + style: theme.textTheme.titleMedium, + ), + const SizedBox(height: 4), + if (match.scheduledAt != null) + Text( + dateFormat.format(match.scheduledAt!.toLocal()), + style: theme.textTheme.bodyMedium, + ), + if (match.location != null && match.location!.isNotEmpty) + Text( + match.location!, + style: theme.textTheme.bodyMedium, + ), + ], + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: hasSession + ? AppTheme.primaryRed.withValues(alpha: 0.2) + : AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + canResume ? 'RIPRENDI' : (hasSession ? 'IN CORSO' : 'CONFIGURA'), + style: theme.textTheme.labelLarge?.copyWith( + color: hasSession ? AppTheme.primaryRed : AppTheme.textSecondary, + fontSize: 11, + ), + ), + ), + if (onDelete != null) ...[ + const SizedBox(width: 4), + IconButton( + icon: const Icon(Icons.delete_outline, color: AppTheme.textSecondary), + tooltip: 'Elimina partita', + onPressed: onDelete, + visualDensity: VisualDensity.compact, + constraints: const BoxConstraints(minWidth: 36, minHeight: 36), + ), + ], + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/matches/no_team_onboarding.dart b/mobile/lib/features/matches/no_team_onboarding.dart new file mode 100644 index 0000000..ad91afd --- /dev/null +++ b/mobile/lib/features/matches/no_team_onboarding.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../app/theme.dart'; +import '../../core/config.dart'; +import '../../shared/widgets/primary_cta.dart'; + +class NoTeamOnboarding extends StatelessWidget { + const NoTeamOnboarding({required this.theme}); + + final ThemeData theme; + + String get _signupUrl { + final base = AppConfig.apiBaseUrl.replaceAll(RegExp(r'/api/v1/?$'), ''); + return '$base/signup'; + } + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.all(28), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.groups_outlined, size: 64, color: AppTheme.textSecondary), + const SizedBox(height: 16), + Text( + 'Completa l\'iscrizione sul sito', + style: theme.textTheme.titleLarge, + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + Text( + 'Crea la tua squadra e scegli il piano Free o Premium da browser. ' + 'Poi torna qui con le stesse credenziali.', + style: theme.textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + PrimaryCta( + label: 'VAI AL SITO', + icon: Icons.open_in_new, + onPressed: () async { + final uri = Uri.parse(_signupUrl); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + }, + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/features/matches/resume_session_sheet.dart b/mobile/lib/features/matches/resume_session_sheet.dart new file mode 100644 index 0000000..62469eb --- /dev/null +++ b/mobile/lib/features/matches/resume_session_sheet.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../app/theme.dart'; +import '../../providers/session_provider.dart'; +import '../../shared/models/match.dart'; + +/// Azioni per riprendere una diretta interrotta (crash, chiusura app, ecc.). +void showResumeSessionSheet( + BuildContext context, + WidgetRef ref, { + required MatchModel match, +}) { + final sessionId = match.activeSessionId; + if (sessionId == null) return; + + showModalBottomSheet( + context: context, + backgroundColor: AppTheme.surface, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + builder: (ctx) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 16, 20, 12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Diretta in corso', + style: Theme.of(ctx).textTheme.titleLarge, + ), + const SizedBox(height: 4), + Text( + '${match.teamName} vs ${match.opponentName}', + style: Theme.of(ctx).textTheme.bodyMedium, + ), + if (match.activeSessionStatus != null) ...[ + const SizedBox(height: 8), + Text( + 'Stato: ${match.activeSessionStatus}', + style: Theme.of(ctx).textTheme.labelLarge?.copyWith( + color: AppTheme.primaryRed, + ), + ), + ], + const SizedBox(height: 20), + if (match.canResumeCamera) + FilledButton.icon( + onPressed: () { + ref.read(sessionProvider.notifier).setDeviceRole(DeviceRole.camera); + Navigator.pop(ctx); + context.push('/session/$sessionId/camera'); + }, + icon: const Icon(Icons.videocam), + label: const Text('Riprendi camera e trasmetti'), + style: FilledButton.styleFrom( + backgroundColor: AppTheme.primaryRed, + padding: const EdgeInsets.symmetric(vertical: 14), + ), + ), + if (match.activeSessionStatus == 'idle') ...[ + const SizedBox(height: 8), + OutlinedButton.icon( + onPressed: () { + Navigator.pop(ctx); + context.push('/setup/${match.id}/1'); + }, + icon: const Icon(Icons.settings), + label: const Text('Continua configurazione'), + ), + ], + const SizedBox(height: 8), + OutlinedButton.icon( + onPressed: () { + ref.read(sessionProvider.notifier).setDeviceRole(DeviceRole.controller); + Navigator.pop(ctx); + context.push('/session/$sessionId/controller'); + }, + icon: const Icon(Icons.sports_esports), + label: const Text('Apri regia (punteggio)'), + ), + const SizedBox(height: 8), + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text('Annulla'), + ), + ], + ), + ), + ); + }, + ); +} diff --git a/mobile/lib/features/matches/team_providers.dart b/mobile/lib/features/matches/team_providers.dart new file mode 100644 index 0000000..64ee8dc --- /dev/null +++ b/mobile/lib/features/matches/team_providers.dart @@ -0,0 +1,34 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../providers/auth_provider.dart'; +import '../../shared/api_client.dart'; +import '../../shared/models/recording_item.dart'; +import '../../shared/models/team.dart'; + +final teamsProvider = FutureProvider>((ref) async { + final auth = ref.watch(authProvider); + if (!auth.isAuthenticated) return []; + final client = ref.read(apiClientProvider); + return client.fetchTeams(); +}); + +final primaryTeamProvider = FutureProvider((ref) async { + final teams = await ref.watch(teamsProvider.future); + if (teams.isEmpty) return null; + return teams.first; +}); + +final teamByIdProvider = FutureProvider.family((ref, teamId) async { + final teams = await ref.watch(teamsProvider.future); + for (final t in teams) { + if (t.id == teamId) return t; + } + return null; +}); + +final recordingsProvider = FutureProvider.family, String>((ref, teamId) async { + final team = await ref.watch(teamByIdProvider(teamId).future); + if (team == null || !team.recordingsEnabled) return []; + final client = ref.read(apiClientProvider); + return client.fetchRecordings(teamId); +}); diff --git a/mobile/lib/features/setup_wizard/step_match.dart b/mobile/lib/features/setup_wizard/step_match.dart new file mode 100644 index 0000000..ef8e65b --- /dev/null +++ b/mobile/lib/features/setup_wizard/step_match.dart @@ -0,0 +1,284 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; + +import '../../app/theme.dart'; +import '../../shared/api_client.dart'; +import '../../shared/models/match.dart'; +import '../../shared/widgets/primary_cta.dart'; +import '../../shared/widgets/screen_insets.dart'; + +class StepMatch extends ConsumerStatefulWidget { + const StepMatch({ + super.key, + required this.match, + required this.onNext, + }); + + final MatchModel match; + final VoidCallback onNext; + + @override + ConsumerState createState() => _StepMatchState(); +} + +class _StepMatchState extends ConsumerState { + late final TextEditingController _opponentController; + late final TextEditingController _locationController; + late final TextEditingController _categoryController; + late final TextEditingController _phaseController; + late final TextEditingController _rosterController; + int _setsToWin = 3; + DateTime? _scheduledAt; + bool _saving = false; + bool _customRules = false; + int _pointsPerSet = 25; + int _pointsDecidingSet = 15; + int _minPointLead = 2; + + @override + void initState() { + super.initState(); + _opponentController = TextEditingController(text: widget.match.opponentName); + _locationController = TextEditingController(text: widget.match.location ?? ''); + _categoryController = TextEditingController(text: widget.match.category ?? ''); + _phaseController = TextEditingController(text: widget.match.phase ?? ''); + _rosterController = TextEditingController( + text: widget.match.rosterNumbers.join(', '), + ); + _setsToWin = widget.match.setsToWin; + _scheduledAt = widget.match.scheduledAt; + final rules = widget.match.scoringRules; + if (rules != null && rules.isNotEmpty) { + _customRules = true; + _pointsPerSet = rules['points_per_set'] as int? ?? 25; + _pointsDecidingSet = rules['points_deciding_set'] as int? ?? 15; + _minPointLead = rules['min_point_lead'] as int? ?? 2; + } + } + + @override + void dispose() { + _opponentController.dispose(); + _locationController.dispose(); + _categoryController.dispose(); + _phaseController.dispose(); + _rosterController.dispose(); + super.dispose(); + } + + List _parseRoster(String raw) { + return raw + .split(RegExp(r'[,\s]+')) + .where((s) => s.isNotEmpty) + .map(int.tryParse) + .whereType() + .toList(); + } + + Future _pickDateTime() async { + final date = await showDatePicker( + context: context, + initialDate: _scheduledAt ?? DateTime.now(), + firstDate: DateTime.now().subtract(const Duration(days: 1)), + lastDate: DateTime.now().add(const Duration(days: 365)), + ); + if (date == null || !mounted) return; + + final time = await showTimePicker( + context: context, + initialTime: TimeOfDay.fromDateTime(_scheduledAt ?? DateTime.now()), + ); + if (time == null) return; + + setState(() { + _scheduledAt = DateTime(date.year, date.month, date.day, time.hour, time.minute); + }); + } + + Future _save() async { + if (_opponentController.text.trim().isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Inserisci il nome avversario')), + ); + return; + } + + setState(() => _saving = true); + try { + final client = ref.read(apiClientProvider); + await client.updateMatch(widget.match.id, { + 'opponent_name': _opponentController.text.trim(), + 'location': _locationController.text.trim(), + 'scheduled_at': _scheduledAt?.toIso8601String(), + 'sets_to_win': _setsToWin, + if (_customRules) + 'scoring_rules': { + 'points_per_set': _pointsPerSet, + 'points_deciding_set': _pointsDecidingSet, + 'min_point_lead': _minPointLead, + }, + 'category': _categoryController.text.trim(), + 'phase': _phaseController.text.trim(), + 'roster_numbers': _parseRoster(_rosterController.text), + }); + widget.onNext(); + } catch (_) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Errore nel salvataggio')), + ); + } + } finally { + if (mounted) setState(() => _saving = false); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final dateLabel = _scheduledAt != null + ? DateFormat('EEE d MMM yyyy · HH:mm', 'it_IT').format(_scheduledAt!.toLocal()) + : 'Seleziona data e ora'; + + return SingleChildScrollView( + padding: ScreenInsets.contentPadding(context, horizontal: 20, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Dettagli partita', style: theme.textTheme.titleLarge), + const SizedBox(height: 20), + _ReadOnlyField(label: 'Squadra casa', value: widget.match.teamName), + const SizedBox(height: 12), + TextField( + controller: _opponentController, + decoration: const InputDecoration(labelText: 'Avversario'), + ), + const SizedBox(height: 12), + TextField( + controller: _locationController, + decoration: const InputDecoration(labelText: 'Luogo'), + ), + const SizedBox(height: 12), + ListTile( + contentPadding: EdgeInsets.zero, + title: Text('Orario', style: theme.textTheme.bodyMedium), + subtitle: Text(dateLabel, style: theme.textTheme.titleMedium), + trailing: const Icon(Icons.calendar_today), + onTap: _pickDateTime, + ), + const SizedBox(height: 12), + Text('Set da vincere', style: theme.textTheme.bodyMedium), + const SizedBox(height: 8), + SegmentedButton( + segments: const [ + ButtonSegment(value: 2, label: Text('2')), + ButtonSegment(value: 3, label: Text('3')), + ], + selected: {_setsToWin}, + onSelectionChanged: (s) => setState(() => _setsToWin = s.first), + ), + const SizedBox(height: 12), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Regole punteggio personalizzate'), + subtitle: const Text( + 'Per tornei non standard (punti set, tie-break, vantaggio minimo)', + ), + value: _customRules, + onChanged: (v) => setState(() => _customRules = v), + ), + if (_customRules) ...[ + const SizedBox(height: 8), + Text('Punti per vincere un set', style: theme.textTheme.bodyMedium), + const SizedBox(height: 6), + SegmentedButton( + segments: const [ + ButtonSegment(value: 21, label: Text('21')), + ButtonSegment(value: 25, label: Text('25')), + ButtonSegment(value: 30, label: Text('30')), + ], + selected: {_pointsPerSet}, + onSelectionChanged: (s) => setState(() => _pointsPerSet = s.first), + ), + const SizedBox(height: 10), + Text('Punti set decisivo', style: theme.textTheme.bodyMedium), + const SizedBox(height: 6), + SegmentedButton( + segments: const [ + ButtonSegment(value: 15, label: Text('15')), + ButtonSegment(value: 21, label: Text('21')), + ], + selected: {_pointsDecidingSet}, + onSelectionChanged: (s) => setState(() => _pointsDecidingSet = s.first), + ), + const SizedBox(height: 10), + Text('Vantaggio minimo', style: theme.textTheme.bodyMedium), + const SizedBox(height: 6), + SegmentedButton( + segments: const [ + ButtonSegment(value: 1, label: Text('1')), + ButtonSegment(value: 2, label: Text('2')), + ], + selected: {_minPointLead}, + onSelectionChanged: (s) => setState(() => _minPointLead = s.first), + ), + ], + const SizedBox(height: 12), + TextField( + controller: _categoryController, + decoration: const InputDecoration(labelText: 'Categoria'), + ), + const SizedBox(height: 12), + TextField( + controller: _phaseController, + decoration: const InputDecoration(labelText: 'Fase (es. semifinale)'), + ), + const SizedBox(height: 12), + TextField( + controller: _rosterController, + decoration: const InputDecoration( + labelText: 'Convocate (numeri maglia)', + hintText: '1, 5, 7, 12', + ), + keyboardType: TextInputType.number, + ), + const SizedBox(height: 32), + PrimaryCta( + label: 'AVANTI >', + loading: _saving, + onPressed: _save, + icon: Icons.arrow_forward, + ), + ], + ), + ); + } +} + +class _ReadOnlyField extends StatelessWidget { + const _ReadOnlyField({required this.label, required this.value}); + + final String label; + final String value; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: theme.textTheme.bodyMedium), + const SizedBox(height: 4), + Text(value, style: theme.textTheme.titleMedium), + ], + ), + ); + } +} diff --git a/mobile/lib/features/setup_wizard/step_network_test.dart b/mobile/lib/features/setup_wizard/step_network_test.dart new file mode 100644 index 0000000..736ccda --- /dev/null +++ b/mobile/lib/features/setup_wizard/step_network_test.dart @@ -0,0 +1,295 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../app/theme.dart'; +import '../../providers/score_provider.dart'; +import '../../providers/session_provider.dart'; +import '../../shared/api_client.dart'; +import '../../shared/models/match.dart'; +import '../../shared/models/score_state.dart'; +import '../../shared/models/stream_session.dart'; +import '../../shared/websocket_service.dart'; +import '../../shared/widgets/metric_card.dart'; +import '../../shared/widgets/primary_cta.dart'; +import '../../shared/widgets/screen_insets.dart'; + +class StepNetworkTest extends ConsumerStatefulWidget { + const StepNetworkTest({ + super.key, + required this.match, + required this.onBack, + }); + + final MatchModel match; + final VoidCallback onBack; + + @override + ConsumerState createState() => _StepNetworkTestState(); +} + +class _StepNetworkTestState extends ConsumerState { + bool _testing = false; + bool _ready = false; + double _downloadMbps = 0; + double _uploadMbps = 0; + int _latencyMs = 0; + String _networkType = '—'; + bool _starting = false; + + Future _runTest() async { + setState(() { + _testing = true; + _ready = false; + }); + + final connectivity = await Connectivity().checkConnectivity(); + final networkLabel = _connectivityLabel(connectivity); + + // Simulazione test rete locale (sostituibile con speed test reale). + await Future.delayed(const Duration(seconds: 2)); + final rng = Random(); + final download = 8 + rng.nextDouble() * 12; + final upload = 2 + rng.nextDouble() * 4; + final latency = 20 + rng.nextInt(80); + + final session = ref.read(sessionProvider).session; + NetworkTestResult? testResult; + if (session != null) { + try { + final client = ref.read(apiClientProvider); + testResult = await client.networkTest( + sessionId: session.id, + downloadMbps: download, + uploadMbps: upload, + latencyMs: latency, + networkType: networkLabel, + ); + } catch (_) { + testResult = null; + } + } + + if (mounted) { + setState(() { + _testing = false; + _downloadMbps = download; + _uploadMbps = upload; + _latencyMs = latency; + _networkType = networkLabel; + _ready = testResult?.ready ?? upload >= 2.0; + }); + } + } + + String _connectivityLabel(List results) { + if (results.contains(ConnectivityResult.wifi)) return 'WiFi'; + if (results.contains(ConnectivityResult.mobile)) return '4G'; + if (results.contains(ConnectivityResult.ethernet)) return 'Ethernet'; + return 'Sconosciuto'; + } + + Future _startLive() async { + final sessionState = ref.read(sessionProvider); + final session = sessionState.session; + if (session == null) return; + + setState(() => _starting = true); + try { + final client = ref.read(apiClientProvider); + final started = await client.startSession(session.id); + ref.read(sessionProvider.notifier).setSession(started, match: widget.match); + + if (started.score != null) { + ref.read(scoreProvider.notifier).reset(started.score!); + } else { + ref.read(scoreProvider.notifier).reset(const ScoreState()); + } + + final role = sessionState.deviceRole; + final path = role == DeviceRole.camera + ? '/session/${started.id}/camera' + : '/session/${started.id}/controller'; + + if (!mounted) return; + context.go(path); + + // WebSocket in background: non bloccare apertura camera/regia + try { + final ws = ref.read(websocketServiceProvider); + await ws.connect( + sessionId: started.id, + deviceRole: role == DeviceRole.camera ? 'camera' : 'controller', + ); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Diretta avviata ma sync regia limitata: $e', + ), + ), + ); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Errore avvio diretta: $e')), + ); + } + } finally { + if (mounted) setState(() => _starting = false); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return SingleChildScrollView( + padding: ScreenInsets.contentPadding(context, horizontal: 20, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Test rete', style: theme.textTheme.titleLarge), + const SizedBox(height: 8), + Text( + 'Verifica che la connessione regga l\'upload della diretta.', + style: theme.textTheme.bodyMedium, + ), + const SizedBox(height: 24), + Row( + children: [ + MetricCard( + label: 'Download', + value: _testing ? '...' : '${_downloadMbps.toStringAsFixed(1)} Mbps', + icon: Icons.download, + ), + const SizedBox(width: 8), + MetricCard( + label: 'Upload', + value: _testing ? '...' : '${_uploadMbps.toStringAsFixed(1)} Mbps', + icon: Icons.upload, + highlight: _ready, + ), + const SizedBox(width: 8), + MetricCard( + label: 'Latenza', + value: _testing ? '...' : '${_latencyMs} ms', + icon: Icons.speed, + ), + ], + ), + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: MetricCard( + label: 'Tipo rete', + value: _networkType, + icon: Icons.signal_cellular_alt, + expand: false, + ), + ), + const SizedBox(height: 20), + if (_ready) ...[ + Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppTheme.successGreen.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppTheme.successGreen.withValues(alpha: 0.4)), + ), + child: Text( + 'PRONTO PER ANDARE IN DIRETTA', + style: theme.textTheme.titleMedium?.copyWith( + color: AppTheme.successGreen, + ), + textAlign: TextAlign.center, + ), + ), + if (ref.read(sessionProvider).session?.watchPageUrl != null) ...[ + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Link per gli spettatori', + style: theme.textTheme.titleSmall, + ), + const SizedBox(height: 8), + SelectableText( + ref.read(sessionProvider).session!.watchPageUrl!, + style: theme.textTheme.bodySmall, + ), + const SizedBox(height: 8), + OutlinedButton.icon( + onPressed: () { + final url = ref.read(sessionProvider).session!.watchPageUrl!; + Clipboard.setData(ClipboardData(text: url)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Link copiato')), + ); + }, + icon: const Icon(Icons.link, size: 18), + label: const Text('COPIA LINK DIRETTA'), + ), + ], + ), + ), + ], + ], + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + 'In caso di disconnessione, lo stream resta attivo con slate per massimo 5 minuti mentre il telefono riconnette.', + style: theme.textTheme.bodyMedium, + ), + ), + const SizedBox(height: 24), + OutlinedButton( + onPressed: _testing ? null : _runTest, + child: Text(_testing ? 'TEST IN CORSO...' : 'AVVIA TEST RETE'), + ), + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: _starting ? null : widget.onBack, + child: const Text('Indietro'), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 2, + child: PrimaryCta( + label: 'INIZIA >', + loading: _starting, + onPressed: _ready ? _startLive : null, + icon: Icons.play_arrow, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/mobile/lib/features/setup_wizard/step_roles.dart b/mobile/lib/features/setup_wizard/step_roles.dart new file mode 100644 index 0000000..88c9ec2 --- /dev/null +++ b/mobile/lib/features/setup_wizard/step_roles.dart @@ -0,0 +1,238 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:qr_flutter/qr_flutter.dart'; + +import '../../app/theme.dart'; +import '../../providers/session_provider.dart'; +import '../../shared/api_client.dart'; +import '../../shared/models/match.dart'; +import '../../shared/widgets/primary_cta.dart'; +import '../../shared/widgets/screen_insets.dart'; + +class StepRoles extends ConsumerStatefulWidget { + const StepRoles({ + super.key, + required this.match, + required this.onBack, + required this.onNext, + }); + + final MatchModel match; + final VoidCallback onBack; + final VoidCallback onNext; + + @override + ConsumerState createState() => _StepRolesState(); +} + +class _StepRolesState extends ConsumerState { + DeviceRole _role = DeviceRole.controller; + String? _qrData; + bool _loadingQr = false; + + Future _generateQr() async { + final session = ref.read(sessionProvider).session; + if (session == null) return; + + setState(() => _loadingQr = true); + try { + final client = ref.read(apiClientProvider); + final response = await client.createPairingToken(session.id); + setState(() { + _qrData = jsonEncode(response.qrPayload); + }); + } catch (_) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Errore generazione QR')), + ); + } + } finally { + if (mounted) setState(() => _loadingQr = false); + } + } + + void _confirm() { + ref.read(sessionProvider.notifier).setDeviceRole(_role); + widget.onNext(); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return SingleChildScrollView( + padding: ScreenInsets.contentPadding(context, horizontal: 20, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Scegli il tuo ruolo', style: theme.textTheme.titleLarge), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: _RoleCard( + title: 'CAMERA', + subtitle: 'Sul cavalletto', + icon: Icons.videocam, + selected: _role == DeviceRole.camera, + onTap: () => setState(() => _role = DeviceRole.camera), + ), + ), + const SizedBox(width: 12), + Expanded( + child: _RoleCard( + title: 'REGIA', + subtitle: 'In mano', + icon: Icons.sports_esports, + selected: _role == DeviceRole.controller, + onTap: () => setState(() => _role = DeviceRole.controller), + ), + ), + ], + ), + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppTheme.accentYellow.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppTheme.accentYellow.withValues(alpha: 0.4)), + ), + child: Row( + children: [ + const Icon(Icons.battery_charging_full, color: AppTheme.accentYellow), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Mantieni in carica. Saranno 90 minuti a pieno carico.', + style: theme.textTheme.bodyMedium?.copyWith(color: Colors.white), + ), + ), + ], + ), + ), + const SizedBox(height: 24), + Text('Collega il secondo telefono', style: theme.textTheme.titleMedium), + const SizedBox(height: 12), + Center( + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: _qrData != null + ? QrImageView( + data: _qrData!, + version: QrVersions.auto, + size: 180, + backgroundColor: Colors.white, + ) + : SizedBox( + width: 180, + height: 180, + child: _loadingQr + ? const Center( + child: CircularProgressIndicator( + color: AppTheme.primaryRed, + ), + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.qr_code, size: 48, color: Colors.black54), + const SizedBox(height: 8), + TextButton( + onPressed: _generateQr, + child: const Text( + 'GENERA QR', + style: TextStyle(color: Colors.black), + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 8), + Text( + 'Scansiona con l\'altro dispositivo per collegare camera e regia.', + style: theme.textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: widget.onBack, + child: const Text('Indietro'), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 2, + child: PrimaryCta( + label: 'AVANTI >', + onPressed: _confirm, + icon: Icons.arrow_forward, + ), + ), + ], + ), + ], + ), + ); + } +} + +class _RoleCard extends StatelessWidget { + const _RoleCard({ + required this.title, + required this.subtitle, + required this.icon, + required this.selected, + required this.onTap, + }); + + final String title; + final String subtitle; + final IconData icon; + final bool selected; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Material( + color: selected + ? AppTheme.primaryRed.withValues(alpha: 0.15) + : AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(12), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: selected ? AppTheme.primaryRed : Colors.transparent, + ), + ), + child: Column( + children: [ + Icon(icon, size: 32, color: selected ? AppTheme.primaryRed : Colors.white), + const SizedBox(height: 8), + Text(title, style: theme.textTheme.titleMedium), + Text(subtitle, style: theme.textTheme.bodyMedium, textAlign: TextAlign.center), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/setup_wizard/step_transmission.dart b/mobile/lib/features/setup_wizard/step_transmission.dart new file mode 100644 index 0000000..4f12e96 --- /dev/null +++ b/mobile/lib/features/setup_wizard/step_transmission.dart @@ -0,0 +1,331 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../app/theme.dart'; +import '../../providers/session_provider.dart'; +import '../../shared/api_client.dart'; +import '../../shared/api_error.dart'; +import '../../shared/models/match.dart'; +import '../../shared/premium_dialog.dart'; +import '../../shared/widgets/primary_cta.dart'; +import '../../shared/widgets/screen_insets.dart'; +import '../../shared/models/team.dart'; +import '../matches/team_providers.dart'; + +class StepTransmission extends ConsumerStatefulWidget { + const StepTransmission({ + super.key, + required this.match, + required this.onBack, + required this.onNext, + }); + + final MatchModel match; + final VoidCallback onBack; + final VoidCallback onNext; + + @override + ConsumerState createState() => _StepTransmissionState(); +} + +class _StepTransmissionState extends ConsumerState { + String _platform = 'matchlivetv'; + String _privacy = 'unlisted'; + bool _creating = false; + + Future _createSession() async { + setState(() => _creating = true); + try { + final client = ref.read(apiClientProvider); + final session = await client.createSession( + widget.match.id, + platform: _platform, + privacyStatus: _privacy, + qualityPreset: '720p_30_2.5mbps', + targetBitrate: 2500000, + targetFps: 30, + ); + ref.read(sessionProvider.notifier).setSession(session, match: widget.match); + widget.onNext(); + } on DioException catch (e) { + final apiErr = ApiException.fromDio(e); + if (mounted) { + if (apiErr?.isPremiumRequired == true) { + await showPremiumRequiredDialog( + context, + message: apiErr!.message, + billingUrl: apiErr.billingUrl, + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(apiErr?.message ?? 'Errore nella creazione sessione')), + ); + } + } + } catch (_) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Errore nella creazione sessione')), + ); + } + } finally { + if (mounted) setState(() => _creating = false); + } + } + + void _onYoutubeTap(Team? team) { + if (team == null) return; + if (team.canUseYoutube && team.youtubeConnected) { + setState(() => _platform = 'youtube'); + return; + } + if (team.canUseYoutube && !team.youtubeConnected) { + showPremiumRequiredDialog( + context, + message: 'Collega il canale YouTube dalla dashboard sul sito, poi seleziona YouTube qui.', + billingUrl: team.billingUrl, + ); + return; + } + showPremiumRequiredDialog( + context, + message: 'YouTube Live è incluso nel piano Premium Full.', + billingUrl: team.billingUrl, + ); + } + + void _onExternalPremiumTap(String platform, Team? team) { + showPremiumRequiredDialog( + context, + message: '$platform è disponibile con Premium (in arrivo).', + billingUrl: team?.billingUrl, + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final teamAsync = ref.watch(teamByIdProvider(widget.match.teamId)); + + return teamAsync.when( + loading: () => const Center(child: CircularProgressIndicator(color: AppTheme.primaryRed)), + error: (_, __) => const Center(child: Text('Errore caricamento squadra')), + data: (team) { + final youtubeSelectable = team != null && team.canUseYoutube && team.youtubeConnected; + + return SingleChildScrollView( + padding: ScreenInsets.contentPadding(context, horizontal: 20, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (team != null) ...[ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppTheme.primaryRed.withValues(alpha: 0.3)), + ), + child: Text( + 'Piano ${team.planName} · Staff ${team.staffLimitLabel}' + '${team.concurrentStreamsLimit != null ? ' · Dirette ${team.concurrentStreamsUsed}/${team.concurrentStreamsLimit}' : ''}', + style: theme.textTheme.bodySmall, + ), + ), + const SizedBox(height: 12), + ], + Text('Piattaforma', style: theme.textTheme.titleLarge), + const SizedBox(height: 12), + _PlatformCard( + title: 'Match Live TV', + subtitle: 'Diretta sul nostro sito (incluso)', + selected: _platform == 'matchlivetv', + enabled: true, + onTap: () => setState(() => _platform = 'matchlivetv'), + ), + const SizedBox(height: 8), + _PlatformCard( + title: 'YouTube Live', + subtitle: team?.canUseYoutube == true + ? (team!.youtubeConnected + ? 'Canale collegato' + : 'Collega canale sul sito') + : 'Premium Full — collegamento canale', + selected: _platform == 'youtube', + enabled: youtubeSelectable, + badge: team?.canUseYoutube == true ? null : 'Full', + onTap: () => _onYoutubeTap(team), + ), + const SizedBox(height: 8), + _PlatformCard( + title: 'Facebook Live', + subtitle: 'Premium — presto disponibile', + selected: false, + enabled: false, + badge: 'Premium', + onTap: () => _onExternalPremiumTap('Facebook Live', team), + ), + const SizedBox(height: 8), + _PlatformCard( + title: 'Twitch', + subtitle: 'Premium — presto disponibile', + selected: false, + enabled: false, + badge: 'Premium', + onTap: () => _onExternalPremiumTap('Twitch', team), + ), + const SizedBox(height: 24), + Text('Privacy', style: theme.textTheme.titleLarge), + const SizedBox(height: 12), + SegmentedButton( + segments: const [ + ButtonSegment(value: 'public', label: Text('Pubblico')), + ButtonSegment(value: 'unlisted', label: Text('Non in elenco')), + ], + selected: {_privacy}, + onSelectionChanged: (s) => setState(() => _privacy = s.first), + ), + const SizedBox(height: 24), + Text('Qualità', style: theme.textTheme.titleLarge), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppTheme.primaryRed.withValues(alpha: 0.4)), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('720p · 30fps · 2.5 Mbps', style: theme.textTheme.titleMedium), + const SizedBox(height: 4), + Text( + 'Bitrate fisso consigliato per reti instabili', + style: theme.textTheme.bodyMedium, + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.primaryRed.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + 'AUTO', + style: theme.textTheme.labelLarge?.copyWith( + color: AppTheme.primaryRed, + fontSize: 11, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: _creating ? null : widget.onBack, + child: const Text('Indietro'), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 2, + child: PrimaryCta( + label: 'AVANTI >', + loading: _creating, + onPressed: _createSession, + icon: Icons.arrow_forward, + ), + ), + ], + ), + ], + ), + ); + }, + ); + } +} + +class _PlatformCard extends StatelessWidget { + const _PlatformCard({ + required this.title, + this.subtitle, + required this.selected, + required this.enabled, + this.badge, + this.onTap, + }); + + final String title; + final String? subtitle; + final bool selected; + final bool enabled; + final String? badge; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Opacity( + opacity: enabled ? 1 : 0.55, + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(10), + child: Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: selected + ? AppTheme.primaryRed.withValues(alpha: 0.15) + : AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: selected ? AppTheme.primaryRed : Colors.transparent, + ), + ), + child: Row( + children: [ + Icon( + selected ? Icons.radio_button_checked : Icons.radio_button_off, + color: selected ? AppTheme.primaryRed : AppTheme.textSecondary, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: theme.textTheme.titleMedium), + if (subtitle != null) + Text(subtitle!, style: theme.textTheme.bodyMedium), + ], + ), + ), + if (badge != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.textSecondary.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(6), + ), + child: Text(badge!, style: theme.textTheme.labelSmall), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/features/setup_wizard/wizard_shell.dart b/mobile/lib/features/setup_wizard/wizard_shell.dart new file mode 100644 index 0000000..7707b3b --- /dev/null +++ b/mobile/lib/features/setup_wizard/wizard_shell.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../app/theme.dart'; +import '../../providers/session_provider.dart'; +import '../../shared/models/match.dart'; +import '../../shared/api_client.dart'; +import '../../shared/widgets/screen_insets.dart'; +import 'step_match.dart'; +import 'step_network_test.dart'; +import 'step_roles.dart'; +import 'step_transmission.dart'; + +class WizardShell extends ConsumerStatefulWidget { + const WizardShell({ + super.key, + required this.matchId, + required this.step, + }); + + final String matchId; + final int step; + + @override + ConsumerState createState() => _WizardShellState(); +} + +class _WizardShellState extends ConsumerState { + MatchModel? _match; + bool _loading = true; + + static const _stepTitles = [ + '01 · Partita', + '02 · Trasmissione', + '03 · Ruoli', + '04 · Test', + ]; + + @override + void initState() { + super.initState(); + _loadMatch(); + } + + @override + void didUpdateWidget(WizardShell oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.matchId != widget.matchId) { + _loadMatch(); + } + } + + Future _loadMatch() async { + setState(() => _loading = true); + try { + final client = ref.read(apiClientProvider); + final match = await client.fetchMatch(widget.matchId); + ref.read(sessionProvider.notifier).setMatch(match); + if (mounted) { + setState(() { + _match = match; + _loading = false; + }); + } + } catch (_) { + if (mounted) setState(() => _loading = false); + } + } + + void _goToStep(int step) { + context.go('/setup/${widget.matchId}/$step'); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final stepIndex = widget.step.clamp(1, 4) - 1; + + return Scaffold( + appBar: AppBar( + title: Text(_stepTitles[stepIndex]), + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => context.go('/matches'), + ), + ), + body: _loading + ? const Center( + child: CircularProgressIndicator(color: AppTheme.primaryRed), + ) + : Column( + children: [ + _StepIndicator(current: widget.step), + Expanded( + child: AppSafeArea( + top: false, + child: _buildStep(stepIndex), + ), + ), + ], + ), + ); + } + + Widget _buildStep(int index) { + final match = _match; + if (match == null) { + return Center( + child: Text( + 'Partita non trovata', + style: Theme.of(context).textTheme.bodyMedium, + ), + ); + } + + switch (index) { + case 0: + return StepMatch( + match: match, + onNext: () => _goToStep(2), + ); + case 1: + return StepTransmission( + match: match, + onBack: () => _goToStep(1), + onNext: () => _goToStep(3), + ); + case 2: + return StepRoles( + match: match, + onBack: () => _goToStep(2), + onNext: () => _goToStep(4), + ); + case 3: + return StepNetworkTest( + match: match, + onBack: () => _goToStep(3), + ); + default: + return const SizedBox.shrink(); + } + } +} + +class _StepIndicator extends StatelessWidget { + const _StepIndicator({required this.current}); + + final int current; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + child: Row( + children: List.generate(4, (i) { + final step = i + 1; + final active = step <= current; + return Expanded( + child: Container( + height: 4, + margin: EdgeInsets.only(right: i < 3 ? 6 : 0), + decoration: BoxDecoration( + color: active ? AppTheme.primaryRed : AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(2), + ), + ), + ); + }), + ), + ); + } +} diff --git a/mobile/lib/features/shared/live_score_actions.dart b/mobile/lib/features/shared/live_score_actions.dart new file mode 100644 index 0000000..3fdf161 --- /dev/null +++ b/mobile/lib/features/shared/live_score_actions.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../providers/match_rules_provider.dart'; +import '../../providers/score_provider.dart'; +import '../../shared/scoring/match_scoring_rules.dart'; +import '../../shared/widgets/score_outcome_dialogs.dart'; + +/// Gestione regole punteggio, dialoghi set/partita e sync. +class LiveScoreActions { + LiveScoreActions(this.ref); + + final WidgetRef ref; + bool _dialogOpen = false; + + MatchScoringRules get _rules => ref.read(matchScoringRulesProvider); + + Future afterPointChange( + BuildContext context, { + required String homeName, + required String awayName, + required Future Function() onCloseSetConfirmed, + }) async { + if (_dialogOpen || !context.mounted) return; + final score = ref.read(scoreProvider); + final winner = _rules.setWinnerFromPoints( + homePoints: score.homePoints, + awayPoints: score.awayPoints, + currentSet: score.currentSet, + ); + if (winner == null) return; + + _dialogOpen = true; + final close = await showSetWonDialog( + context, + winner: winner, + homeName: homeName, + awayName: awayName, + homePoints: score.homePoints, + awayPoints: score.awayPoints, + ); + _dialogOpen = false; + if (close && context.mounted) { + await onCloseSetConfirmed(); + } + } + + Future requestCloseSet( + BuildContext context, { + required Future Function() onCloseSetConfirmed, + }) async { + if (_dialogOpen || !context.mounted) return; + final score = ref.read(scoreProvider); + final winner = _rules.setWinnerFromPoints( + homePoints: score.homePoints, + awayPoints: score.awayPoints, + currentSet: score.currentSet, + ); + if (winner == null) { + final ok = await showCloseSetAnywayDialog(context); + if (!ok) return; + } + await onCloseSetConfirmed(); + } + + Future afterCloseSet( + BuildContext context, { + required String homeName, + required String awayName, + required Future Function() onStopStream, + }) async { + if (_dialogOpen || !context.mounted) return; + final score = ref.read(scoreProvider); + final winner = _rules.matchWinner( + homeSets: score.homeSets, + awaySets: score.awaySets, + ); + if (winner == null) return; + + _dialogOpen = true; + final stop = await showMatchWonDialog( + context, + winner: winner, + homeName: homeName, + awayName: awayName, + homeSets: score.homeSets, + awaySets: score.awaySets, + ); + _dialogOpen = false; + if (stop && context.mounted) { + await onStopStream(); + } + } +} diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart new file mode 100644 index 0000000..531d6a3 --- /dev/null +++ b/mobile/lib/main.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import 'app/router.dart'; +import 'app/theme.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await initializeDateFormatting('it_IT', null); + await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemNavigationBarColor: Colors.transparent, + statusBarIconBrightness: Brightness.light, + systemNavigationBarIconBrightness: Brightness.light, + ), + ); + runApp(const ProviderScope(child: MatchLiveTvApp())); +} + +class MatchLiveTvApp extends ConsumerWidget { + const MatchLiveTvApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final router = ref.watch(routerProvider); + + return MaterialApp.router( + title: 'Match Live TV', + debugShowCheckedModeBanner: false, + theme: AppTheme.dark, + routerConfig: router, + ); + } +} diff --git a/mobile/lib/platform/streaming_channel.dart b/mobile/lib/platform/streaming_channel.dart new file mode 100644 index 0000000..1c3c343 --- /dev/null +++ b/mobile/lib/platform/streaming_channel.dart @@ -0,0 +1,53 @@ +import 'package:flutter/services.dart'; + +/// Bridge verso engine nativo Android (CameraX + RootEncoder). +class StreamingChannel { + StreamingChannel._(); + + static const MethodChannel _channel = + MethodChannel('com.matchlivetv.match_live_tv/streaming'); + + static const EventChannel _metricsChannel = + EventChannel('com.matchlivetv.match_live_tv/streaming_events'); + + static Future startPreview() async { + await _channel.invokeMethod('startPreview'); + } + + static Future bindPreview() async { + final ok = await _channel.invokeMethod('bindPreview'); + return ok ?? false; + } + + static Future stopPreview() async { + await _channel.invokeMethod('stopPreview'); + } + + static Future startStream({ + required String rtmpUrl, + int targetBitrate = 2500000, + int targetFps = 30, + }) async { + await _channel.invokeMethod('startStream', { + 'rtmpUrl': rtmpUrl, + 'videoBitrate': targetBitrate, + 'fps': targetFps, + }); + } + + static Future stopStream() async { + await _channel.invokeMethod('stopStream'); + } + + static Future pauseStream() async { + await _channel.invokeMethod('pauseStream'); + } + + static Future?> getMetrics() async { + final result = + await _channel.invokeMethod>('getMetrics'); + return result?.map((key, value) => MapEntry(key.toString(), value)); + } + + static EventChannel get metricsStream => _metricsChannel; +} diff --git a/mobile/lib/platform/streaming_preview.dart b/mobile/lib/platform/streaming_preview.dart new file mode 100644 index 0000000..5aaa2ed --- /dev/null +++ b/mobile/lib/platform/streaming_preview.dart @@ -0,0 +1,103 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// Preview camera nativa (OpenGL) o placeholder su altre piattaforme. +class NativeStreamingPreview extends StatelessWidget { + const NativeStreamingPreview({ + super.key, + this.showGrid = true, + this.platformLabel = 'LIVE', + }); + + final bool showGrid; + final String platformLabel; + + @override + Widget build(BuildContext context) { + if (defaultTargetPlatform == TargetPlatform.android) { + return Stack( + fit: StackFit.expand, + children: [ + AndroidView( + key: key, + viewType: 'match_live_tv/streaming_preview', + layoutDirection: TextDirection.ltr, + creationParamsCodec: StandardMessageCodec(), + ), + if (showGrid) CameraPreviewOverlay(platformLabel: platformLabel), + ], + ); + } + return CameraPreviewPlaceholder(showGrid: showGrid); + } +} + +class CameraPreviewPlaceholder extends StatelessWidget { + const CameraPreviewPlaceholder({super.key, this.showGrid = true}); + + final bool showGrid; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.black, + child: const Center( + child: Icon(Icons.videocam, size: 64, color: Colors.white24), + ), + ); + } +} + +/// Overlay REC / badge sopra la preview nativa. +class CameraPreviewOverlay extends StatelessWidget { + const CameraPreviewOverlay({super.key, this.platformLabel = 'LIVE'}); + + final String platformLabel; + + @override + Widget build(BuildContext context) { + final inset = MediaQuery.viewPaddingOf(context); + const gap = 8.0; + + return Stack( + fit: StackFit.expand, + children: [ + Positioned( + top: inset.top + gap, + left: inset.left + gap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(4), + ), + child: const Text( + 'REC', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 11, + ), + ), + ), + ), + Positioned( + top: inset.top + gap, + right: inset.right + gap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + platformLabel, + style: const TextStyle(color: Colors.white, fontSize: 11), + ), + ), + ), + ], + ); + } +} diff --git a/mobile/lib/providers/auth_provider.dart b/mobile/lib/providers/auth_provider.dart new file mode 100644 index 0000000..1eb9479 --- /dev/null +++ b/mobile/lib/providers/auth_provider.dart @@ -0,0 +1,90 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../core/auth_storage.dart'; +import '../shared/models/user.dart'; + +class AuthState { + const AuthState({ + this.user, + this.accessToken, + this.refreshToken, + this.loading = false, + this.error, + }); + + final User? user; + final String? accessToken; + final String? refreshToken; + final bool loading; + final String? error; + + bool get isAuthenticated => + accessToken != null && accessToken!.isNotEmpty && user != null; + + AuthState copyWith({ + User? user, + String? accessToken, + String? refreshToken, + bool? loading, + String? error, + bool clearError = false, + }) { + return AuthState( + user: user ?? this.user, + accessToken: accessToken ?? this.accessToken, + refreshToken: refreshToken ?? this.refreshToken, + loading: loading ?? this.loading, + error: clearError ? null : (error ?? this.error), + ); + } +} + +class AuthNotifier extends StateNotifier { + AuthNotifier() : super(const AuthState()); + + void setSession({ + required User user, + required String accessToken, + required String refreshToken, + }) { + state = AuthState( + user: user, + accessToken: accessToken, + refreshToken: refreshToken, + loading: false, + ); + AuthStorage.save( + user: user, + accessToken: accessToken, + refreshToken: refreshToken, + ); + } + + Future restoreSession() async { + final stored = await AuthStorage.load(); + if (stored == null) return; + state = AuthState( + user: stored.user, + accessToken: stored.accessToken, + refreshToken: stored.refreshToken, + loading: false, + ); + } + + void setLoading(bool loading) { + state = state.copyWith(loading: loading, clearError: true); + } + + void setError(String message) { + state = state.copyWith(loading: false, error: message); + } + + void logout() { + state = const AuthState(); + AuthStorage.clear(); + } +} + +final authProvider = StateNotifierProvider( + (ref) => AuthNotifier(), +); diff --git a/mobile/lib/providers/match_rules_provider.dart b/mobile/lib/providers/match_rules_provider.dart new file mode 100644 index 0000000..7ab3c8b --- /dev/null +++ b/mobile/lib/providers/match_rules_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../shared/scoring/match_scoring_rules.dart'; +import 'session_provider.dart'; + +final matchScoringRulesProvider = Provider((ref) { + final match = ref.watch(sessionProvider).match; + if (match == null) { + return const MatchScoringRules(setsToWin: 3); + } + return MatchScoringRules.fromMatch(match); +}); diff --git a/mobile/lib/providers/score_provider.dart b/mobile/lib/providers/score_provider.dart new file mode 100644 index 0000000..ccb28fd --- /dev/null +++ b/mobile/lib/providers/score_provider.dart @@ -0,0 +1,77 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../shared/models/score_state.dart'; + +class ScoreNotifier extends StateNotifier { + ScoreNotifier() : super(const ScoreState()); + + void reset(ScoreState score) { + state = score; + } + + void applyRemote(ScoreState score) { + state = score; + } + + void incrementHome() { + state = state.copyWith(homePoints: state.homePoints + 1); + } + + void incrementAway() { + state = state.copyWith(awayPoints: state.awayPoints + 1); + } + + void decrementHome() { + if (state.homePoints > 0) { + state = state.copyWith(homePoints: state.homePoints - 1); + } + } + + void decrementAway() { + if (state.awayPoints > 0) { + state = state.copyWith(awayPoints: state.awayPoints - 1); + } + } + + void closeSet() { + final homeWon = state.homePoints > state.awayPoints; + final partials = List.from(state.setPartials); + if (state.homePoints > 0 || state.awayPoints > 0) { + partials.add(SetPartial( + set: state.currentSet, + home: state.homePoints, + away: state.awayPoints, + )); + } + state = state.copyWith( + homeSets: homeWon ? state.homeSets + 1 : state.homeSets, + awaySets: homeWon ? state.awaySets : state.awaySets + 1, + homePoints: 0, + awayPoints: 0, + currentSet: state.currentSet + 1, + setPartials: partials, + timeoutHome: false, + timeoutAway: false, + ); + } + + void timeoutHome() { + state = state.copyWith(timeoutHome: true); + } + + void timeoutAway() { + state = state.copyWith(timeoutAway: true); + } + + void setTimeoutHome(bool value) { + state = state.copyWith(timeoutHome: value); + } + + void setTimeoutAway(bool value) { + state = state.copyWith(timeoutAway: value); + } +} + +final scoreProvider = StateNotifierProvider( + (ref) => ScoreNotifier(), +); diff --git a/mobile/lib/providers/session_provider.dart b/mobile/lib/providers/session_provider.dart new file mode 100644 index 0000000..61373ce --- /dev/null +++ b/mobile/lib/providers/session_provider.dart @@ -0,0 +1,165 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../shared/models/device_state.dart'; +import '../shared/models/match.dart'; +import '../shared/models/stream_session.dart'; + +enum DeviceRole { camera, controller } + +class SessionState { + const SessionState({ + this.session, + this.match, + this.connected = false, + this.deviceRole = DeviceRole.controller, + this.cameraDevice, + this.elapsed = Duration.zero, + this.viewerCount = 0, + this.loading = false, + this.error, + }); + + final StreamSession? session; + final MatchModel? match; + final bool connected; + final DeviceRole deviceRole; + final DeviceStateModel? cameraDevice; + final Duration elapsed; + final int viewerCount; + final bool loading; + final String? error; + + SessionState copyWith({ + StreamSession? session, + MatchModel? match, + bool? connected, + DeviceRole? deviceRole, + DeviceStateModel? cameraDevice, + Duration? elapsed, + int? viewerCount, + bool? loading, + String? error, + bool clearError = false, + }) { + return SessionState( + session: session ?? this.session, + match: match ?? this.match, + connected: connected ?? this.connected, + deviceRole: deviceRole ?? this.deviceRole, + cameraDevice: cameraDevice ?? this.cameraDevice, + elapsed: elapsed ?? this.elapsed, + viewerCount: viewerCount ?? this.viewerCount, + loading: loading ?? this.loading, + error: clearError ? null : (error ?? this.error), + ); + } +} + +class SessionNotifier extends StateNotifier { + SessionNotifier() : super(const SessionState()); + + void setSession(StreamSession session, {MatchModel? match}) { + state = state.copyWith( + session: session, + match: match, + clearError: true, + ); + } + + void setMatch(MatchModel match) { + state = state.copyWith(match: match); + } + + void setConnected(bool connected) { + state = state.copyWith(connected: connected); + } + + void setDeviceRole(DeviceRole role) { + state = state.copyWith(deviceRole: role); + } + + void updateDeviceState(DeviceStateModel device) { + if (device.deviceRole == 'camera') { + state = state.copyWith(cameraDevice: device); + } + if (device.status != null && state.session != null) { + state = state.copyWith( + session: StreamSession( + id: state.session!.id, + matchId: state.session!.matchId, + status: device.status!, + platform: state.session!.platform, + rtmpIngestUrl: state.session!.rtmpIngestUrl, + youtubeBroadcastId: state.session!.youtubeBroadcastId, + privacyStatus: state.session!.privacyStatus, + qualityPreset: state.session!.qualityPreset, + targetBitrate: state.session!.targetBitrate, + startedAt: state.session!.startedAt, + disconnectionCount: state.session!.disconnectionCount, + score: state.session!.score, + devices: state.session!.devices, + ), + ); + } + } + + void setElapsed(Duration elapsed) { + state = state.copyWith(elapsed: elapsed); + } + + void setViewerCount(int count) { + state = state.copyWith(viewerCount: count); + } + + void applyCommand(String action) { + final session = state.session; + if (session == null) return; + + String newStatus = session.status; + switch (action) { + case 'start_stream': + newStatus = 'live'; + break; + case 'pause_stream': + newStatus = 'paused'; + break; + case 'stop_stream': + newStatus = 'ended'; + break; + } + + state = state.copyWith( + session: StreamSession( + id: session.id, + matchId: session.matchId, + status: newStatus, + platform: session.platform, + rtmpIngestUrl: session.rtmpIngestUrl, + youtubeBroadcastId: session.youtubeBroadcastId, + privacyStatus: session.privacyStatus, + qualityPreset: session.qualityPreset, + targetBitrate: session.targetBitrate, + startedAt: session.startedAt, + disconnectionCount: session.disconnectionCount, + score: session.score, + devices: session.devices, + ), + ); + } + + void setLoading(bool loading) { + state = state.copyWith(loading: loading); + } + + void setError(String message) { + state = state.copyWith(loading: false, error: message); + } + + void reset() { + state = const SessionState(); + } +} + +final sessionProvider = StateNotifierProvider( + (ref) => SessionNotifier(), +); diff --git a/mobile/lib/shared/api_client.dart b/mobile/lib/shared/api_client.dart new file mode 100644 index 0000000..c086bad --- /dev/null +++ b/mobile/lib/shared/api_client.dart @@ -0,0 +1,249 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../core/config.dart'; +import '../providers/auth_provider.dart'; +import 'models/match.dart'; +import 'models/recording_item.dart'; +import 'models/stream_session.dart'; +import 'models/team.dart'; +import 'models/user.dart'; + +final apiClientProvider = Provider((ref) { + final auth = ref.watch(authProvider); + return ApiClient(accessToken: auth.accessToken); +}); + +class ApiClient { + ApiClient({this.accessToken}) { + _dio = Dio( + BaseOptions( + baseUrl: AppConfig.apiV1, + connectTimeout: const Duration(seconds: 15), + receiveTimeout: const Duration(seconds: 15), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + ), + ); + + _dio.interceptors.add( + InterceptorsWrapper( + onRequest: (options, handler) { + if (accessToken != null && accessToken!.isNotEmpty) { + options.headers['Authorization'] = 'Bearer $accessToken'; + } + handler.next(options); + }, + ), + ); + } + + late final Dio _dio; + final String? accessToken; + + // --- Auth --- + + Future<({User user, AuthTokens tokens})> login({ + required String email, + required String password, + }) async { + final response = await _dio.post>( + '/auth/login', + data: {'email': email, 'password': password}, + ); + final data = response.data!; + return ( + user: User.fromJson(data['user'] as Map), + tokens: AuthTokens.fromJson(data), + ); + } + + Future refresh(String refreshToken) async { + final response = await _dio.post>( + '/auth/refresh', + data: {'refresh_token': refreshToken}, + ); + return AuthTokens.fromJson(response.data!); + } + + Future me() async { + final response = await _dio.get>('/auth/me'); + return User.fromJson(response.data!); + } + + Future logout() async { + await _dio.post('/auth/logout'); + } + + // --- Teams --- + + Future> fetchTeams() async { + final response = await _dio.get>('/teams'); + return response.data! + .map((e) => Team.fromJson(e as Map)) + .toList(); + } + + Future> fetchRecordings(String teamId) async { + final response = await _dio.get>('/teams/$teamId/recordings'); + return response.data! + .map((e) => RecordingItem.fromJson(e as Map)) + .toList(); + } + + // --- Matches --- + + Future> fetchMatches(String teamId) async { + final response = + await _dio.get>('/teams/$teamId/matches'); + return response.data! + .map((e) => MatchModel.fromJson(e as Map)) + .toList(); + } + + Future fetchMatch(String matchId) async { + final response = + await _dio.get>('/matches/$matchId'); + return MatchModel.fromJson(response.data!); + } + + Future updateMatch(String matchId, Map body) async { + final response = await _dio.patch>( + '/matches/$matchId', + data: {'match': body}, + ); + return MatchModel.fromJson(response.data!); + } + + Future deleteMatch(String matchId) async { + await _dio.delete('/matches/$matchId'); + } + + Future createMatch(String teamId, Map body) async { + final response = await _dio.post>( + '/teams/$teamId/matches', + data: {'match': body}, + ); + return MatchModel.fromJson(response.data!); + } + + // --- Sessions --- + + Future createSession( + String matchId, { + String platform = 'matchlivetv', + String privacyStatus = 'unlisted', + String qualityPreset = '720p_30_2.5mbps', + int? targetBitrate, + int? targetFps, + }) async { + final response = await _dio.post>( + '/matches/$matchId/sessions', + data: { + 'platform': platform, + 'privacy_status': privacyStatus, + 'quality_preset': qualityPreset, + if (targetBitrate != null) 'target_bitrate': targetBitrate, + if (targetFps != null) 'target_fps': targetFps, + }, + ); + return StreamSession.fromJson(response.data!); + } + + Future fetchSession(String sessionId) async { + final response = + await _dio.get>('/sessions/$sessionId'); + return StreamSession.fromJson(response.data!); + } + + Future startSession(String sessionId) async { + final response = + await _dio.patch>('/sessions/$sessionId/start'); + return StreamSession.fromJson(response.data!); + } + + Future stopSession(String sessionId) async { + final response = + await _dio.patch>('/sessions/$sessionId/stop'); + return StreamSession.fromJson(response.data!); + } + + Future pauseSession(String sessionId) async { + final response = + await _dio.patch>('/sessions/$sessionId/pause'); + return StreamSession.fromJson(response.data!); + } + + Future createPairingToken(String sessionId) async { + final response = await _dio.post>( + '/sessions/$sessionId/pairing_token', + ); + return PairingTokenResponse.fromJson(response.data!); + } + + Future claimPairing({ + required String sessionId, + required String pairingToken, + required String deviceRole, + }) async { + final response = await _dio.post>( + '/sessions/$sessionId/claim_pairing', + data: { + 'pairing_token': pairingToken, + 'device_role': deviceRole, + }, + ); + return StreamSession.fromJson(response.data!); + } + + Future networkTest({ + required String sessionId, + required double downloadMbps, + required double uploadMbps, + required int latencyMs, + required String networkType, + }) async { + final response = await _dio.post>( + '/sessions/$sessionId/network_test', + data: { + 'download_mbps': downloadMbps, + 'upload_mbps': uploadMbps, + 'latency_ms': latencyMs, + 'network_type': networkType, + }, + ); + return NetworkTestResult.fromJson(response.data!); + } + + Future youtubeStats(String sessionId) async { + final response = + await _dio.get>('/sessions/$sessionId/youtube_stats'); + return YoutubeStats.fromJson(response.data!); + } + + Future postTelemetry({ + required String sessionId, + required String deviceRole, + int? batteryLevel, + String? networkType, + int? signalStrength, + int? currentBitrate, + int? targetBitrate, + int? fps, + }) async { + await _dio.post( + '/sessions/$sessionId/telemetry', + data: { + 'device_role': deviceRole, + if (batteryLevel != null) 'battery_level': batteryLevel, + if (networkType != null) 'network_type': networkType, + if (signalStrength != null) 'signal_strength': signalStrength, + if (currentBitrate != null) 'current_bitrate': currentBitrate, + if (targetBitrate != null) 'target_bitrate': targetBitrate, + if (fps != null) 'fps': fps, + }, + ); + } +} diff --git a/mobile/lib/shared/api_error.dart b/mobile/lib/shared/api_error.dart new file mode 100644 index 0000000..63ce291 --- /dev/null +++ b/mobile/lib/shared/api_error.dart @@ -0,0 +1,32 @@ +class ApiException implements Exception { + ApiException({ + required this.message, + this.statusCode, + this.errorCode, + this.billingUrl, + }); + + final String message; + final int? statusCode; + final String? errorCode; + final String? billingUrl; + + bool get isPremiumRequired => errorCode == 'premium_required'; + + @override + String toString() => message; + + static ApiException? fromDio(dynamic error) { + // ignore: avoid_dynamic_calls + final response = error.response; + if (response == null) return null; + final data = response.data; + if (data is! Map) return null; + return ApiException( + message: data['error'] as String? ?? 'Errore di rete', + statusCode: response.statusCode as int?, + errorCode: data['error_code'] as String?, + billingUrl: data['billing_url'] as String?, + ); + } +} diff --git a/mobile/lib/shared/models/device_state.dart b/mobile/lib/shared/models/device_state.dart new file mode 100644 index 0000000..4642bba --- /dev/null +++ b/mobile/lib/shared/models/device_state.dart @@ -0,0 +1,37 @@ +class DeviceStateModel { + const DeviceStateModel({ + this.deviceRole = 'camera', + this.batteryLevel, + this.networkType, + this.signalStrength, + this.currentBitrate, + this.targetBitrate, + this.fps, + this.status, + }); + + final String deviceRole; + final int? batteryLevel; + final String? networkType; + final int? signalStrength; + final int? currentBitrate; + final int? targetBitrate; + final int? fps; + final String? status; + + factory DeviceStateModel.fromJson(Map json) { + return DeviceStateModel( + deviceRole: json['device_role'] as String? ?? 'camera', + batteryLevel: json['battery'] as int? ?? json['battery_level'] as int?, + networkType: json['network'] as String? ?? json['network_type'] as String?, + signalStrength: json['signal_strength'] as int?, + currentBitrate: json['bitrate'] as int? ?? json['current_bitrate'] as int?, + targetBitrate: json['target_bitrate'] as int?, + fps: json['fps'] as int?, + status: json['status'] as String?, + ); + } + + double get bitrateMbps => + currentBitrate != null ? currentBitrate! / 1_000_000 : 0; +} diff --git a/mobile/lib/shared/models/match.dart b/mobile/lib/shared/models/match.dart new file mode 100644 index 0000000..181c890 --- /dev/null +++ b/mobile/lib/shared/models/match.dart @@ -0,0 +1,81 @@ +class MatchModel { + const MatchModel({ + required this.id, + required this.teamId, + required this.teamName, + required this.opponentName, + this.location, + this.scheduledAt, + this.sport = 'volleyball', + this.setsToWin = 3, + this.scoringRules, + this.rosterNumbers = const [], + this.category, + this.phase, + this.activeSessionId, + this.activeSessionStatus, + }); + + final String id; + final String teamId; + final String teamName; + final String opponentName; + final String? location; + final DateTime? scheduledAt; + final String sport; + final int setsToWin; + final Map? scoringRules; + final List rosterNumbers; + final String? category; + final String? phase; + final String? activeSessionId; + final String? activeSessionStatus; + + /// Sessione avviata (o in ripresa) — si può rientrare in camera senza rifare il wizard. + bool get canResumeCamera { + const resumable = {'connecting', 'live', 'reconnecting', 'paused'}; + return activeSessionId != null && + activeSessionStatus != null && + resumable.contains(activeSessionStatus); + } + + bool get hasActiveSession => activeSessionId != null; + + factory MatchModel.fromJson(Map json) { + return MatchModel( + id: json['id'] as String, + teamId: json['team_id'] as String, + teamName: json['team_name'] as String? ?? '', + opponentName: json['opponent_name'] as String, + location: json['location'] as String?, + scheduledAt: json['scheduled_at'] != null + ? DateTime.tryParse(json['scheduled_at'] as String) + : null, + sport: json['sport'] as String? ?? 'volleyball', + setsToWin: json['sets_to_win'] as int? ?? 3, + scoringRules: json['scoring_rules'] is Map + ? Map.from(json['scoring_rules'] as Map) + : null, + rosterNumbers: (json['roster_numbers'] as List?) + ?.map((e) => (e as num).toInt()) + .toList() ?? + const [], + category: json['category'] as String?, + phase: json['phase'] as String?, + activeSessionId: json['active_session_id'] as String?, + activeSessionStatus: json['active_session_status'] as String?, + ); + } + + Map toJson() => { + 'opponent_name': opponentName, + 'location': location, + 'scheduled_at': scheduledAt?.toIso8601String(), + 'sport': sport, + 'sets_to_win': setsToWin, + if (scoringRules != null) 'scoring_rules': scoringRules, + 'roster_numbers': rosterNumbers, + 'category': category, + 'phase': phase, + }; +} diff --git a/mobile/lib/shared/models/recording_item.dart b/mobile/lib/shared/models/recording_item.dart new file mode 100644 index 0000000..d87d381 --- /dev/null +++ b/mobile/lib/shared/models/recording_item.dart @@ -0,0 +1,35 @@ +class RecordingItem { + const RecordingItem({ + required this.id, + required this.sessionId, + required this.opponentName, + required this.teamName, + this.endedAt, + this.replayUrl, + this.expiresAt, + }); + + final String id; + final String sessionId; + final String opponentName; + final String teamName; + final DateTime? endedAt; + final String? replayUrl; + final DateTime? expiresAt; + + factory RecordingItem.fromJson(Map json) { + return RecordingItem( + id: json['id'] as String, + sessionId: json['session_id'] as String, + opponentName: json['opponent_name'] as String, + teamName: json['team_name'] as String, + endedAt: json['ended_at'] != null + ? DateTime.parse(json['ended_at'] as String) + : null, + replayUrl: json['replay_url'] as String?, + expiresAt: json['expires_at'] != null + ? DateTime.parse(json['expires_at'] as String) + : null, + ); + } +} diff --git a/mobile/lib/shared/models/score_state.dart b/mobile/lib/shared/models/score_state.dart new file mode 100644 index 0000000..55cf4cf --- /dev/null +++ b/mobile/lib/shared/models/score_state.dart @@ -0,0 +1,100 @@ +class SetPartial { + const SetPartial({ + required this.set, + required this.home, + required this.away, + }); + + final int set; + final int home; + final int away; + + factory SetPartial.fromJson(Map json) { + return SetPartial( + set: json['set'] as int? ?? 1, + home: json['home'] as int? ?? 0, + away: json['away'] as int? ?? 0, + ); + } + + Map toJson() => { + 'set': set, + 'home': home, + 'away': away, + }; +} + +class ScoreState { + const ScoreState({ + this.homeSets = 0, + this.awaySets = 0, + this.homePoints = 0, + this.awayPoints = 0, + this.currentSet = 1, + this.setPartials = const [], + this.timeoutHome = false, + this.timeoutAway = false, + }); + + final int homeSets; + final int awaySets; + final int homePoints; + final int awayPoints; + final int currentSet; + final List setPartials; + final bool timeoutHome; + final bool timeoutAway; + + factory ScoreState.fromJson(Map json) { + final rawPartials = json['set_partials']; + final partials = rawPartials is List + ? rawPartials + .whereType() + .map((e) => SetPartial.fromJson(Map.from(e))) + .toList() + : []; + + return ScoreState( + homeSets: json['home_sets'] as int? ?? 0, + awaySets: json['away_sets'] as int? ?? 0, + homePoints: json['home_points'] as int? ?? 0, + awayPoints: json['away_points'] as int? ?? 0, + currentSet: json['current_set'] as int? ?? 1, + setPartials: partials, + timeoutHome: json['timeout_home'] as bool? ?? false, + timeoutAway: json['timeout_away'] as bool? ?? false, + ); + } + + Map toCablePayload() => { + 'type': 'score_update', + 'home_sets': homeSets, + 'away_sets': awaySets, + 'home_points': homePoints, + 'away_points': awayPoints, + 'current_set': currentSet, + 'set_partials': setPartials.map((p) => p.toJson()).toList(), + }; + + ScoreState copyWith({ + int? homeSets, + int? awaySets, + int? homePoints, + int? awayPoints, + int? currentSet, + List? setPartials, + bool? timeoutHome, + bool? timeoutAway, + }) { + return ScoreState( + homeSets: homeSets ?? this.homeSets, + awaySets: awaySets ?? this.awaySets, + homePoints: homePoints ?? this.homePoints, + awayPoints: awayPoints ?? this.awayPoints, + currentSet: currentSet ?? this.currentSet, + setPartials: setPartials ?? this.setPartials, + timeoutHome: timeoutHome ?? this.timeoutHome, + timeoutAway: timeoutAway ?? this.timeoutAway, + ); + } +} diff --git a/mobile/lib/shared/models/stream_session.dart b/mobile/lib/shared/models/stream_session.dart new file mode 100644 index 0000000..a0c8743 --- /dev/null +++ b/mobile/lib/shared/models/stream_session.dart @@ -0,0 +1,129 @@ +import 'device_state.dart'; +import 'score_state.dart'; + +class StreamSession { + const StreamSession({ + required this.id, + required this.matchId, + required this.status, + this.platform = 'matchlivetv', + this.rtmpIngestUrl, + this.hlsPlaybackUrl, + this.watchPageUrl, + this.youtubeBroadcastId, + this.privacyStatus = 'unlisted', + this.qualityPreset = '720p_30_2.5mbps', + this.targetBitrate = 2500000, + this.startedAt, + this.disconnectionCount = 0, + this.score, + this.devices = const [], + }); + + final String id; + final String matchId; + final String status; + final String platform; + final String? rtmpIngestUrl; + final String? hlsPlaybackUrl; + final String? watchPageUrl; + final String? youtubeBroadcastId; + final String privacyStatus; + final String qualityPreset; + final int targetBitrate; + final DateTime? startedAt; + final int disconnectionCount; + final ScoreState? score; + final List devices; + + bool get isLive => status == 'live' || status == 'reconnecting'; + + bool get isTerminal => status == 'ended' || status == 'error'; + + bool get canResumeBroadcast => + status == 'connecting' || + status == 'live' || + status == 'reconnecting' || + status == 'paused'; + + factory StreamSession.fromJson(Map json) { + return StreamSession( + id: json['id'] as String, + matchId: json['match_id'] as String, + status: json['status'] as String? ?? 'idle', + platform: json['platform'] as String? ?? 'matchlivetv', + rtmpIngestUrl: json['rtmp_ingest_url'] as String?, + hlsPlaybackUrl: json['hls_playback_url'] as String?, + watchPageUrl: json['watch_page_url'] as String?, + youtubeBroadcastId: json['youtube_broadcast_id'] as String?, + privacyStatus: json['privacy_status'] as String? ?? 'unlisted', + qualityPreset: json['quality_preset'] as String? ?? '720p_30_2.5mbps', + targetBitrate: json['target_bitrate'] as int? ?? 2500000, + startedAt: json['started_at'] != null + ? DateTime.tryParse(json['started_at'] as String) + : null, + disconnectionCount: json['disconnection_count'] as int? ?? 0, + score: json['score'] != null + ? ScoreState.fromJson(json['score'] as Map) + : null, + devices: (json['devices'] as List?) + ?.map((e) => DeviceStateModel.fromJson(e as Map)) + .toList() ?? + const [], + ); + } +} + +class PairingTokenResponse { + const PairingTokenResponse({ + required this.pairingToken, + required this.expiresAt, + required this.qrPayload, + }); + + final String pairingToken; + final DateTime expiresAt; + final Map qrPayload; + + factory PairingTokenResponse.fromJson(Map json) { + return PairingTokenResponse( + pairingToken: json['pairing_token'] as String, + expiresAt: DateTime.parse(json['expires_at'] as String), + qrPayload: json['qr_payload'] as Map, + ); + } +} + +class NetworkTestResult { + const NetworkTestResult({ + required this.ready, + required this.targetUploadMbps, + }); + + final bool ready; + final double targetUploadMbps; + + factory NetworkTestResult.fromJson(Map json) { + return NetworkTestResult( + ready: json['ready'] as bool? ?? false, + targetUploadMbps: (json['target_upload_mbps'] as num?)?.toDouble() ?? 2.5, + ); + } +} + +class YoutubeStats { + const YoutubeStats({ + required this.concurrentViewers, + required this.live, + }); + + final int concurrentViewers; + final bool live; + + factory YoutubeStats.fromJson(Map json) { + return YoutubeStats( + concurrentViewers: json['concurrent_viewers'] as int? ?? 0, + live: json['live'] as bool? ?? false, + ); + } +} diff --git a/mobile/lib/shared/models/team.dart b/mobile/lib/shared/models/team.dart new file mode 100644 index 0000000..147f25d --- /dev/null +++ b/mobile/lib/shared/models/team.dart @@ -0,0 +1,96 @@ +class Team { + const Team({ + required this.id, + required this.name, + required this.sport, + this.logoUrl, + this.youtubeConnected = false, + this.planSlug = 'free', + this.planName = 'Free', + this.premiumActive = false, + this.premiumFull = false, + this.billingUrl, + this.staffManageUrl, + this.maxStaff = 2, + this.staffUsed = 0, + this.maxStaffTransmission, + this.maxStaffRegia, + this.staffTransmissionUsed = 0, + this.staffRegiaUsed = 0, + this.concurrentStreamsUsed = 0, + this.concurrentStreamsLimit, + this.recordingsEnabled = false, + this.phoneDownloadEnabled = false, + this.youtubeEnabled = false, + this.youtubeMode, + }); + + final String id; + final String name; + final String sport; + final String? logoUrl; + final bool youtubeConnected; + final String planSlug; + final String planName; + final bool premiumActive; + final bool premiumFull; + final String? billingUrl; + final String? staffManageUrl; + final int maxStaff; + final int staffUsed; + final int? maxStaffTransmission; + final int? maxStaffRegia; + final int staffTransmissionUsed; + final int staffRegiaUsed; + final int concurrentStreamsUsed; + final int? concurrentStreamsLimit; + final bool recordingsEnabled; + final bool phoneDownloadEnabled; + final bool youtubeEnabled; + final String? youtubeMode; + + bool get canUseYoutube => premiumFull && youtubeEnabled; + bool get canUseRecordings => recordingsEnabled; + bool get canDownloadOnPhone => phoneDownloadEnabled; + + String get staffLimitLabel { + if (maxStaffTransmission == null && maxStaffRegia == null) { + return 'illimitato'; + } + final tx = maxStaffTransmission == null + ? 'tx ∞' + : 'tx $staffTransmissionUsed/$maxStaffTransmission'; + final rg = maxStaffRegia == null + ? 'rg ∞' + : 'rg $staffRegiaUsed/$maxStaffRegia'; + return '$tx · $rg'; + } + + factory Team.fromJson(Map json) { + return Team( + id: json['id'] as String, + name: json['name'] as String, + sport: json['sport'] as String? ?? 'volleyball', + logoUrl: json['logo_url'] as String?, + youtubeConnected: json['youtube_connected'] as bool? ?? false, + planSlug: json['plan_slug'] as String? ?? 'free', + planName: json['plan_name'] as String? ?? 'Free', + premiumActive: json['premium_active'] as bool? ?? false, + premiumFull: json['premium_full'] as bool? ?? false, + billingUrl: json['billing_url'] as String?, + staffManageUrl: json['staff_manage_url'] as String?, + maxStaff: json['max_staff'] as int? ?? 2, + staffUsed: json['staff_used'] as int? ?? 0, + maxStaffTransmission: json['max_staff_transmission'] as int?, + maxStaffRegia: json['max_staff_regia'] as int?, + staffTransmissionUsed: json['staff_transmission_used'] as int? ?? 0, + staffRegiaUsed: json['staff_regia_used'] as int? ?? 0, + concurrentStreamsUsed: json['concurrent_streams_used'] as int? ?? 0, + concurrentStreamsLimit: json['concurrent_streams_limit'] as int?, + recordingsEnabled: json['recordings_enabled'] as bool? ?? false, + phoneDownloadEnabled: json['phone_download_enabled'] as bool? ?? false, + youtubeEnabled: json['youtube_enabled'] as bool? ?? false, + youtubeMode: json['youtube_mode'] as String?, + ); + } +} diff --git a/mobile/lib/shared/models/user.dart b/mobile/lib/shared/models/user.dart new file mode 100644 index 0000000..7f35f05 --- /dev/null +++ b/mobile/lib/shared/models/user.dart @@ -0,0 +1,46 @@ +class User { + const User({ + required this.id, + required this.email, + required this.name, + required this.role, + }); + + final String id; + final String email; + final String name; + final String role; + + factory User.fromJson(Map json) { + return User( + id: json['id'] as String, + email: json['email'] as String, + name: json['name'] as String, + role: json['role'] as String? ?? 'coach', + ); + } + + Map toJson() => { + 'id': id, + 'email': email, + 'name': name, + 'role': role, + }; +} + +class AuthTokens { + const AuthTokens({ + required this.accessToken, + required this.refreshToken, + }); + + final String accessToken; + final String refreshToken; + + factory AuthTokens.fromJson(Map json) { + return AuthTokens( + accessToken: json['access_token'] as String, + refreshToken: json['refresh_token'] as String, + ); + } +} diff --git a/mobile/lib/shared/premium_dialog.dart b/mobile/lib/shared/premium_dialog.dart new file mode 100644 index 0000000..169f946 --- /dev/null +++ b/mobile/lib/shared/premium_dialog.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../app/theme.dart'; + +Future showPremiumRequiredDialog( + BuildContext context, { + required String message, + String? billingUrl, +}) async { + await showDialog( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: AppTheme.surface, + title: const Text('Premium richiesto'), + content: Text(message), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text('Chiudi'), + ), + if (billingUrl != null && billingUrl.isNotEmpty) + FilledButton( + onPressed: () async { + final uri = Uri.parse(billingUrl); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + if (ctx.mounted) Navigator.pop(ctx); + }, + style: FilledButton.styleFrom(backgroundColor: AppTheme.primaryRed), + child: const Text('Attiva sul sito'), + ), + ], + ), + ); +} diff --git a/mobile/lib/shared/scoring/match_scoring_rules.dart b/mobile/lib/shared/scoring/match_scoring_rules.dart new file mode 100644 index 0000000..b01f5d6 --- /dev/null +++ b/mobile/lib/shared/scoring/match_scoring_rules.dart @@ -0,0 +1,70 @@ +import '../models/match.dart'; + +/// Lato che ha vinto set o partita. +enum ScoringSide { home, away } + +/// Regole punteggio (pallavolo di default, campi sovrascrivibili per tornei). +class MatchScoringRules { + const MatchScoringRules({ + required this.setsToWin, + this.pointsPerSet = 25, + this.pointsDecidingSet = 15, + this.minPointLead = 2, + }); + + final int setsToWin; + final int pointsPerSet; + final int pointsDecidingSet; + final int minPointLead; + + int get maxSets => (setsToWin * 2) - 1; + + factory MatchScoringRules.fromMatch(MatchModel match) { + final overrides = match.scoringRules; + return MatchScoringRules( + setsToWin: match.setsToWin, + pointsPerSet: overrides?['points_per_set'] as int? ?? 25, + pointsDecidingSet: overrides?['points_deciding_set'] as int? ?? 15, + minPointLead: overrides?['min_point_lead'] as int? ?? 2, + ); + } + + Map toJson() => { + 'points_per_set': pointsPerSet, + 'points_deciding_set': pointsDecidingSet, + 'min_point_lead': minPointLead, + }; + + /// Punti necessari per chiudere il set corrente. + int pointsTarget(int currentSet) { + if (currentSet >= maxSets) return pointsDecidingSet; + return pointsPerSet; + } + + /// Chi ha vinto il set in corso in base ai punti (null se ancora aperto). + ScoringSide? setWinnerFromPoints({ + required int homePoints, + required int awayPoints, + required int currentSet, + }) { + final target = pointsTarget(currentSet); + final lead = minPointLead; + if (homePoints >= target && homePoints - awayPoints >= lead) { + return ScoringSide.home; + } + if (awayPoints >= target && awayPoints - homePoints >= lead) { + return ScoringSide.away; + } + return null; + } + + bool isMatchOver({required int homeSets, required int awaySets}) { + return homeSets >= setsToWin || awaySets >= setsToWin; + } + + ScoringSide? matchWinner({required int homeSets, required int awaySets}) { + if (homeSets >= setsToWin) return ScoringSide.home; + if (awaySets >= setsToWin) return ScoringSide.away; + return null; + } +} diff --git a/mobile/lib/shared/websocket_service.dart b/mobile/lib/shared/websocket_service.dart new file mode 100644 index 0000000..c7b8354 --- /dev/null +++ b/mobile/lib/shared/websocket_service.dart @@ -0,0 +1,158 @@ +import 'dart:convert'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:x_action_cable_v2/x_action_cable.dart'; + +import '../core/config.dart'; +import '../providers/auth_provider.dart'; +import '../providers/score_provider.dart'; +import '../providers/session_provider.dart'; +import 'models/device_state.dart'; +import 'models/score_state.dart'; + +final websocketServiceProvider = Provider((ref) { + final service = WebsocketService(ref); + ref.onDispose(service.dispose); + return service; +}); + +typedef CableMessageHandler = void Function(Map data); + +class WebsocketService { + WebsocketService(this._ref); + + final Ref _ref; + ActionCable? _cable; + ActionChannel? _channel; + String? _sessionId; + + bool get isConnected => _cable != null && _channel != null; + + Future connect({ + required String sessionId, + required String deviceRole, + }) async { + if (_sessionId == sessionId && isConnected) return; + + await disconnect(); + + final token = _ref.read(authProvider).accessToken; + if (token == null || token.isEmpty) { + throw StateError('Token di accesso mancante'); + } + + _sessionId = sessionId; + + _cable = ActionCable.connect( + AppConfig.cableUrl, + headers: {'Authorization': 'Bearer $token'}, + onConnected: () { + _ref.read(sessionProvider.notifier).setConnected(true); + }, + onConnectionLost: () { + _ref.read(sessionProvider.notifier).setConnected(false); + }, + onCannotConnect: (_) { + _ref.read(sessionProvider.notifier).setConnected(false); + }, + ); + + _channel = _cable!.subscribe( + 'SessionChannel', + channelParams: { + 'session_id': sessionId, + 'device_role': deviceRole, + }, + onSubscribed: () { + _ref.read(sessionProvider.notifier).setConnected(true); + }, + onDisconnected: () { + _ref.read(sessionProvider.notifier).setConnected(false); + }, + callbacks: [ + ActionCallback( + name: 'receive_message', + callback: _handleMessage, + ), + ], + ); + } + + void _handleMessage(ActionResponse response) { + if (response.hasError || response.data == null) return; + _dispatch(Map.from(response.data!)); + } + + void _dispatch(Map data) { + final type = data['type'] as String?; + + switch (type) { + case 'score_update': + _ref.read(scoreProvider.notifier).applyRemote(ScoreState.fromJson(data)); + break; + case 'device_state': + _ref + .read(sessionProvider.notifier) + .updateDeviceState(DeviceStateModel.fromJson(data)); + break; + case 'timeout': + final team = data['team'] as String?; + if (team == 'home') { + _ref.read(scoreProvider.notifier).setTimeoutHome(true); + } else if (team == 'away') { + _ref.read(scoreProvider.notifier).setTimeoutAway(true); + } + break; + case 'command': + final action = data['action'] as String?; + if (action != null) { + _ref.read(sessionProvider.notifier).applyCommand(action); + } + break; + default: + break; + } + } + + void sendScoreUpdate(ScoreState score) { + _perform({'type': 'score_update', ...score.toCablePayload()}); + } + + void sendTimeout(String team) { + _perform({'type': 'timeout', 'team': team}); + } + + void sendCommand(String action) { + _perform({'type': 'command', 'action': action}); + } + + void sendDeviceState(DeviceStateModel state) { + _perform({ + 'type': 'device_state', + 'device_role': state.deviceRole, + 'battery': state.batteryLevel, + 'network': state.networkType, + 'bitrate': state.currentBitrate, + 'fps': state.fps, + }); + } + + void _perform(Map payload) { + _channel?.performAction('receive', params: payload); + } + + Future disconnect() async { + _channel?.unsubscribe(); + _channel = null; + _cable?.disconnect(); + _cable = null; + _sessionId = null; + _ref.read(sessionProvider.notifier).setConnected(false); + } + + void dispose() { + disconnect(); + } + + String encodeQrPayload(Map payload) => jsonEncode(payload); +} diff --git a/mobile/lib/shared/widgets/camera_compact_metrics.dart b/mobile/lib/shared/widgets/camera_compact_metrics.dart new file mode 100644 index 0000000..e36d0c2 --- /dev/null +++ b/mobile/lib/shared/widgets/camera_compact_metrics.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +/// Riga compatta metriche stream (sostituisce le MetricCard ingombranti in camera). +class CameraCompactMetrics extends StatelessWidget { + const CameraCompactMetrics({ + super.key, + required this.networkType, + required this.bitrateMbps, + required this.fps, + this.batteryLevel, + this.viewerCount, + this.light = false, + }); + + final String networkType; + final double bitrateMbps; + final int fps; + final int? batteryLevel; + final int? viewerCount; + final bool light; + + @override + Widget build(BuildContext context) { + final color = light ? Colors.white70 : Theme.of(context).textTheme.bodySmall?.color; + final parts = [ + networkType, + '${bitrateMbps.toStringAsFixed(1)} Mbps', + '$fps fps', + if (batteryLevel != null) '$batteryLevel%', + if (viewerCount != null && viewerCount! > 0) '$viewerCount spett.', + ]; + return Text( + parts.join(' · '), + style: TextStyle(fontSize: 11, color: color, fontWeight: FontWeight.w500), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } +} diff --git a/mobile/lib/shared/widgets/camera_score_controls.dart b/mobile/lib/shared/widgets/camera_score_controls.dart new file mode 100644 index 0000000..3cbce1e --- /dev/null +++ b/mobile/lib/shared/widgets/camera_score_controls.dart @@ -0,0 +1,303 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; +import '../../features/controller_mode/controller_screen.dart'; +import 'score_overlay_bar.dart'; + +/// Controlli punteggio per overlay camera (portrait e landscape). +class CameraScoreControls extends StatelessWidget { + const CameraScoreControls({ + super.key, + required this.homeName, + required this.awayName, + required this.homePoints, + required this.awayPoints, + required this.currentSet, + required this.homeSets, + required this.awaySets, + required this.onHomePoint, + required this.onAwayPoint, + required this.onHomeMinus, + required this.onAwayMinus, + required this.onCloseSet, + this.lastDelta, + this.compact = false, + this.pointsTarget, + }); + + final String homeName; + final String awayName; + final int homePoints; + final int awayPoints; + final int currentSet; + final int homeSets; + final int awaySets; + final VoidCallback onHomePoint; + final VoidCallback onAwayPoint; + final VoidCallback onHomeMinus; + final VoidCallback onAwayMinus; + final VoidCallback onCloseSet; + final int? lastDelta; + final bool compact; + final int? pointsTarget; + + @override + Widget build(BuildContext context) { + if (compact) { + return _LandscapeControls( + homeName: homeName, + awayName: awayName, + homePoints: homePoints, + awayPoints: awayPoints, + currentSet: currentSet, + homeSets: homeSets, + awaySets: awaySets, + lastDelta: lastDelta, + pointsTarget: pointsTarget, + onHomePoint: onHomePoint, + onAwayPoint: onAwayPoint, + onHomeMinus: onHomeMinus, + onAwayMinus: onAwayMinus, + onCloseSet: onCloseSet, + ); + } + + return _PortraitControls( + homeName: homeName, + awayName: awayName, + homePoints: homePoints, + awayPoints: awayPoints, + currentSet: currentSet, + homeSets: homeSets, + awaySets: awaySets, + pointsTarget: pointsTarget, + onHomePoint: onHomePoint, + onAwayPoint: onAwayPoint, + onHomeMinus: onHomeMinus, + onAwayMinus: onAwayMinus, + onCloseSet: onCloseSet, + ); + } +} + +class _PortraitControls extends StatelessWidget { + const _PortraitControls({ + required this.homeName, + required this.awayName, + required this.homePoints, + required this.awayPoints, + required this.currentSet, + required this.homeSets, + required this.awaySets, + required this.onHomePoint, + required this.onAwayPoint, + required this.onHomeMinus, + required this.onAwayMinus, + required this.onCloseSet, + this.pointsTarget, + }); + + final String homeName; + final String awayName; + final int homePoints; + final int awayPoints; + final int currentSet; + final int homeSets; + final int awaySets; + final VoidCallback onHomePoint; + final VoidCallback onAwayPoint; + final VoidCallback onHomeMinus; + final VoidCallback onAwayMinus; + final VoidCallback onCloseSet; + final int? pointsTarget; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + ScoreOverlayBar( + homeName: homeName, + awayName: awayName, + homePoints: homePoints, + awayPoints: awayPoints, + currentSet: currentSet, + homeSets: homeSets, + awaySets: awaySets, + pointsTarget: pointsTarget, + ), + const SizedBox(height: 10), + Row( + children: [ + Expanded( + child: ScoreTapButton(label: '+1 $homeName', onTap: onHomePoint, large: true), + ), + const SizedBox(width: 8), + Expanded( + child: ScoreTapButton(label: '+1 $awayName', onTap: onAwayPoint, large: true), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: homePoints > 0 ? onHomeMinus : null, + child: Text('-1 $homeName', style: const TextStyle(fontSize: 12)), + ), + ), + const SizedBox(width: 8), + Expanded( + child: OutlinedButton( + onPressed: awayPoints > 0 ? onAwayMinus : null, + child: Text('-1 $awayName', style: const TextStyle(fontSize: 12)), + ), + ), + ], + ), + const SizedBox(height: 8), + YellowActionButton(label: 'Chiudi set', onPressed: onCloseSet), + ], + ); + } +} + +class _LandscapeControls extends StatelessWidget { + const _LandscapeControls({ + required this.homeName, + required this.awayName, + required this.homePoints, + required this.awayPoints, + required this.currentSet, + required this.homeSets, + required this.awaySets, + required this.onHomePoint, + required this.onAwayPoint, + required this.onHomeMinus, + required this.onAwayMinus, + required this.onCloseSet, + this.lastDelta, + this.pointsTarget, + }); + + final String homeName; + final String awayName; + final int homePoints; + final int awayPoints; + final int currentSet; + final int homeSets; + final int awaySets; + final VoidCallback onHomePoint; + final VoidCallback onAwayPoint; + final VoidCallback onHomeMinus; + final VoidCallback onAwayMinus; + final VoidCallback onCloseSet; + final int? lastDelta; + final int? pointsTarget; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + ScoreOverlayBar( + homeName: homeName, + awayName: awayName, + homePoints: homePoints, + awayPoints: awayPoints, + currentSet: currentSet, + homeSets: homeSets, + awaySets: awaySets, + compact: true, + lastDelta: lastDelta, + pointsTarget: pointsTarget, + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded(child: _CompactScoreBtn(label: '+1', sub: homeName, onTap: onHomePoint)), + const SizedBox(width: 6), + Expanded(child: _CompactScoreBtn(label: '+1', sub: awayName, onTap: onAwayPoint)), + const SizedBox(width: 6), + Expanded( + child: _CompactScoreBtn( + label: '-1', + sub: homeName, + onTap: homePoints > 0 ? onHomeMinus : null, + muted: true, + ), + ), + const SizedBox(width: 6), + Expanded( + child: _CompactScoreBtn( + label: '-1', + sub: awayName, + onTap: awayPoints > 0 ? onAwayMinus : null, + muted: true, + ), + ), + ], + ), + const SizedBox(height: 6), + YellowActionButton(label: 'Chiudi set', onPressed: onCloseSet), + ], + ); + } +} + +class _CompactScoreBtn extends StatelessWidget { + const _CompactScoreBtn({ + required this.label, + required this.sub, + required this.onTap, + this.muted = false, + }); + + final String label; + final String sub; + final VoidCallback? onTap; + final bool muted; + + @override + Widget build(BuildContext context) { + final enabled = onTap != null; + return Material( + color: muted + ? Colors.black.withValues(alpha: enabled ? 0.65 : 0.35) + : AppTheme.primaryRed.withValues(alpha: enabled ? 0.92 : 0.45), + borderRadius: BorderRadius.circular(10), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(10), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + label, + style: TextStyle( + color: enabled ? Colors.white : Colors.white38, + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ), + Text( + sub, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: enabled ? Colors.white70 : Colors.white30, + fontSize: 9, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/shared/widgets/connected_badge.dart b/mobile/lib/shared/widgets/connected_badge.dart new file mode 100644 index 0000000..931332c --- /dev/null +++ b/mobile/lib/shared/widgets/connected_badge.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; + +/// Dot verde + etichetta COLLEGATO. +class ConnectedBadge extends StatelessWidget { + const ConnectedBadge({ + super.key, + this.connected = true, + this.label, + }); + + final bool connected; + final String? label; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final color = connected ? AppTheme.successGreen : AppTheme.textSecondary; + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 6), + Text( + label ?? (connected ? 'COLLEGATO' : 'DISCONNESSO'), + style: theme.textTheme.labelLarge?.copyWith( + color: color, + fontSize: 12, + ), + ), + ], + ); + } +} diff --git a/mobile/lib/shared/widgets/destructive_cta.dart b/mobile/lib/shared/widgets/destructive_cta.dart new file mode 100644 index 0000000..24f5f66 --- /dev/null +++ b/mobile/lib/shared/widgets/destructive_cta.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; + +/// CTA distruttiva nera con icona rossa (INTERROMPI, FERMA TRASMISSIONE). +class DestructiveCta extends StatelessWidget { + const DestructiveCta({ + super.key, + required this.label, + required this.onPressed, + this.loading = false, + this.compact = false, + }); + + final String label; + final VoidCallback? onPressed; + final bool loading; + final bool compact; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return SizedBox( + width: compact ? null : double.infinity, + height: compact ? 44 : 52, + child: OutlinedButton( + onPressed: loading ? null : onPressed, + style: OutlinedButton.styleFrom( + backgroundColor: Colors.black, + foregroundColor: AppTheme.primaryRed, + side: BorderSide( + color: AppTheme.primaryRed.withValues(alpha: 0.6), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(compact ? 10 : 12), + ), + ), + child: loading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppTheme.primaryRed, + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: compact ? MainAxisSize.min : MainAxisSize.max, + children: [ + const Icon(Icons.stop_circle_outlined, size: 20), + const SizedBox(width: 8), + Text( + label, + style: theme.textTheme.labelLarge?.copyWith( + color: AppTheme.primaryRed, + fontSize: compact ? 12 : 14, + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/shared/widgets/end_stream_dialog.dart b/mobile/lib/shared/widgets/end_stream_dialog.dart new file mode 100644 index 0000000..1c7b18b --- /dev/null +++ b/mobile/lib/shared/widgets/end_stream_dialog.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; + +/// Conferma chiusura definitiva della diretta (non riprendibile). +Future confirmEndStream(BuildContext context) async { + final result = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: AppTheme.surface, + title: const Text('Chiudi diretta'), + content: const Text( + 'La diretta verrà chiusa definitivamente. ' + 'Gli spettatori vedranno che lo stream è terminato e non potrai riprendere questa sessione.\n\n' + 'Per una pausa temporanea usa «Interrompi».', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Annulla'), + ), + FilledButton( + onPressed: () => Navigator.pop(ctx, true), + style: FilledButton.styleFrom(backgroundColor: AppTheme.primaryRed), + child: const Text('Chiudi definitivamente'), + ), + ], + ), + ); + return result == true; +} diff --git a/mobile/lib/shared/widgets/live_badge.dart b/mobile/lib/shared/widgets/live_badge.dart new file mode 100644 index 0000000..b13f278 --- /dev/null +++ b/mobile/lib/shared/widgets/live_badge.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; + +/// Pill rossa con dot pulsante e timer diretta. +class LiveBadge extends StatelessWidget { + const LiveBadge({ + super.key, + required this.elapsed, + this.compact = false, + }); + + final Duration elapsed; + final bool compact; + + String _format(Duration d) { + final h = d.inHours; + final m = d.inMinutes.remainder(60).toString().padLeft(2, '0'); + final s = d.inSeconds.remainder(60).toString().padLeft(2, '0'); + if (h > 0) return '$h:$m:$s'; + return '$m:$s'; + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + padding: EdgeInsets.symmetric( + horizontal: compact ? 10 : 14, + vertical: compact ? 4 : 6, + ), + decoration: BoxDecoration( + color: AppTheme.primaryRed, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: compact ? 6 : 8, + height: compact ? 6 : 8, + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + ), + SizedBox(width: compact ? 6 : 8), + Text( + compact ? 'LIVE ${_format(elapsed)}' : 'IN DIRETTA ${_format(elapsed)}', + style: theme.textTheme.labelLarge?.copyWith( + color: Colors.white, + fontSize: compact ? 11 : 13, + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/shared/widgets/match_live_wordmark.dart b/mobile/lib/shared/widgets/match_live_wordmark.dart new file mode 100644 index 0000000..ce76a26 --- /dev/null +++ b/mobile/lib/shared/widgets/match_live_wordmark.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; + +/// Wordmark orizzontale: MATCH + pill LIVE + TV. +class MatchLiveWordmark extends StatelessWidget { + const MatchLiveWordmark({ + super.key, + this.compact = false, + this.showSlogan = false, + }); + + final bool compact; + final bool showSlogan; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final matchStyle = theme.textTheme.displaySmall?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w900, + letterSpacing: 1, + ); + final tvStyle = theme.textTheme.displaySmall?.copyWith( + color: AppTheme.textSecondary, + fontWeight: FontWeight.w900, + letterSpacing: 1, + ); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('MATCH', style: matchStyle), + const SizedBox(width: 8), + Container( + padding: EdgeInsets.symmetric( + horizontal: compact ? 8 : 12, + vertical: compact ? 2 : 4, + ), + decoration: BoxDecoration( + color: AppTheme.primaryRed, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + 'LIVE', + style: theme.textTheme.labelLarge?.copyWith( + color: Colors.white, + fontSize: compact ? 14 : 18, + ), + ), + ), + const SizedBox(width: 8), + Text('TV', style: tvStyle), + ], + ), + if (showSlogan) ...[ + const SizedBox(height: 12), + Text( + 'LO STREAMING CHE NON MUORE', + style: theme.textTheme.labelLarge?.copyWith( + color: AppTheme.textSecondary, + fontSize: 12, + letterSpacing: 2, + ), + ), + ], + ], + ); + } +} diff --git a/mobile/lib/shared/widgets/metric_card.dart b/mobile/lib/shared/widgets/metric_card.dart new file mode 100644 index 0000000..613bd0c --- /dev/null +++ b/mobile/lib/shared/widgets/metric_card.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; + +/// Card metrica singola (segnale, Mbps, fps). +class MetricCard extends StatelessWidget { + const MetricCard({ + super.key, + required this.label, + required this.value, + this.icon, + this.highlight = false, + this.expand = true, + }); + + final String label; + final String value; + final IconData? icon; + final bool highlight; + final bool expand; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final card = Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + decoration: BoxDecoration( + color: AppTheme.surfaceElevated, + borderRadius: BorderRadius.circular(10), + border: highlight + ? Border.all(color: AppTheme.successGreen.withValues(alpha: 0.5)) + : null, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) ...[ + Icon(icon, size: 18, color: AppTheme.textSecondary), + const SizedBox(height: 4), + ], + Text( + value, + style: theme.textTheme.titleLarge?.copyWith( + color: highlight ? AppTheme.successGreen : Colors.white, + fontWeight: FontWeight.w900, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 2), + Text( + label, + style: theme.textTheme.bodyMedium?.copyWith(fontSize: 11), + textAlign: TextAlign.center, + ), + ], + ), + ); + + if (!expand) { + return SizedBox(width: 72, child: card); + } + + return Expanded(child: card); + } +} diff --git a/mobile/lib/shared/widgets/primary_cta.dart b/mobile/lib/shared/widgets/primary_cta.dart new file mode 100644 index 0000000..ad916fd --- /dev/null +++ b/mobile/lib/shared/widgets/primary_cta.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; + +/// CTA primaria rossa full-width (AVANTI, INIZIA). +class PrimaryCta extends StatelessWidget { + const PrimaryCta({ + super.key, + required this.label, + required this.onPressed, + this.loading = false, + this.icon, + }); + + final String label; + final VoidCallback? onPressed; + final bool loading; + final IconData? icon; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return SizedBox( + width: double.infinity, + height: 52, + child: ElevatedButton( + onPressed: loading ? null : onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryRed, + foregroundColor: Colors.white, + disabledBackgroundColor: AppTheme.primaryRed.withValues(alpha: 0.5), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + ), + child: loading + ? const SizedBox( + width: 22, + height: 22, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + label, + style: theme.textTheme.titleMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.w800, + letterSpacing: 1, + ), + ), + if (icon != null) ...[ + const SizedBox(width: 8), + Icon(icon, size: 20), + ], + ], + ), + ), + ); + } +} diff --git a/mobile/lib/shared/widgets/score_outcome_dialogs.dart b/mobile/lib/shared/widgets/score_outcome_dialogs.dart new file mode 100644 index 0000000..e1de669 --- /dev/null +++ b/mobile/lib/shared/widgets/score_outcome_dialogs.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; +import '../scoring/match_scoring_rules.dart'; + +Future showSetWonDialog( + BuildContext context, { + required ScoringSide winner, + required String homeName, + required String awayName, + required int homePoints, + required int awayPoints, +}) async { + final winnerName = winner == ScoringSide.home ? homeName : awayName; + final result = await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog( + backgroundColor: AppTheme.surface, + title: const Text('Set concluso'), + content: Text( + '$winnerName vince il set $homePoints-$awayPoints.\n\nChiudere il set e passare al successivo?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Continua a segnare'), + ), + FilledButton( + onPressed: () => Navigator.pop(ctx, true), + style: FilledButton.styleFrom(backgroundColor: AppTheme.accentYellow), + child: const Text('Chiudi set', style: TextStyle(color: Colors.black)), + ), + ], + ), + ); + return result == true; +} + +Future showCloseSetAnywayDialog(BuildContext context) async { + final result = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: AppTheme.surface, + title: const Text('Chiudi set'), + content: const Text( + 'Il punteggio non soddisfa ancora le regole del torneo. Chiudere il set comunque?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Annulla'), + ), + FilledButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text('Chiudi comunque'), + ), + ], + ), + ); + return result == true; +} + +Future showMatchWonDialog( + BuildContext context, { + required ScoringSide winner, + required String homeName, + required String awayName, + required int homeSets, + required int awaySets, +}) async { + final winnerName = winner == ScoringSide.home ? homeName : awayName; + final result = await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog( + backgroundColor: AppTheme.surface, + title: const Text('Partita terminata'), + content: Text( + '$winnerName vince la partita ($homeSets-$awaySets set).\n\nChiudere definitivamente la diretta?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('Continua in onda'), + ), + FilledButton( + onPressed: () => Navigator.pop(ctx, true), + style: FilledButton.styleFrom(backgroundColor: AppTheme.primaryRed), + child: const Text('Chiudi diretta'), + ), + ], + ), + ); + return result == true; +} diff --git a/mobile/lib/shared/widgets/score_overlay_bar.dart b/mobile/lib/shared/widgets/score_overlay_bar.dart new file mode 100644 index 0000000..ac9a5c6 --- /dev/null +++ b/mobile/lib/shared/widgets/score_overlay_bar.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; + +import '../../app/theme.dart'; + +/// Barra overlay punteggio: SET n | HOME x - AWAY y. +class ScoreOverlayBar extends StatelessWidget { + const ScoreOverlayBar({ + super.key, + required this.homeName, + required this.awayName, + required this.homePoints, + required this.awayPoints, + required this.currentSet, + this.homeSets, + this.awaySets, + this.compact = false, + this.lastDelta, + this.pointsTarget, + }); + + final String homeName; + final String awayName; + final int homePoints; + final int awayPoints; + final int currentSet; + final int? homeSets; + final int? awaySets; + final bool compact; + final int? lastDelta; + final int? pointsTarget; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final bg = Colors.black.withValues(alpha: 0.72); + + final bar = Container( + padding: EdgeInsets.symmetric( + horizontal: compact ? 10 : 14, + vertical: compact ? 6 : 8, + ), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(compact ? 8 : 10), + border: Border.all(color: Colors.white12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + pointsTarget != null ? 'SET $currentSet → $pointsTarget' : 'SET $currentSet', + style: theme.textTheme.labelLarge?.copyWith( + color: AppTheme.accentYellow, + fontSize: compact ? 11 : 13, + ), + ), + Container( + width: 1, + height: compact ? 14 : 18, + margin: const EdgeInsets.symmetric(horizontal: 10), + color: Colors.white24, + ), + Text( + homeName.toUpperCase(), + style: theme.textTheme.labelLarge?.copyWith( + fontSize: compact ? 11 : 13, + ), + ), + const SizedBox(width: 6), + Text( + '$homePoints', + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w900, + fontSize: compact ? 16 : 20, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + '-', + style: theme.textTheme.titleMedium?.copyWith( + color: AppTheme.textSecondary, + ), + ), + ), + Text( + '$awayPoints', + style: theme.textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w900, + fontSize: compact ? 16 : 20, + ), + ), + const SizedBox(width: 6), + Text( + awayName.toUpperCase(), + style: theme.textTheme.labelLarge?.copyWith( + fontSize: compact ? 11 : 13, + ), + ), + if (homeSets != null && awaySets != null) ...[ + const SizedBox(width: 10), + Text( + '($homeSets-$awaySets)', + style: theme.textTheme.bodyMedium?.copyWith(fontSize: 11), + ), + ], + if (lastDelta != null && lastDelta != 0) ...[ + const SizedBox(width: 8), + Text( + lastDelta! > 0 ? '+$lastDelta' : '$lastDelta', + style: theme.textTheme.labelLarge?.copyWith( + color: lastDelta! > 0 ? AppTheme.successGreen : AppTheme.primaryRed, + fontSize: compact ? 11 : 13, + ), + ), + ], + ], + ), + ); + + return LayoutBuilder( + builder: (context, constraints) { + if (!constraints.hasBoundedWidth) { + return bar; + } + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: bar, + ); + }, + ); + } +} diff --git a/mobile/lib/shared/widgets/screen_insets.dart b/mobile/lib/shared/widgets/screen_insets.dart new file mode 100644 index 0000000..2d5e283 --- /dev/null +++ b/mobile/lib/shared/widgets/screen_insets.dart @@ -0,0 +1,84 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +/// Padding di sistema (status bar, notch, barra navigazione) con minimo configurabile. +class ScreenInsets { + ScreenInsets._(); + + static EdgeInsets of(BuildContext context, {double minimum = 8}) { + final mq = MediaQuery.of(context); + final padding = mq.padding; + final view = mq.viewPadding; + return EdgeInsets.only( + left: math.max(math.max(padding.left, view.left), minimum), + top: math.max(math.max(padding.top, view.top), minimum), + right: math.max(math.max(padding.right, view.right), minimum), + bottom: math.max(math.max(padding.bottom, view.bottom), minimum), + ); + } + + /// Padding per contenuti scrollabili (login, wizard, elenco partite). + /// Overlay camera: in landscape su Android la barra nav può stare a sinistra. + static EdgeInsets cameraOverlay(BuildContext context, {double minimum = 12}) { + final base = of(context, minimum: minimum); + final landscape = + MediaQuery.orientationOf(context) == Orientation.landscape; + if (landscape && base.left < 48) { + return base.copyWith(left: 48); + } + return base; + } + + static EdgeInsets contentPadding( + BuildContext context, { + double horizontal = 20, + double vertical = 16, + double minimum = 8, + }) { + final inset = of(context, minimum: minimum); + return EdgeInsets.fromLTRB( + math.max(inset.left, horizontal), + math.max(inset.top, vertical), + math.max(inset.right, horizontal), + math.max(inset.bottom, vertical), + ); + } +} + +/// SafeArea con padding minimo su tutti i lati (gestisce edge-to-edge Android). +class AppSafeArea extends StatelessWidget { + const AppSafeArea({ + super.key, + required this.child, + this.minimum = 8, + this.top = true, + this.bottom = true, + this.left = true, + this.right = true, + }); + + final Widget child; + final double minimum; + final bool top; + final bool bottom; + final bool left; + final bool right; + + @override + Widget build(BuildContext context) { + return SafeArea( + minimum: EdgeInsets.only( + left: left ? minimum : 0, + top: top ? minimum : 0, + right: right ? minimum : 0, + bottom: bottom ? minimum : 0, + ), + top: top, + bottom: bottom, + left: left, + right: right, + child: child, + ); + } +} diff --git a/mobile/linux/.gitignore b/mobile/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/mobile/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/mobile/linux/CMakeLists.txt b/mobile/linux/CMakeLists.txt new file mode 100644 index 0000000..3cb0f5d --- /dev/null +++ b/mobile/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "match_live_tv") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.matchlivetv.match_live_tv") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/mobile/linux/flutter/CMakeLists.txt b/mobile/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/mobile/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/mobile/linux/flutter/generated_plugin_registrant.cc b/mobile/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/mobile/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/mobile/linux/flutter/generated_plugin_registrant.h b/mobile/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/mobile/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/mobile/linux/flutter/generated_plugins.cmake b/mobile/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..df8d2f7 --- /dev/null +++ b/mobile/linux/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/mobile/linux/runner/CMakeLists.txt b/mobile/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/mobile/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/mobile/linux/runner/main.cc b/mobile/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/mobile/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/mobile/linux/runner/my_application.cc b/mobile/linux/runner/my_application.cc new file mode 100644 index 0000000..cc32559 --- /dev/null +++ b/mobile/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "match_live_tv"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "match_live_tv"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/mobile/linux/runner/my_application.h b/mobile/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/mobile/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/mobile/macos/.gitignore b/mobile/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/mobile/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/mobile/macos/Flutter/Flutter-Debug.xcconfig b/mobile/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/mobile/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/mobile/macos/Flutter/Flutter-Release.xcconfig b/mobile/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/mobile/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..6d56bd2 --- /dev/null +++ b/mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,24 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import battery_plus +import connectivity_plus +import mobile_scanner +import package_info_plus +import shared_preferences_foundation +import url_launcher_macos +import wakelock_plus + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) + ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) + MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) +} diff --git a/mobile/macos/Runner.xcodeproj/project.pbxproj b/mobile/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b82ced3 --- /dev/null +++ b/mobile/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,729 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* match_live_tv.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "match_live_tv.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* match_live_tv.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* match_live_tv.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.matchLiveTv.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/match_live_tv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/match_live_tv"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.matchLiveTv.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/match_live_tv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/match_live_tv"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.matchLiveTv.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/match_live_tv.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/match_live_tv"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/mobile/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..34d04f3 --- /dev/null +++ b/mobile/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/macos/Runner.xcworkspace/contents.xcworkspacedata b/mobile/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/mobile/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/mobile/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile/macos/Runner/AppDelegate.swift b/mobile/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/mobile/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 diff --git a/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 diff --git a/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 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 literal 0 HcmV?d00001 diff --git a/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/mobile/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/macos/Runner/Configs/AppInfo.xcconfig b/mobile/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..e97a26c --- /dev/null +++ b/mobile/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = match_live_tv + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.matchlivetv.matchLiveTv + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.matchlivetv. All rights reserved. diff --git a/mobile/macos/Runner/Configs/Debug.xcconfig b/mobile/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/mobile/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/mobile/macos/Runner/Configs/Release.xcconfig b/mobile/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/mobile/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/mobile/macos/Runner/Configs/Warnings.xcconfig b/mobile/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/mobile/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/mobile/macos/Runner/DebugProfile.entitlements b/mobile/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/mobile/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/mobile/macos/Runner/Info.plist b/mobile/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/mobile/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/mobile/macos/Runner/MainFlutterWindow.swift b/mobile/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/mobile/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/mobile/macos/Runner/Release.entitlements b/mobile/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/mobile/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/mobile/macos/RunnerTests/RunnerTests.swift b/mobile/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/mobile/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +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. + } + +} diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml new file mode 100644 index 0000000..81bc94c --- /dev/null +++ b/mobile/pubspec.yaml @@ -0,0 +1,34 @@ +name: match_live_tv +description: Match Live TV - Lo streaming che non muore +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ^3.12.0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.8 + flutter_riverpod: ^2.6.1 + go_router: ^14.8.1 + dio: ^5.8.0+1 + x_action_cable_v2: ^1.0.0 + connectivity_plus: ^6.1.4 + battery_plus: ^6.2.1 + wakelock_plus: ^1.3.2 + permission_handler: ^11.4.0 + google_fonts: ^6.2.1 + intl: ^0.20.2 + qr_flutter: ^4.1.0 + mobile_scanner: ^6.0.10 + shared_preferences: ^2.5.3 + url_launcher: ^6.3.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + uses-material-design: true diff --git a/mobile/test/widget_test.dart b/mobile/test/widget_test.dart new file mode 100644 index 0000000..6e46190 --- /dev/null +++ b/mobile/test/widget_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:match_live_tv/shared/widgets/live_badge.dart'; +import 'package:match_live_tv/shared/widgets/match_live_wordmark.dart'; + +void main() { + testWidgets('MatchLiveWordmark renders', (tester) async { + await tester.pumpWidget( + const MaterialApp(home: Scaffold(body: MatchLiveWordmark())), + ); + expect(find.text('MATCH'), findsOneWidget); + expect(find.text('LIVE'), findsOneWidget); + }); + + testWidgets('LiveBadge shows timer', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold(body: LiveBadge(elapsed: Duration(minutes: 45, seconds: 35))), + ), + ); + expect(find.textContaining('IN DIRETTA'), findsOneWidget); + }); +} diff --git a/mobile/web/favicon.png b/mobile/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/mobile/web/icons/Icon-192.png b/mobile/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 zcmZ`-2T+sGz6~)*FVZ`aW+(v>MIm&M-g^@e2u-B-DoB?qO+b1Tq<5uCCv>ESfRum& zp%X;f!~1{tzL__3=gjVJ=j=J>+nMj%ncXj1Q(b|Ckbw{Y0FWpt%4y%$uD=Z*c-x~o zE;IoE;xa#7Ll5nj-e4CuXB&G*IM~D21rCP$*xLXAK8rIMCSHuSu%bL&S3)8YI~vyp@KBu9Ph7R_pvKQ@xv>NQ`dZp(u{Z8K3yOB zn7-AR+d2JkW)KiGx0hosml;+eCXp6+w%@STjFY*CJ?udJ64&{BCbuebcuH;}(($@@ znNlgBA@ZXB)mcl9nbX#F!f_5Z=W>0kh|UVWnf!At4V*LQP%*gPdCXd6P@J4Td;!Ur z<2ZLmwr(NG`u#gDEMP19UcSzRTL@HsK+PnIXbVBT@oHm53DZr?~V(0{rsalAfwgo zEh=GviaqkF;}F_5-yA!1u3!gxaR&Mj)hLuj5Q-N-@Lra{%<4ONja8pycD90&>yMB` zchhd>0CsH`^|&TstH-8+R`CfoWqmTTF_0?zDOY`E`b)cVi!$4xA@oO;SyOjJyP^_j zx^@Gdf+w|FW@DMdOi8=4+LJl$#@R&&=UM`)G!y%6ZzQLoSL%*KE8IO0~&5XYR9 z&N)?goEiWA(YoRfT{06&D6Yuu@Qt&XVbuW@COb;>SP9~aRc+z`m`80pB2o%`#{xD@ zI3RAlukL5L>px6b?QW1Ac_0>ew%NM!XB2(H+1Y3AJC?C?O`GGs`331Nd4ZvG~bMo{lh~GeL zSL|tT*fF-HXxXYtfu5z+T5Mx9OdP7J4g%@oeC2FaWO1D{=NvL|DNZ}GO?O3`+H*SI z=grGv=7dL{+oY0eJFGO!Qe(e2F?CHW(i!!XkGo2tUvsQ)I9ev`H&=;`N%Z{L zO?vV%rDv$y(@1Yj@xfr7Kzr<~0{^T8wM80xf7IGQF_S-2c0)0D6b0~yD7BsCy+(zL z#N~%&e4iAwi4F$&dI7x6cE|B{f@lY5epaDh=2-(4N05VO~A zQT3hanGy_&p+7Fb^I#ewGsjyCEUmSCaP6JDB*=_()FgQ(-pZ28-{qx~2foO4%pM9e z*_63RT8XjgiaWY|*xydf;8MKLd{HnfZ2kM%iq}fstImB-K6A79B~YoPVa@tYN@T_$ zea+9)<%?=Fl!kd(Y!G(-o}ko28hg2!MR-o5BEa_72uj7Mrc&{lRh3u2%Y=Xk9^-qa zBPWaD=2qcuJ&@Tf6ue&)4_V*45=zWk@Z}Q?f5)*z)-+E|-yC4fs5CE6L_PH3=zI8p z*Z3!it{1e5_^(sF*v=0{`U9C741&lub89gdhKp|Y8CeC{_{wYK-LSbp{h)b~9^j!s z7e?Y{Z3pZv0J)(VL=g>l;<}xk=T*O5YR|hg0eg4u98f2IrA-MY+StQIuK-(*J6TRR z|IM(%uI~?`wsfyO6Tgmsy1b3a)j6M&-jgUjVg+mP*oTKdHg?5E`!r`7AE_#?Fc)&a z08KCq>Gc=ne{PCbRvs6gVW|tKdcE1#7C4e`M|j$C5EYZ~Y=jUtc zj`+?p4ba3uy7><7wIokM79jPza``{Lx0)zGWg;FW1^NKY+GpEi=rHJ+fVRGfXO zPHV52k?jxei_!YYAw1HIz}y8ZMwdZqU%ESwMn7~t zdI5%B;U7RF=jzRz^NuY9nM)&<%M>x>0(e$GpU9th%rHiZsIT>_qp%V~ILlyt^V`=d z!1+DX@ah?RnB$X!0xpTA0}lN@9V-ePx>wQ?-xrJr^qDlw?#O(RsXeAvM%}rg0NT#t z!CsT;-vB=B87ShG`GwO;OEbeL;a}LIu=&@9cb~Rsx(ZPNQ!NT7H{@j0e(DiLea>QD zPmpe90gEKHEZ8oQ@6%E7k-Ptn#z)b9NbD@_GTxEhbS+}Bb74WUaRy{w;E|MgDAvHw zL)ycgM7mB?XVh^OzbC?LKFMotw3r@i&VdUV%^Efdib)3@soX%vWCbnOyt@Y4swW925@bt45y0HY3YI~BnnzZYrinFy;L?2D3BAL`UQ zEj))+f>H7~g8*VuWQ83EtGcx`hun$QvuurSMg3l4IP8Fe`#C|N6mbYJ=n;+}EQm;< z!!N=5j1aAr_uEnnzrEV%_E|JpTb#1p1*}5!Ce!R@d$EtMR~%9# zd;h8=QGT)KMW2IKu_fA_>p_und#-;Q)p%%l0XZOXQicfX8M~7?8}@U^ihu;mizj)t zgV7wk%n-UOb z#!P5q?Ex+*Kx@*p`o$q8FWL*E^$&1*!gpv?Za$YO~{BHeGY*5%4HXUKa_A~~^d z=E*gf6&+LFF^`j4$T~dR)%{I)T?>@Ma?D!gi9I^HqvjPc3-v~=qpX1Mne@*rzT&Xw zQ9DXsSV@PqpEJO-g4A&L{F&;K6W60D!_vs?Vx!?w27XbEuJJP&);)^+VF1nHqHBWu z^>kI$M9yfOY8~|hZ9WB!q-9u&mKhEcRjlf2nm_@s;0D#c|@ED7NZE% zzR;>P5B{o4fzlfsn3CkBK&`OSb-YNrqx@N#4CK!>bQ(V(D#9|l!e9(%sz~PYk@8zt zPN9oK78&-IL_F zhsk1$6p;GqFbtB^ZHHP+cjMvA0(LqlskbdYE_rda>gvQLTiqOQ1~*7lg%z*&p`Ry& zRcG^DbbPj_jOKHTr8uk^15Boj6>hA2S-QY(W-6!FIq8h$<>MI>PYYRenQDBamO#Fv zAH5&ImqKBDn0v5kb|8i0wFhUBJTpT!rB-`zK)^SNnRmLraZcPYK7b{I@+}wXVdW-{Ps17qdRA3JatEd?rPV z4@}(DAMf5EqXCr4-B+~H1P#;t@O}B)tIJ(W6$LrK&0plTmnPpb1TKn3?f?Kk``?D+ zQ!MFqOX7JbsXfQrz`-M@hq7xlfNz;_B{^wbpG8des56x(Q)H)5eLeDwCrVR}hzr~= zM{yXR6IM?kXxauLza#@#u?Y|o;904HCqF<8yT~~c-xyRc0-vxofnxG^(x%>bj5r}N zyFT+xnn-?B`ohA>{+ZZQem=*Xpqz{=j8i2TAC#x-m;;mo{{sLB_z(UoAqD=A#*juZ zCv=J~i*O8;F}A^Wf#+zx;~3B{57xtoxC&j^ie^?**T`WT2OPRtC`xj~+3Kprn=rVM zVJ|h5ux%S{dO}!mq93}P+h36mZ5aZg1-?vhL$ke1d52qIiXSE(llCr5i=QUS?LIjc zV$4q=-)aaR4wsrQv}^shL5u%6;`uiSEs<1nG^?$kl$^6DL z43CjY`M*p}ew}}3rXc7Xck@k41jx}c;NgEIhKZ*jsBRZUP-x2cm;F1<5$jefl|ppO zmZd%%?gMJ^g9=RZ^#8Mf5aWNVhjAS^|DQO+q$)oeob_&ZLFL(zur$)); zU19yRm)z<4&4-M}7!9+^Wl}Uk?`S$#V2%pQ*SIH5KI-mn%i;Z7-)m$mN9CnI$G7?# zo`zVrUwoSL&_dJ92YhX5TKqaRkfPgC4=Q&=K+;_aDs&OU0&{WFH}kKX6uNQC6%oUH z2DZa1s3%Vtk|bglbxep-w)PbFG!J17`<$g8lVhqD2w;Z0zGsh-r zxZ13G$G<48leNqR!DCVt9)@}(zMI5w6Wo=N zpP1*3DI;~h2WDWgcKn*f!+ORD)f$DZFwgKBafEZmeXQMAsq9sxP9A)7zOYnkHT9JU zRA`umgmP9d6=PHmFIgx=0$(sjb>+0CHG)K@cPG{IxaJ&Ueo8)0RWgV9+gO7+Bl1(F z7!BslJ2MP*PWJ;x)QXbR$6jEr5q3 z(3}F@YO_P1NyTdEXRLU6fp?9V2-S=E+YaeLL{Y)W%6`k7$(EW8EZSA*(+;e5@jgD^I zaJQ2|oCM1n!A&-8`;#RDcZyk*+RPkn_r8?Ak@agHiSp*qFNX)&i21HE?yuZ;-C<3C zwJGd1lx5UzViP7sZJ&|LqH*mryb}y|%AOw+v)yc`qM)03qyyrqhX?ub`Cjwx2PrR! z)_z>5*!*$x1=Qa-0uE7jy0z`>|Ni#X+uV|%_81F7)b+nf%iz=`fF4g5UfHS_?PHbr zB;0$bK@=di?f`dS(j{l3-tSCfp~zUuva+=EWxJcRfp(<$@vd(GigM&~vaYZ0c#BTs z3ijkxMl=vw5AS&DcXQ%eeKt!uKvh2l3W?&3=dBHU=Gz?O!40S&&~ei2vg**c$o;i89~6DVns zG>9a*`k5)NI9|?W!@9>rzJ;9EJ=YlJTx1r1BA?H`LWijk(rTax9(OAu;q4_wTj-yj z1%W4GW&K4T=uEGb+E!>W0SD_C0RR91 literal 0 HcmV?d00001 diff --git a/mobile/web/icons/Icon-512.png b/mobile/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/mobile/web/icons/Icon-maskable-192.png b/mobile/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/mobile/web/icons/Icon-maskable-512.png b/mobile/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/mobile/web/index.html b/mobile/web/index.html new file mode 100644 index 0000000..be80a12 --- /dev/null +++ b/mobile/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + match_live_tv + + + + + + + diff --git a/mobile/web/manifest.json b/mobile/web/manifest.json new file mode 100644 index 0000000..49cc9de --- /dev/null +++ b/mobile/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "match_live_tv", + "short_name": "match_live_tv", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/mobile/windows/.gitignore b/mobile/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/mobile/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/mobile/windows/CMakeLists.txt b/mobile/windows/CMakeLists.txt new file mode 100644 index 0000000..b0419b6 --- /dev/null +++ b/mobile/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(match_live_tv LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "match_live_tv") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/mobile/windows/flutter/CMakeLists.txt b/mobile/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/mobile/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/mobile/windows/flutter/generated_plugin_registrant.cc b/mobile/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..bfc9598 --- /dev/null +++ b/mobile/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,23 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + BatteryPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin")); + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/mobile/windows/flutter/generated_plugin_registrant.h b/mobile/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/mobile/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/mobile/windows/flutter/generated_plugins.cmake b/mobile/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..d60dff0 --- /dev/null +++ b/mobile/windows/flutter/generated_plugins.cmake @@ -0,0 +1,28 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + battery_plus + connectivity_plus + permission_handler_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/mobile/windows/runner/CMakeLists.txt b/mobile/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/mobile/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/mobile/windows/runner/Runner.rc b/mobile/windows/runner/Runner.rc new file mode 100644 index 0000000..960b12c --- /dev/null +++ b/mobile/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.matchlivetv" "\0" + VALUE "FileDescription", "match_live_tv" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "match_live_tv" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.matchlivetv. All rights reserved." "\0" + VALUE "OriginalFilename", "match_live_tv.exe" "\0" + VALUE "ProductName", "match_live_tv" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/mobile/windows/runner/flutter_window.cpp b/mobile/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/mobile/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/mobile/windows/runner/flutter_window.h b/mobile/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/mobile/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/mobile/windows/runner/main.cpp b/mobile/windows/runner/main.cpp new file mode 100644 index 0000000..6373396 --- /dev/null +++ b/mobile/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"match_live_tv", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/mobile/windows/runner/resource.h b/mobile/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/mobile/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/mobile/windows/runner/resources/app_icon.ico b/mobile/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/mobile/windows/runner/runner.exe.manifest b/mobile/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/mobile/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/mobile/windows/runner/utils.cpp b/mobile/windows/runner/utils.cpp new file mode 100644 index 0000000..3cb7146 --- /dev/null +++ b/mobile/windows/runner/utils.cpp @@ -0,0 +1,69 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + // First, find the length of the string with a safe upper bound (CWE-126). + // UNICODE_STRING_MAX_CHARS (32767) is the maximum length of a UNICODE_STRING. + int input_length = static_cast(wcsnlen(utf16_string, UNICODE_STRING_MAX_CHARS)); + // Now use that bounded length to determine the required buffer size. + // When an explicit length is passed, WideCharToMultiByte does not include + // the null terminator in its returned size. + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || static_cast(target_length) > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/mobile/windows/runner/utils.h b/mobile/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/mobile/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/mobile/windows/runner/win32_window.cpp b/mobile/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/mobile/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/mobile/windows/runner/win32_window.h b/mobile/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/mobile/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/scripts/deploy/post_bootstrap_deploy.sh b/scripts/deploy/post_bootstrap_deploy.sh new file mode 100644 index 0000000..013642e --- /dev/null +++ b/scripts/deploy/post_bootstrap_deploy.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Dopo server_bootstrap.sh: avvia lo stack produzione +set -euo pipefail + +DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/matchlivetv}" +if [[ -d /opt/matchlivetv ]]; then + DEPLOY_ROOT="/opt/matchlivetv" +fi + +cd "$DEPLOY_ROOT/infra" + +if [[ ! -f .env ]]; then + echo "Manca infra/.env — copia da .env.production.example e compila i secret" + exit 1 +fi + +docker compose -f docker-compose.prod.yml --env-file .env up -d --build + +echo "Attendo healthcheck Rails..." +sleep 30 +curl -sf "http://127.0.0.1:3000/up" && echo " OK" || echo " FAIL — vedi: docker compose -f docker-compose.prod.yml logs rails" + +echo "Seed demo (opzionale, una tantum):" +echo " docker compose -f docker-compose.prod.yml exec rails bundle exec rails db:seed" diff --git a/scripts/deploy/server_bootstrap.sh b/scripts/deploy/server_bootstrap.sh new file mode 100755 index 0000000..d8bbce2 --- /dev/null +++ b/scripts/deploy/server_bootstrap.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Eseguire UNA VOLTA sul server con sudo (da console SSH o: sudo bash server_bootstrap.sh) +set -euo pipefail + +if [[ "${EUID}" -ne 0 ]]; then + if sudo -n true 2>/dev/null; then + exec sudo -n bash "$0" "$@" + else + echo "ERRORE: eseguire come root o con sudo senza password:" + echo " sudo bash $0" + echo "" + echo "Per abilitare NOPASSWD per eminux (opzionale):" + echo " echo 'eminux ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/eminux-nopasswd" + exit 1 + fi +fi + +export DEBIAN_FRONTEND=noninteractive + +apt-get update -qq +apt-get install -y -qq ca-certificates curl git ufw rsync + +if ! command -v docker >/dev/null 2>&1; then + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + + . /etc/os-release + CODENAME="${VERSION_CODENAME:-bookworm}" + if [[ "$CODENAME" == "trixie" ]]; then + CODENAME="bookworm" + fi + + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian ${CODENAME} stable" \ + > /etc/apt/sources.list.d/docker.list + + apt-get update -qq + apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +fi + +usermod -aG docker eminux 2>/dev/null || true +systemctl enable --now docker + +mkdir -p /opt/matchlivetv +chown -R eminux:eminux /opt/matchlivetv + +# UFW base +ufw --force reset || true +ufw default deny incoming +ufw default allow outgoing +ufw allow 22/tcp comment 'SSH' +ufw allow 1935/tcp comment 'RTMP MediaMTX' +ufw allow 8888/tcp comment 'HLS MediaMTX optional' +ufw allow from 192.168.0.0/16 to any port 3000 proto tcp comment 'Rails LAN NPM' +ufw --force enable + +docker --version +docker compose version +echo "Bootstrap completato. Riavvia sessione SSH di eminux per gruppo docker." diff --git a/scripts/deploy/sync_to_server.sh b/scripts/deploy/sync_to_server.sh new file mode 100755 index 0000000..5b72f83 --- /dev/null +++ b/scripts/deploy/sync_to_server.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Sincronizza il monorepo sul server di produzione (pre-Gitea) +set -euo pipefail + +SERVER="${DEPLOY_SERVER:-eminux@192.168.1.146}" +DEST="${DEPLOY_PATH:-/opt/matchlivetv}" +SRC="$(cd "$(dirname "$0")/../.." && pwd)" + +echo "Sync $SRC -> $SERVER:$DEST" + +if ssh "$SERVER" 'command -v rsync >/dev/null 2>&1'; then + rsync -avz --delete \ + --exclude '.git' \ + --exclude 'mobile/build' \ + --exclude 'mobile/.dart_tool' \ + --exclude 'backend/log' \ + --exclude 'backend/tmp' \ + --exclude 'backend/.bundle' \ + --exclude 'infra/.env' \ + --exclude 'node_modules' \ + "$SRC/" "$SERVER:$DEST/" +else + echo "rsync non disponibile sul server, uso tar+scp..." + tar -C "$SRC" -czf /tmp/matchlivetv-deploy.tar.gz \ + --exclude='./mobile/build' --exclude='./mobile/.dart_tool' \ + --exclude='./backend/log' --exclude='./backend/tmp' --exclude='./infra/.env' --exclude='./.git' . + scp /tmp/matchlivetv-deploy.tar.gz "$SERVER:~/matchlivetv-deploy.tar.gz" + ssh "$SERVER" "mkdir -p $DEST && tar -xzf ~/matchlivetv-deploy.tar.gz -C $DEST" +fi + +echo "Done. On server run:" +echo " cd $DEST/infra && cp .env.production.example .env # edit secrets" +echo " docker compose -f docker-compose.prod.yml up -d --build" diff --git a/scripts/e2e_api.sh b/scripts/e2e_api.sh new file mode 100755 index 0000000..eb3cb43 --- /dev/null +++ b/scripts/e2e_api.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail +API="${API:-http://localhost:3000}" +PLATFORM="${PLATFORM:-matchlivetv}" + +echo "== Login ==" +LOGIN=$(curl -sf -X POST "$API/api/v1/auth/login" \ + -H "Content-Type: application/json" \ + -d '{"email":"coach@matchlivetv.test","password":"password123"}') +TOKEN=$(echo "$LOGIN" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") +echo "OK token received" + +AUTH="Authorization: Bearer $TOKEN" + +echo "== Teams ==" +TEAMS=$(curl -sf -H "$AUTH" "$API/api/v1/teams") +TEAM_ID=$(echo "$TEAMS" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") +echo "Team: $TEAM_ID" + +echo "== Matches ==" +MATCHES=$(curl -sf -H "$AUTH" "$API/api/v1/teams/$TEAM_ID/matches") +MATCH_ID=$(echo "$MATCHES" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") +echo "Match: $MATCH_ID" + +echo "== Create session (platform=$PLATFORM) ==" +SESSION=$(curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \ + "$API/api/v1/matches/$MATCH_ID/sessions" \ + -d "{\"platform\":\"$PLATFORM\",\"privacy_status\":\"unlisted\",\"quality_preset\":\"720p_30_2.5mbps\"}") +SESSION_ID=$(echo "$SESSION" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") +echo "Session: $SESSION_ID" + +python3 -c " +import sys, json +s = json.loads('''$SESSION''') +assert s.get('platform') == '$PLATFORM', s +if '$PLATFORM' == 'matchlivetv': + assert s.get('hls_playback_url'), 'missing hls_playback_url' + assert s.get('watch_page_url'), 'missing watch_page_url' + assert not s.get('youtube_broadcast_id'), s +print('Session fields OK') +" + +echo "== Start session ==" +curl -sf -X PATCH -H "$AUTH" "$API/api/v1/sessions/$SESSION_ID/start" > /dev/null +echo "Started" + +echo "== Webhook disconnect ==" +SECRET="${MEDIAMTX_WEBHOOK_SECRET:-mediamtx_webhook_dev_secret}" +BODY="{\"session_id\":\"$SESSION_ID\"}" +SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}') +curl -sf -X POST "$API/webhooks/mediamtx/disconnect" \ + -H "Content-Type: application/json" \ + -H "X-MediaMTX-Signature: $SIG" \ + -d "$BODY" +echo "Disconnect OK" + +STATUS=$(curl -sf -H "$AUTH" "$API/api/v1/sessions/$SESSION_ID" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])") +echo "Status after disconnect: $STATUS (expected reconnecting)" + +echo "== Webhook connect ==" +curl -sf -X POST "$API/webhooks/mediamtx/connect" \ + -H "Content-Type: application/json" \ + -H "X-MediaMTX-Signature: $SIG" \ + -d "$BODY" +STATUS=$(curl -sf -H "$AUTH" "$API/api/v1/sessions/$SESSION_ID" | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])") +echo "Status after reconnect: $STATUS (expected live)" + +echo "== Validate publish token ==" +curl -sf -X POST "$API/internal/validate_publish" \ + -d "session_id=$SESSION_ID&token=fake" | grep -q '"valid":false' && echo "Invalid token rejected OK" + +echo "== All E2E API checks passed ==" diff --git a/scripts/run_mobile_android.sh b/scripts/run_mobile_android.sh new file mode 100755 index 0000000..0b7c19a --- /dev/null +++ b/scripts/run_mobile_android.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Avvio su emulatore Android con port forwarding +set -euo pipefail +export PATH="$HOME/flutter/bin:$HOME/Android/Sdk/platform-tools:$PATH" +export ANDROID_HOME="$HOME/Android/Sdk" +export ANDROID_AVD_HOME="${ANDROID_AVD_HOME:-$HOME/.var/app/com.google.AndroidStudio/config/.android/avd}" + +adb reverse tcp:3000 tcp:3000 2>/dev/null || true + +cd "$(dirname "$0")/../mobile" +flutter pub get +flutter run -d emulator-5554 \ + --dart-define=API_BASE_URL=http://127.0.0.1:3000 \ + --dart-define=CABLE_URL=ws://127.0.0.1:3000/cable diff --git a/scripts/run_mobile_android_prod.sh b/scripts/run_mobile_android_prod.sh new file mode 100755 index 0000000..4892113 --- /dev/null +++ b/scripts/run_mobile_android_prod.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Telefono Android fisico → API produzione +set -euo pipefail +export PATH="$HOME/flutter/bin:$HOME/Android/Sdk/platform-tools:$PATH" +export ANDROID_HOME="$HOME/Android/Sdk" + +DEVICE="${ANDROID_DEVICE:-}" +if [[ -z "$DEVICE" ]]; then + DEVICE=$(adb devices | awk '/device$/{print $1; exit}' | grep -v emulator || true) +fi +if [[ -z "$DEVICE" ]]; then + echo "Nessun telefono USB collegato. Collega il device e abilita debug USB." + exit 1 +fi + +API_BASE_URL="${API_BASE_URL:-https://matchlivetv.eminux.it}" +MODE="${BUILD_MODE:-debug}" + +cd "$(dirname "$0")/../mobile" +flutter pub get + +ARGS=(run -d "$DEVICE" "--dart-define=API_BASE_URL=$API_BASE_URL") +if [[ "$MODE" == "release" ]]; then + ARGS=(run --release -d "$DEVICE" "--dart-define=API_BASE_URL=$API_BASE_URL") +fi + +echo "Device: $DEVICE | API: $API_BASE_URL | Mode: $MODE" +flutter "${ARGS[@]}" diff --git a/scripts/run_mobile_linux.sh b/scripts/run_mobile_linux.sh new file mode 100755 index 0000000..83796b3 --- /dev/null +++ b/scripts/run_mobile_linux.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Avvio leggero su Linux desktop (evita emulatore Android che consuma ~80% CPU) +set -euo pipefail +export PATH="$HOME/flutter/bin:$PATH" +cd "$(dirname "$0")/../mobile" +flutter pub get +flutter run -d linux --dart-define=API_BASE_URL=http://127.0.0.1:3000