From d4b5379e4b9c52e673f52a2d0993bf0a27782a70 Mon Sep 17 00:00:00 2001 From: Pushpam <93931528+Decoder07@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:36:43 +0530 Subject: [PATCH] FLUT-262: HLS Player revamped UI (#1748) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added HLS Player UI with chat * Added HLS Player desktop UI * 🤖 Automated Format and Fix * released sample app version 1.5.164 (464) 🍀 * updated pods * released sample app version 1.5.165 (465) 🍀 * released sample app version 1.5.166 (466) 🍀 * resolved lint warnings * added iOS Privacy info file * updated build version * Fixed foreground services on android 14 * released sample app version 1.5.167 (467) 🍀 * Added resolution changes and removed broadcast receiver * Fixed chat bottom sheet * released sample app version 1.5.168 (468) 🍀 * resolved lint warnings * Added caption supported api in iOS * Added buffering state * released sample app version 1.5.169 (469) 🍀 * Fixed player flickr * released sample app version 1.5.170 (470) 🍀 * Fixed SIP Peer UI issue (#1751) * released sample app version 1.5.171 (471) 🍀 * updated docs * Fixed reconnection handling in android and iOS (#1753) * released sample app version 1.5.172 (472) 🍀 * Fixed iOS network error * released sample app version 1.5.173 (473) 🍀 --------- Co-authored-by: Decoder07 Co-authored-by: Yogesh Singh Co-authored-by: ygit --- .trunk/.gitignore | 1 + .trunk/trunk.yaml | 28 +- .../hms_room_kit/example/ios/Podfile.lock | 12 +- packages/hms_room_kit/example/pubspec.lock | 188 +++----- .../lib/src/assets/icons/caption_off.svg | 1 + .../lib/src/assets/icons/caption_on.svg | 1 + .../lib/src/assets/icons/hand_off.svg | 1 + .../lib/src/assets/icons/music_wave.svg | 10 +- .../lib/src/assets/icons/sip_call.svg | 8 +- .../src/hls_viewer/hls_hand_raise_menu.dart | 79 +++ .../lib/src/hls_viewer/hls_player.dart | 100 +++- .../hls_player_desktop_controls.dart | 50 ++ .../hls_player_overlay_options.dart | 30 ++ .../lib/src/hls_viewer/hls_player_store.dart | 104 +++- .../lib/src/hls_viewer/hls_stats_view.dart | 38 +- .../hls_viewer/hls_stream_description.dart | 319 +++++++++++++ .../lib/src/hls_viewer/hls_stream_timer.dart | 75 +++ .../hls_viewer_bottom_navigation_bar.dart | 287 +++-------- .../lib/src/hls_viewer/hls_viewer_header.dart | 247 +++------- .../lib/src/hls_viewer/hls_viewer_page.dart | 158 +++--- .../lib/src/hls_viewer/hls_waiting_ui.dart | 69 +-- .../layout_api/hms_conferencing_items.dart | 33 +- .../meeting_bottom_navigation_bar.dart | 4 +- .../lib/src/meeting/meeting_store.dart | 37 +- .../preview_bottom_button_section.dart | 10 +- .../bottom_sheets/chat_bottom_sheet.dart | 225 +++++---- .../hls_app_utilities_bottom_sheet.dart | 9 +- .../leave_session_bottom_sheet.dart | 26 +- .../participants_bottom_sheet.dart | 4 +- .../refresh_stream_bottom_sheet.dart | 118 +++++ .../remote_peer_bottom_sheet.dart | 4 +- .../widgets/chat_widgets/chat_text_field.dart | 411 ++++++++-------- .../chat_widgets}/overlay_chat_component.dart | 0 .../chat_widgets/recipient_selector_chip.dart | 2 +- .../common_widgets/hms_subheading_text.dart | 4 +- .../common_widgets/hms_subtitle_text.dart | 2 +- .../common_widgets/hms_title_text.dart | 4 +- .../common_widgets/message_container.dart | 17 +- packages/hms_room_kit/pubspec.lock | 188 +++----- packages/hms_room_kit/pubspec.yaml | 2 +- .../hmssdk_flutter/HMSHLSVariantExtension.kt | 1 - .../hms/hmssdk_flutter/HMSPeerExtension.kt | 2 +- .../hmssdk_flutter/HMSRtmpStreamingState.kt | 1 - .../HMSTrackSettingsExtension.kt | 2 +- .../hms/hmssdk_flutter/HmssdkFlutterPlugin.kt | 127 ++--- .../hls_player/HLSStatsHandler.kt | 6 +- .../hls_player/HMSHLSPlayerAction.kt | 190 +++++--- .../hls_player/IHLSPlayerActionInterface.kt | 48 ++ .../methods/HMSCameraControlsAction.kt | 19 +- .../HMSNoiseCancellationControllerAction.kt | 50 +- .../methods/HMSPeerListIteratorAction.kt | 28 +- .../hmssdk_flutter/methods/HMSPollAction.kt | 450 +++++++++++------- .../methods/HMSSessionStoreAction.kt | 3 - .../poll_extension/HMSPollAnswerExtension.kt | 135 +++--- .../HMSPollAnswerResponseExtension.kt | 16 +- .../poll_extension/HMSPollBuilderExtension.kt | 172 ++++--- .../poll_extension/HMSPollExtension.kt | 35 +- .../HMSPollLeaderboardEntryExtension.kt | 11 +- .../HMSPollLeaderboardResponseExtension.kt | 13 +- .../HMSPollLeaderboardSummaryExtension.kt | 13 +- .../HMSPollQuestionAnswerExtension.kt | 56 +-- .../HMSPollQuestionExtension.kt | 234 ++++----- .../HMSPollQuestionOptionExtension.kt | 79 +-- .../HMSPollResponsePeerInfoExtension.kt | 13 +- .../HMSPollResultDisplayExtension.kt | 17 +- .../HMSPollStatsQuestionsExtension.kt | 13 +- .../hms/hmssdk_flutter/views/HMSHLSPlayer.kt | 229 ++++----- .../hmssdk_flutter/views/HMSTextureView.kt | 82 ++-- .../hms/hmssdk_flutter/views/HMSVideoView.kt | 3 +- .../views/HMSVideoViewFactory.kt | 9 +- .../example/ExampleAppChangelog.txt | 18 +- .../hmssdk_flutter/example/android/Gemfile | 2 +- .../example/android/Gemfile.lock | 65 ++- .../example/android/app/build.gradle | 4 +- packages/hmssdk_flutter/example/ios/Gemfile | 2 +- .../hmssdk_flutter/example/ios/Gemfile.lock | 65 ++- .../hmssdk_flutter/example/ios/Podfile.lock | 34 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 + .../example/ios/Runner/AppDelegate.swift | 4 +- .../example/ios/Runner/Info.plist | 4 +- .../example/ios/Runner/PrivacyInfo.xcprivacy | 59 +++ .../example/lib/foreground_task_handler.dart | 50 +- packages/hmssdk_flutter/example/lib/main.dart | 2 +- .../example/lib/qr_code_screen.dart | 2 +- packages/hmssdk_flutter/example/pubspec.lock | 86 ++-- ...HMSNoiseCancellationControllerAction.swift | 55 +-- .../ios/Classes/Actions/HMSPollAction.swift | 314 ++++++------ .../Actions/HMSSessionStoreAction.swift | 29 +- .../HLSPlayer/HMSHLSPlayerAction.swift | 23 + .../HMSHLSStreamViewController.swift | 11 + .../ios/Classes/Models/HMSPeerExtension.swift | 2 +- .../Models/HMSPermissionExtension.swift | 2 +- .../ios/Classes/Models/HMSRoomExtension.swift | 2 +- .../Models/HMSStreamingStateExtension.swift | 4 +- .../Models/HMSTrackSettingsExtension.swift | 12 +- .../Models/polls/HMSPollAnswerExtension.swift | 12 +- .../HMSPollAnswerResponseExtension.swift | 21 +- .../polls/HMSPollBuilderExtension.swift | 54 +-- .../Models/polls/HMSPollExtension.swift | 29 +- .../HMSPollLeaderboardEntryExtension.swift | 12 +- .../HMSPollLeaderboardResponseExtension.swift | 20 +- .../HMSPollLeaderboardSummaryExtension.swift | 26 +- .../HMSPollQuestionAnswerExtension.swift | 14 +- .../polls/HMSPollQuestionExtension.swift | 30 +- .../HMSPollQuestionOptionExtension.swift | 10 +- .../HMSPollQuestionResultExtension.swift | 14 +- .../HMSPollResponsePeerInfoExtension.swift | 24 +- .../Models/polls/HMSPollResultExtension.swift | 18 +- .../Classes/SwiftHmssdkFlutterPlugin.swift | 66 ++- .../ios/Classes/Views/HMSHLSPlayerView.swift | 34 ++ .../lib/assets/sdk-versions.json | 2 +- .../lib/src/common/platform_methods.dart | 24 + .../enum/hms_hls_playback_event_method.dart | 3 + .../hms_hls_playback_event_listener.dart | 8 + .../hls_player/hms_hls_player_controller.dart | 21 + .../lib/src/service/platform_service.dart | 7 + .../lib/src/ui/meeting/hms_hls_player.dart | 42 +- packages/hmssdk_flutter/pubspec.lock | 66 +-- sample apps/flutter-meet/README.md | 1 - .../flutter-meet/analysis_options.yaml | 1 - .../com/aditya/google_meet/MainActivity.kt | 3 +- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 490 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 396 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 661 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 951 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 1371 bytes .../AppIcon.appiconset/Contents.json | 160 +++---- .../Icon-App-1024x1024@1x.png | Bin 10932 -> 10756 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 564 -> 446 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 1283 -> 1083 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 1588 -> 1514 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 1025 -> 849 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 1716 -> 1630 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 1920 -> 1847 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 1283 -> 1083 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 1895 -> 1821 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 2665 -> 2617 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 2665 -> 2617 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 1888 -> 1831 bytes .../LaunchImage.imageset/Contents.json | 26 +- .../LaunchImage.imageset/README.md | 2 +- sample apps/flutter-meet/pubspec.yaml | 6 +- 142 files changed, 3766 insertions(+), 2810 deletions(-) create mode 100644 packages/hms_room_kit/lib/src/assets/icons/caption_off.svg create mode 100644 packages/hms_room_kit/lib/src/assets/icons/caption_on.svg create mode 100644 packages/hms_room_kit/lib/src/assets/icons/hand_off.svg create mode 100644 packages/hms_room_kit/lib/src/hls_viewer/hls_hand_raise_menu.dart create mode 100644 packages/hms_room_kit/lib/src/hls_viewer/hls_player_desktop_controls.dart create mode 100644 packages/hms_room_kit/lib/src/hls_viewer/hls_player_overlay_options.dart create mode 100644 packages/hms_room_kit/lib/src/hls_viewer/hls_stream_description.dart create mode 100644 packages/hms_room_kit/lib/src/hls_viewer/hls_stream_timer.dart create mode 100644 packages/hms_room_kit/lib/src/widgets/bottom_sheets/refresh_stream_bottom_sheet.dart rename packages/hms_room_kit/lib/src/{hls_viewer => widgets/chat_widgets}/overlay_chat_component.dart (100%) create mode 100644 packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/IHLSPlayerActionInterface.kt create mode 100644 packages/hmssdk_flutter/example/ios/Runner/PrivacyInfo.xcprivacy diff --git a/.trunk/.gitignore b/.trunk/.gitignore index 1e2465290..15966d087 100644 --- a/.trunk/.gitignore +++ b/.trunk/.gitignore @@ -6,3 +6,4 @@ plugins user_trunk.yaml user.yaml +tmp diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 3d660863f..bb53f1c98 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,28 +1,28 @@ version: 0.1 cli: - version: 1.18.0 + version: 1.21.0 plugins: sources: - id: trunk - ref: v1.3.0 + ref: v1.4.5 uri: https://github.com/trunk-io/plugins lint: enabled: - - actionlint@1.6.26 - - checkov@3.1.9 - - osv-scanner@1.5.0 - - trivy@0.48.0 - - trufflehog@3.63.2 + - actionlint@1.6.27 + - checkov@3.2.60 + - osv-scanner@1.7.0 + - trivy@0.50.1 + - trufflehog@3.71.0 - oxipng@9.0.0 - - yamllint@1.33.0 - - markdownlint@0.37.0 - - prettier@3.1.0 + - yamllint@1.35.1 + - markdownlint@0.39.0 + - prettier@3.2.5 - git-diff-check - shfmt@3.6.0 - - shellcheck@0.9.0 - - gitleaks@8.18.1 - - svgo@3.0.5 - - ktlint@1.0.1 + - shellcheck@0.10.0 + - gitleaks@8.18.2 + - svgo@3.2.0 + - ktlint@1.2.1 runtimes: enabled: - python@3.10.8 diff --git a/packages/hms_room_kit/example/ios/Podfile.lock b/packages/hms_room_kit/example/ios/Podfile.lock index c2f593f29..1d21584f3 100644 --- a/packages/hms_room_kit/example/ios/Podfile.lock +++ b/packages/hms_room_kit/example/ios/Podfile.lock @@ -4,14 +4,16 @@ PODS: - HMSBroadcastExtensionSDK (0.0.9) - HMSHLSPlayerSDK (0.0.2): - HMSAnalyticsSDK (= 0.0.2) - - HMSSDK (1.7.0): + - HMSNoiseCancellationModels (1.0.0) + - HMSSDK (1.8.0): - HMSAnalyticsSDK (= 0.0.2) - HMSWebRTC (= 1.0.5118) - hmssdk_flutter (1.10.0): - Flutter - HMSBroadcastExtensionSDK (= 0.0.9) - HMSHLSPlayerSDK (= 0.0.2) - - HMSSDK (= 1.7.0) + - HMSNoiseCancellationModels (= 1.0.0) + - HMSSDK (= 1.8.0) - HMSWebRTC (1.0.5118) - path_provider_foundation (0.0.1): - Flutter @@ -40,6 +42,7 @@ SPEC REPOS: - HMSAnalyticsSDK - HMSBroadcastExtensionSDK - HMSHLSPlayerSDK + - HMSNoiseCancellationModels - HMSSDK - HMSWebRTC @@ -64,8 +67,9 @@ SPEC CHECKSUMS: HMSAnalyticsSDK: 4d2a88a729b1eb42f3d25f217c28937ec318a5b7 HMSBroadcastExtensionSDK: d80fe325f6c928bd8e5176290b5a4b7ae15d6fbb HMSHLSPlayerSDK: 6a54ad4d12f3dc2270d1ecd24019d71282a4f6a3 - HMSSDK: 421b4ce83a601bbda283b3b9fbcd1da6898d2a84 - hmssdk_flutter: dd0d9bcb8fb90c54319d0114e917c4607c0bc382 + HMSNoiseCancellationModels: a3bda1405a16015632f4bcabd46ce48f35103b02 + HMSSDK: c893d1381a47ed02760ef6d06625b9aa5251f998 + hmssdk_flutter: 997715f0bedfcb22750fb95549672bf3fea380ff HMSWebRTC: 4487c7200f1e9358412c1d8cd974edd2766467dc path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 diff --git a/packages/hms_room_kit/example/pubspec.lock b/packages/hms_room_kit/example/pubspec.lock index 8cca87c8d..a43e05a3d 100644 --- a/packages/hms_room_kit/example/pubspec.lock +++ b/packages/hms_room_kit/example/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.2" convert: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" url: "https://pub.dev" source: hosted - version: "0.3.4+1" + version: "0.3.3+7" crypto: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.0" file: dependency: transitive description: @@ -145,14 +145,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" - url: "https://pub.dev" - source: hosted - version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -218,20 +210,18 @@ packages: hmssdk_flutter: dependency: transitive description: - path: "packages/hmssdk_flutter" - ref: develop - resolved-ref: f23908cdd1254aab1ebe01df6dfc75bdbbd44ae8 - url: "https://github.com/100mslive/100ms-flutter.git" - source: git + path: "../../hmssdk_flutter" + relative: true + source: path version: "1.10.0" http: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.1.0" http_parser: dependency: transitive description: @@ -252,34 +242,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf - url: "https://pub.dev" - source: hosted - version: "0.7.1" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "0.6.7" linkify: dependency: transitive description: @@ -308,34 +274,34 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.9.1" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.4" nested: dependency: transitive description: @@ -348,10 +314,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" path_parsing: dependency: transitive description: @@ -364,18 +330,18 @@ packages: dependency: transitive description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" path_provider_foundation: dependency: transitive description: @@ -412,58 +378,50 @@ packages: dependency: transitive description: name: permission_handler - sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" url: "https://pub.dev" source: hosted - version: "11.3.1" + version: "11.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474" + sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e url: "https://pub.dev" source: hosted - version: "12.0.5" + version: "11.1.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662 + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "9.4.4" - permission_handler_html: - dependency: transitive - description: - name: permission_handler_html - sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d" - url: "https://pub.dev" - source: hosted - version: "0.1.1" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20" + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "3.12.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.1.3" petitparser: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "5.4.0" platform: dependency: transitive description: @@ -484,10 +442,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" url: "https://pub.dev" source: hosted - version: "3.7.4" + version: "3.8.0" provider: dependency: transitive description: @@ -516,10 +474,10 @@ packages: dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: @@ -556,10 +514,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.1" shared_preferences_windows: dependency: transitive description: @@ -593,18 +551,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -625,10 +583,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.0" tuple: dependency: transitive description: @@ -649,10 +607,10 @@ packages: dependency: transitive description: name: url_launcher - sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.2.4" url_launcher_android: dependency: transitive description: @@ -665,10 +623,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.2.4" url_launcher_linux: dependency: transitive description: @@ -697,10 +655,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.0" url_launcher_windows: dependency: transitive description: @@ -713,10 +671,10 @@ packages: dependency: transitive description: name: uuid - sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" url: "https://pub.dev" source: hosted - version: "4.3.3" + version: "4.2.2" vector_graphics: dependency: transitive description: @@ -757,30 +715,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.0+2" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.1.4-beta" win32: dependency: transitive description: name: win32 - sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.1.1" xdg_directories: dependency: transitive description: @@ -793,10 +743,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.3.0" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" diff --git a/packages/hms_room_kit/lib/src/assets/icons/caption_off.svg b/packages/hms_room_kit/lib/src/assets/icons/caption_off.svg new file mode 100644 index 000000000..9532a1d0c --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/caption_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/hms_room_kit/lib/src/assets/icons/caption_on.svg b/packages/hms_room_kit/lib/src/assets/icons/caption_on.svg new file mode 100644 index 000000000..0716c0d8b --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/caption_on.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/hms_room_kit/lib/src/assets/icons/hand_off.svg b/packages/hms_room_kit/lib/src/assets/icons/hand_off.svg new file mode 100644 index 000000000..88e183ecd --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/hand_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/hms_room_kit/lib/src/assets/icons/music_wave.svg b/packages/hms_room_kit/lib/src/assets/icons/music_wave.svg index 863ee3614..0e56ef9fa 100644 --- a/packages/hms_room_kit/lib/src/assets/icons/music_wave.svg +++ b/packages/hms_room_kit/lib/src/assets/icons/music_wave.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/packages/hms_room_kit/lib/src/assets/icons/sip_call.svg b/packages/hms_room_kit/lib/src/assets/icons/sip_call.svg index 635ee3b49..500fe670f 100644 --- a/packages/hms_room_kit/lib/src/assets/icons/sip_call.svg +++ b/packages/hms_room_kit/lib/src/assets/icons/sip_call.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_hand_raise_menu.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_hand_raise_menu.dart new file mode 100644 index 000000000..20aafc5af --- /dev/null +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_hand_raise_menu.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_embedded_button.dart'; +import 'package:provider/provider.dart'; + +class HLSHandRaiseMenu extends StatelessWidget { + const HLSHandRaiseMenu({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Selector( + selector: (_, meetingStore) => meetingStore.isRaisedHand, + builder: (_, isRaisedHand, __) { + return HMSEmbeddedButton( + onTap: () => { + context.read().toggleLocalPeerHandRaise(), + }, + enabledBorderColor: HMSThemeColors.surfaceBrighter, + offColor: HMSThemeColors.surfaceDefault, + disabledBorderColor: HMSThemeColors.surfaceDefault, + onColor: HMSThemeColors.surfaceBrighter, + isActive: isRaisedHand, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + isRaisedHand + ? "packages/hms_room_kit/lib/src/assets/icons/hand_off.svg" + : "packages/hms_room_kit/lib/src/assets/icons/hand_outline.svg", + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), + semanticsLabel: "hand_raise_button", + ), + ), + ); + }), + const SizedBox( + width: 8, + ), + if (HMSRoomLayout.isParticipantsListEnabled || + Constant.prebuiltOptions?.userName == null) + HMSEmbeddedButton( + onTap: () async => { + showModalBottomSheet( + isScrollControlled: true, + backgroundColor: HMSThemeColors.surfaceDim, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)), + ), + context: context, + builder: (ctx) => ChangeNotifierProvider.value( + value: context.read(), + child: const HLSAppUtilitiesBottomSheet()), + ) + }, + enabledBorderColor: HMSThemeColors.surfaceDefault, + onColor: HMSThemeColors.surfaceDefault, + isActive: true, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/menu.svg", + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), + semanticsLabel: "more_button"), + ), + ), + ], + ); + } +} diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player.dart index f9a90edfe..86d690dcc 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_player.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player.dart @@ -3,41 +3,97 @@ library; ///Package imports import 'package:flutter/material.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:provider/provider.dart'; ///Project imports import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; import 'package:hms_room_kit/src/meeting/meeting_store.dart'; -import 'package:provider/provider.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_player_overlay_options.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_waiting_ui.dart'; +import 'package:hms_room_kit/src/layout_api/hms_theme_colors.dart'; ///[HLSPlayer] is a component that is used to show the HLS Player class HLSPlayer extends StatelessWidget { - final double? ratio; - const HLSPlayer({Key? key, this.ratio}) : super(key: key); + const HLSPlayer({Key? key}) : super(key: key); @override Widget build(BuildContext context) { ///We use the hlsAspectRatio from the [MeetingStore] to set the aspect ratio of the player ///By default the aspect ratio is 9:16 - return Selector( - selector: (_, meetingStore) => meetingStore.hlsAspectRatio, - builder: (_, ratio, __) { - return GestureDetector( - child: AspectRatio( - aspectRatio: ratio, - child: InkWell( - onTap: () => - context.read().toggleButtonsVisibility(), - splashFactory: NoSplash.splashFactory, - child: IgnorePointer( - child: HMSHLSPlayer( - key: key, - showPlayerControls: false, - isHLSStatsRequired: - context.read().isHLSStatsEnabled, - ), - ), + return Selector( + selector: (_, meetingStore) => meetingStore.hasHlsStarted, + builder: (_, hasHLSStarted, __) { + return Stack( + children: [ + ///Renders the HLS Player if the HLS has started + ///Otherwise renders the waiting UI + hasHLSStarted + ? Align( + alignment: Alignment.center, + child: Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerSize, + builder: (_, hlsPlayerSize, __) { + return AspectRatio( + aspectRatio: + hlsPlayerSize.width / hlsPlayerSize.height, + child: InkWell( + onTap: () => context + .read() + .toggleButtonsVisibility(), + splashFactory: NoSplash.splashFactory, + splashColor: HMSThemeColors.backgroundDim, + child: IgnorePointer( + child: const HMSHLSPlayer( + showPlayerControls: false, + ), + ), + ), + ); + }), + ) + : Center(child: const HLSWaitingUI()), + + ///This renders the overlay controls for HLS Player + Align( + alignment: Alignment.center, + child: Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.isFullScreen, + builder: (_, isFullScreen, __) { + return isFullScreen + ? Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerSize, + builder: (_, hlsPlayerSize, __) { + return AspectRatio( + aspectRatio: hlsPlayerSize.width / + hlsPlayerSize.height, + child: HLSPlayerOverlayOptions( + hasHLSStarted: hasHLSStarted, + ), + ); + }) + : HLSPlayerOverlayOptions( + hasHLSStarted: hasHLSStarted); + }), ), - ), + Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.playerPlaybackState, + builder: (_, state, __) { + return state == HMSHLSPlaybackState.BUFFERING || state == HMSHLSPlaybackState.FAILED + ? Align( + alignment: Alignment.center, + child: CircularProgressIndicator( + color: HMSThemeColors.primaryDefault, + strokeWidth: 2, + ), + ) + : const SizedBox(); + }, + ) + ], ); }); } diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_desktop_controls.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_desktop_controls.dart new file mode 100644 index 000000000..9ebba4811 --- /dev/null +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_desktop_controls.dart @@ -0,0 +1,50 @@ +library; + +///Package imports +import 'package:flutter/material.dart'; + +///Project imports +import 'package:hms_room_kit/src/hls_viewer/hls_stream_description.dart'; +import 'package:hms_room_kit/src/widgets/bottom_sheets/chat_bottom_sheet.dart'; + +///[HLSPlayerDesktopControls] is the desktop controls for the HLS Player +class HLSPlayerDesktopControls extends StatefulWidget { + @override + State createState() => + _HLSPlayerDesktopControlsState(); +} + +class _HLSPlayerDesktopControlsState extends State { + bool showDescription = false; + + ///[toggleDescription] toggles the visibility of description + void toggleDescription() { + setState(() { + showDescription = !showDescription; + }); + } + + @override + Widget build(BuildContext context) { + return Expanded( + child: Column( + children: [ + ///Renders HLS Stream Description and Chat Bottom Sheet + HLSStreamDescription( + showDescription: showDescription, + toggleDescription: toggleDescription), + + ///Renders Chat Bottom Sheet only is the description is not visible + if (!showDescription) + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: ChatBottomSheet( + isHLSChat: true, + ), + )) + ], + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_overlay_options.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_overlay_options.dart new file mode 100644 index 000000000..58dec346e --- /dev/null +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_overlay_options.dart @@ -0,0 +1,30 @@ +library; + +///Package imports +import 'package:flutter/material.dart'; + +///Project imports +import 'package:hms_room_kit/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_viewer_header.dart'; + +///[HLSPlayerOverlayOptions] renders the overlay options for the HLS Player +class HLSPlayerOverlayOptions extends StatelessWidget { + final bool hasHLSStarted; + + const HLSPlayerOverlayOptions({super.key, required this.hasHLSStarted}); + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + HLSViewerHeader( + hasHLSStarted: hasHLSStarted, + ), + + ///Renders the bottom navigation bar if the HLS has started + ///Otherwise does not render the bottom navigation bar + hasHLSStarted ? HLSViewerBottomNavigationBar() : const SizedBox() + ], + ); + } +} diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_store.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_store.dart index 8651d49b7..f48fab29b 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_player_store.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_player_store.dart @@ -1,8 +1,19 @@ +library; + +///Dart imports import 'dart:async'; +import 'dart:developer'; + +///Package imports import 'package:flutter/material.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +///Project imports import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; -class HLSPlayerStore extends ChangeNotifier { +///[HLSPlayerStore] is a store that stores the state of the HLS Player +class HLSPlayerStore extends ChangeNotifier + implements HMSHLSPlaybackEventsListener { ///This variable stores whether the application is in full screen or not bool isFullScreen = false; @@ -12,6 +23,12 @@ class HLSPlayerStore extends ChangeNotifier { ///This variable stores whether the buttons are visible or not bool areStreamControlsVisible = true; + ///This variable stores whether the captions are enabled or not + bool isCaptionEnabled = false; + + ///This variable stores whether the captions are supported or not + bool areCaptionsSupported = false; + ///This variable stores whether the chat is opened or not ///The initial value is taken from the [HMSRoomLayout.chatData] bool isChatOpened = (HMSRoomLayout.chatData?.isOpenInitially ?? false) && @@ -22,6 +39,20 @@ class HLSPlayerStore extends ChangeNotifier { ///This is done to avoid multiple timers running at the same time bool _isTimerActive = false; + ///This variable stores whether there is some error in playing stream + bool isPlayerFailed = false; + + ///HLS Player Stats + + HMSHLSPlayerStats? hlsPlayerStats; + + ///[hlsPlayerSize] stores the resolution of HLS Stream + Size hlsPlayerSize = Size(1, 1); + + bool isHLSStatsEnabled = false; + + HMSHLSPlaybackState playerPlaybackState = HMSHLSPlaybackState.PLAYING; + ///This method starts a timer for 5 seconds and then hides the buttons /// ///[isStreamPlaying] is used to check if the video is playing or not @@ -38,6 +69,13 @@ class HLSPlayerStore extends ChangeNotifier { }); } + ///[toggleFullScreen] toggles the full screen mode + void toggleFullScreen() { + isFullScreen = !isFullScreen; + notifyListeners(); + } + + ///This method sets the [isStreamPlaying] to true or false void setStreamPlaying(bool isStreamPlaying) { this.isStreamPlaying = isStreamPlaying; if (isStreamPlaying) { @@ -47,15 +85,23 @@ class HLSPlayerStore extends ChangeNotifier { return; } else { areStreamControlsVisible = true; + isFullScreen = false; } notifyListeners(); } + ///This method toggles the visibility of the chat void toggleIsChatOpened() { isChatOpened = !isChatOpened; notifyListeners(); } + ///This method toggles the visibility of the captions + void toggleCaptions() { + isCaptionEnabled = !isCaptionEnabled; + notifyListeners(); + } + ///This method toggles the visibility of the buttons /// ///If the buttons are not visible we set the [areStreamControlsVisible] to true @@ -77,4 +123,60 @@ class HLSPlayerStore extends ChangeNotifier { } } } + + ///[areClosedCaptionsSupported] checks if the closed captions are supported or not + void areClosedCaptionsSupported() async { + areCaptionsSupported = + await HMSHLSPlayerController.areClosedCaptionsSupported(); + notifyListeners(); + } + + void setHLSPlayerStats(bool value) { + isHLSStatsEnabled = value; + if (!value) { + HMSHLSPlayerController.removeHLSStatsListener(); + } else { + HMSHLSPlayerController.addHLSStatsListener(); + } + notifyListeners(); + } + + @override + void onCue({required HMSHLSCue hlsCue}) {} + + @override + void onHLSError({required HMSException hlsException}) {} + + @override + void onHLSEventUpdate({required HMSHLSPlayerStats playerStats}) { + log("onHLSEventUpdate-> bitrate:${playerStats.averageBitrate} buffered duration: ${playerStats.bufferedDuration}"); + hlsPlayerStats = playerStats; + notifyListeners(); + } + + @override + void onPlaybackFailure({required String? error}) { + log("Playback failure $error"); + } + + @override + void onPlaybackStateChanged({required HMSHLSPlaybackState playbackState}) { + log("Playback state changed to ${playbackState.name}"); + playerPlaybackState = playbackState; + if (playerPlaybackState == HMSHLSPlaybackState.PLAYING) { + isPlayerFailed = false; + areClosedCaptionsSupported(); + } + if (playerPlaybackState == HMSHLSPlaybackState.FAILED) { + isPlayerFailed = true; + } + notifyListeners(); + } + + @override + void onVideoSizeChanged({required Size size}) { + log("onVideoSizeChanged -> height:${size.height} width:${size.width}"); + hlsPlayerSize = size; + notifyListeners(); + } } diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_stats_view.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_stats_view.dart index 3b4f686df..1b411e895 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_stats_view.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_stats_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hms_room_kit/src/common/app_color.dart'; -import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; import 'package:provider/provider.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_text_style.dart'; @@ -18,72 +18,72 @@ class HLSStatsView extends StatelessWidget { child: ListView( shrinkWrap: true, children: [ - Selector( + Selector( builder: (_, bitrate, __) { return Text( "Bitrate : ${bitrate == null ? "-" : (bitrate / 8000)} KBps", style: HMSTextStyle.setTextStyle( color: iconColor, fontSize: 12)); }, - selector: (_, meetingStore) => - meetingStore.hlsPlayerStats?.averageBitrate), + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerStats?.averageBitrate), const SizedBox( height: 10, ), - Selector( + Selector( builder: (_, bufferedDuration, __) { return Text( "Buffered Duration : ${bufferedDuration == null ? "-" : bufferedDuration / 1000}", style: HMSTextStyle.setTextStyle( color: iconColor, fontSize: 12)); }, - selector: (_, meetingStore) => - meetingStore.hlsPlayerStats?.bufferedDuration), + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerStats?.bufferedDuration), const SizedBox( height: 10, ), - Selector( + Selector( builder: (_, videoWidth, __) { return Text("Video Width : ${videoWidth ?? "-"} px", style: HMSTextStyle.setTextStyle( color: iconColor, fontSize: 12)); }, - selector: (_, meetingStore) => - meetingStore.hlsPlayerStats?.videoWidth), + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerStats?.videoWidth), const SizedBox( height: 10, ), - Selector( + Selector( builder: (_, videoHeight, __) { return Text("Video Height : ${videoHeight ?? "-"} px", style: HMSTextStyle.setTextStyle( color: iconColor, fontSize: 12)); }, - selector: (_, meetingStore) => - meetingStore.hlsPlayerStats?.videoHeight), + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerStats?.videoHeight), const SizedBox( height: 10, ), - Selector( + Selector( builder: (_, droppedFrameCount, __) { return Text("Dropped Frames : ${droppedFrameCount ?? "-"} ", style: HMSTextStyle.setTextStyle( color: iconColor, fontSize: 12)); }, - selector: (_, meetingStore) => - meetingStore.hlsPlayerStats?.droppedFrameCount), + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerStats?.droppedFrameCount), const SizedBox( height: 10, ), - Selector( + Selector( builder: (_, distanceFromLive, __) { return Text( "Distance from live edge : ${distanceFromLive == null ? "-" : distanceFromLive / 1000}s", style: HMSTextStyle.setTextStyle( color: iconColor, fontSize: 12)); }, - selector: (_, meetingStore) => - meetingStore.hlsPlayerStats?.distanceFromLive), + selector: (_, hlsPlayerStore) => + hlsPlayerStore.hlsPlayerStats?.distanceFromLive), ], ), ); diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_stream_description.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_stream_description.dart new file mode 100644 index 000000000..fc752d663 --- /dev/null +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_stream_description.dart @@ -0,0 +1,319 @@ +library; + +///Dart imports +import 'dart:developer'; + +///Package imports +import 'package:flutter/material.dart'; +import 'package:flutter_linkify/flutter_linkify.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; +import 'package:url_launcher/url_launcher.dart'; + +///Project imports +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_stream_timer.dart'; +import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; + +///[HLSStreamDescription] is a widget that is used to render the description of the HLS Stream +class HLSStreamDescription extends StatelessWidget { + final bool showDescription; + final Function toggleDescription; + + HLSStreamDescription( + {Key? key, this.showDescription = false, required this.toggleDescription}) + : super(key: key); + @override + Widget build(BuildContext context) { + return Container( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + ///If [showDescription] is true, we render the description of the HLS Stream + ///This widget is only rendered if description is present and it's visibility is true + if (showDescription) + GestureDetector( + onTap: () => toggleDescription(), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: HMSTitleText( + text: "About Session", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + letterSpacing: 0 / 15, + ), + ), + Icon(Icons.keyboard_arrow_down_rounded), + ], + ), + ), + ), + if (showDescription) + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Divider( + color: HMSThemeColors.borderBright, + height: 2, + ), + ), + Row( + children: [ + ///This renders the logo + ///If the logo is null, we render an empty SizedBox + ///If the logo is an svg, we render the svg + ///If the logo is an image, we render the image + HMSRoomLayout.roleLayoutData?.logo?.url == null + ? Container() + : HMSRoomLayout.roleLayoutData!.logo!.url!.contains("svg") + ? SvgPicture.network( + HMSRoomLayout.roleLayoutData!.logo!.url!, + height: 32, + width: 32, + ) + : Image.network( + HMSRoomLayout.roleLayoutData!.logo!.url!, + errorBuilder: (context, exception, _) { + log('Error is $exception'); + return const SizedBox( + width: 32, + height: 32, + ); + }, + height: 32, + width: 32, + ), + SizedBox( + width: 12, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ///This renders the title + ///Only renders this if the title is non null and not empty + if (HMSRoomLayout + .roleLayoutData + ?.screens + ?.conferencing + ?.hlsLiveStreaming + ?.elements + ?.header + ?.title + ?.isNotEmpty ?? + false) + Container( + child: HMSSubheadingText( + text: HMSRoomLayout + .roleLayoutData + ?.screens + ?.conferencing + ?.hlsLiveStreaming + ?.elements + ?.header + ?.title ?? + "", + maxLines: showDescription ? 5 : 2, + textColor: HMSThemeColors.onSecondaryHighEmphasis, + textOverflow: TextOverflow.ellipsis, + fontWeight: FontWeight.w600, + ), + ), + Row( + children: [ + ///This renders the number of peers watching the stream + Selector( + selector: (_, meetingStore) => + meetingStore.peersInRoom, + builder: (_, data, __) { + return HMSSubtitleText( + text: Utilities.formatNumber(data) + + " watching", + letterSpacing: 0.4, + textColor: + HMSThemeColors.onSurfaceMediumEmphasis); + }), + + if (context.read().hasHlsStarted) + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/red_dot.svg", + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, + BlendMode.srcIn), + ), + ), + + ///This renders the timer for the HLS Stream + ///This is only rendered if the HLS Stream has started + ///This shows for how much time you have been watching stream + if (context.read().hasHlsStarted) + HLSStreamTimer(), + + ///This renders the recording status + ///If the recording is started, we render the recording indicator + ///else we render an empty Container + /// + ///For recording status we use the recordingType map from the [MeetingStore] + (HMSRoomLayout + .roleLayoutData + ?.screens + ?.conferencing + ?.hlsLiveStreaming + ?.elements + ?.header + ?.description != + null && + !showDescription) + ? GestureDetector( + onTap: () { + if ((HMSRoomLayout + .roleLayoutData + ?.screens + ?.conferencing + ?.hlsLiveStreaming + ?.elements + ?.header + ?.description != + null)) { + toggleDescription(); + } + }, + child: HMSSubtitleText( + text: " ...more", + fontWeight: FontWeight.w600, + textColor: + HMSThemeColors.onSurfaceHighEmphasis, + letterSpacing: 0.4, + ), + ) + : Selector< + MeetingStore, + Tuple3< + HMSRecordingState, + HMSRecordingState, + HMSRecordingState>>( + selector: (_, meetingStore) => Tuple3( + meetingStore.recordingType["browser"] ?? + HMSRecordingState.none, + meetingStore.recordingType["server"] ?? + HMSRecordingState.none, + meetingStore.recordingType["hls"] ?? + HMSRecordingState.none), + builder: (_, data, __) { + return (data.item1 == + HMSRecordingState.started || + data.item1 == + HMSRecordingState.resumed || + data.item2 == + HMSRecordingState.started || + data.item2 == + HMSRecordingState.resumed || + data.item3 == + HMSRecordingState.started || + data.item3 == + HMSRecordingState.resumed) + ? Row( + children: [ + Padding( + padding: + const EdgeInsets.symmetric( + horizontal: 8.0), + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/red_dot.svg", + colorFilter: ColorFilter.mode( + HMSThemeColors + .onSurfaceHighEmphasis, + BlendMode.srcIn), + ), + ), + HMSSubtitleText( + text: "Recording", + letterSpacing: 0.4, + textColor: HMSThemeColors + .onSurfaceMediumEmphasis, + ), + ], + ) + : const SizedBox(); + }), + ], + ) + ], + ), + ) + ], + ), + + ///This renders the description of the HLS Stream + ///This is only rendered if the description is present and the visibility is true + if (!showDescription) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Divider( + color: HMSThemeColors.borderBright, + height: 2, + ), + ), + if (showDescription && + (HMSRoomLayout.roleLayoutData?.screens?.conferencing + ?.hlsLiveStreaming?.elements?.header?.description != + null)) + Padding( + padding: const EdgeInsets.only(top: 16.0), + + ///Here we use SelectableLinkify to render the description + ///so that if there is any link in the description, it can be clicked + child: SingleChildScrollView( + child: Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.38), + child: SelectableLinkify( + text: HMSRoomLayout + .roleLayoutData + ?.screens + ?.conferencing + ?.hlsLiveStreaming + ?.elements + ?.header + ?.description ?? + "", + style: HMSTextStyle.setTextStyle( + fontSize: 14.0, + color: HMSThemeColors.onSurfaceMediumEmphasis, + letterSpacing: 0.25, + height: 20 / 14, + fontWeight: FontWeight.w400, + ), + linkStyle: HMSTextStyle.setTextStyle( + fontSize: 14.0, + color: HMSThemeColors.primaryBright, + letterSpacing: 0.25, + height: 20 / 14, + fontWeight: FontWeight.w400), + onOpen: (link) async { + Uri url = Uri.parse(link.url); + if (await canLaunchUrl(url)) { + await launchUrl(url, + mode: LaunchMode.externalApplication); + } + }, + ), + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_stream_timer.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_stream_timer.dart new file mode 100644 index 000000000..10ee84c08 --- /dev/null +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_stream_timer.dart @@ -0,0 +1,75 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:provider/provider.dart'; + +class HLSStreamTimer extends StatefulWidget { + @override + State createState() => _HLSStreamTimerState(); +} + +class _HLSStreamTimerState extends State { + int _secondsElapsed = 0; + Timer? _timer; + + @override + void initState() { + super.initState(); + DateTime? startedAt = context + .read() + .hmsRoom + ?.hmshlsStreamingState + ?.variants + .first + ?.startedAt; + + if (startedAt != null) { + _secondsElapsed = DateTime.now() + .difference(DateTime.fromMillisecondsSinceEpoch( + startedAt.millisecondsSinceEpoch)) + .inSeconds; + } + _startTimer(); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + void _startTimer() { + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + setState(() { + _secondsElapsed++; + }); + }); + } + + ///[_getFormattedTime] returns the formatted time in hh:mm:ss format + String _getFormattedTime() { + int _minutes = _secondsElapsed ~/ 60; + int _seconds = _secondsElapsed % 60; + + int _hours = 0; + if (_minutes > 59) { + _hours = _minutes ~/ 60; + _minutes %= 60; + } + + ///We only show seconds if hours and minutes are 0 + ///only minutes if hours are 0 + ///only hours if hours are greater than 0 + return "Started${_hours > 0 ? " ${_hours.toString()}h" : ""} ${_hours < 1 && _minutes > 0 ? "${_minutes.toString()}m" : ""} ${_hours < 1 && _minutes < 1 ? "${_seconds.toString()}s " : ""}ago"; + } + + @override + Widget build(BuildContext context) { + return HMSSubtitleText( + text: _getFormattedTime(), + letterSpacing: 0.4, + textColor: HMSThemeColors.onSurfaceMediumEmphasis); + } +} diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart index 4defbf420..76584868a 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart @@ -1,24 +1,13 @@ library; -///Dart imports -import 'dart:io'; - ///Package imports import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; ///Project imports -import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; import 'package:hms_room_kit/hms_room_kit.dart'; -import 'package:hms_room_kit/src/widgets/bottom_sheets/chat_only_bottom_sheet.dart'; -import 'package:hms_room_kit/src/widgets/tab_widgets/chat_participants_tab_bar.dart'; -import 'package:hms_room_kit/src/common/utility_components.dart'; -import 'package:hms_room_kit/src/hls_viewer/overlay_chat_component.dart'; -import 'package:hms_room_kit/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart'; import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; -import 'package:hms_room_kit/src/meeting/meeting_store.dart'; -import 'package:hms_room_kit/src/widgets/common_widgets/hms_embedded_button.dart'; ///[HLSViewerBottomNavigationBar] is the bottom navigation bar for the HLS Viewer class HLSViewerBottomNavigationBar extends StatelessWidget { @@ -33,25 +22,25 @@ class HLSViewerBottomNavigationBar extends StatelessWidget { end: Alignment.bottomCenter, colors: [Colors.black.withAlpha(0), Colors.black.withAlpha(64)])), child: Padding( - padding: EdgeInsets.only(bottom: Platform.isIOS ? 32.0 : 8), + padding: EdgeInsets.only(left: 12, right: 12), ///Here we render the chat component if the chat is opened ///We also render the leave button, hand raise button, chat button and the menu button child: Column( children: [ ///Chat Component only visible when the chat is opened - if (HMSRoomLayout.chatData != null) - Selector( - selector: (_, hlsPlayerStore) => hlsPlayerStore.isChatOpened, - builder: (_, isChatOpened, __) { - if (isChatOpened) { - Provider.of(context, listen: true) - .isNewMessageReceived = false; - } - return isChatOpened - ? const OverlayChatComponent() - : Container(); - }), + // if (HMSRoomLayout.chatData != null) + // Selector( + // selector: (_, hlsPlayerStore) => hlsPlayerStore.isChatOpened, + // builder: (_, isChatOpened, __) { + // if (isChatOpened) { + // Provider.of(context, listen: true) + // .isNewMessageReceived = false; + // } + // return isChatOpened + // ? const OverlayChatComponent() + // : Container(); + // }), ///Bottom Navigation Bar ///We render the leave button, hand raise button, chat button and the menu button @@ -60,214 +49,54 @@ class HLSViewerBottomNavigationBar extends StatelessWidget { selector: (_, hlsPlayerStore) => hlsPlayerStore.areStreamControlsVisible, builder: (_, areStreamControlsVisible, __) { - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - height: areStreamControlsVisible ? 40 : 0, - child: areStreamControlsVisible - ? Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ///Leave Button - HMSEmbeddedButton( - onTap: () async => { - await UtilityComponents.onBackPressed(context) - }, - offColor: HMSThemeColors.alertErrorDefault, - disabledBorderColor: - HMSThemeColors.alertErrorDefault, - isActive: false, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/exit_room.svg", - colorFilter: ColorFilter.mode( - HMSThemeColors.alertErrorBrighter, - BlendMode.srcIn), - semanticsLabel: "leave_room_button", - ), - ), - ), - const SizedBox( - width: 24, - ), - - ///Hand Raise Button - Selector( - selector: (_, meetingStore) => - meetingStore.isRaisedHand, - builder: (_, isRaisedHand, __) { - return HMSEmbeddedButton( - onTap: () => { - context - .read() - .toggleLocalPeerHandRaise(), - }, - enabledBorderColor: HMSThemeColors - .backgroundDim - .withAlpha(64), - onColor: HMSThemeColors.backgroundDim - .withAlpha(64), - isActive: !isRaisedHand, - child: Padding( - padding: const EdgeInsets.all(8.0), + return areStreamControlsVisible + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ///This renders the go live button + Row( + children: [ + GestureDetector( + onTap: () => {}, + child: Row( + children: [ + Padding( + padding: + const EdgeInsets.only(right: 8.0), child: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/hand_outline.svg", - colorFilter: ColorFilter.mode( - HMSThemeColors - .onSurfaceHighEmphasis, - BlendMode.srcIn), - semanticsLabel: "hand_raise_button", - ), - ), - ); - }), - const SizedBox( - width: 24, - ), - - ///Chat Button - if (HMSRoomLayout.chatData != null) - Selector( - selector: (_, hlsPlayerStore) => - hlsPlayerStore.isChatOpened, - builder: (_, isChatOpened, __) { - return HMSEmbeddedButton( - onTap: () => { - if (HMSRoomLayout - .chatData?.isOverlay ?? - false) - { - context - .read() - .toggleIsChatOpened() - } - else - { - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: - HMSThemeColors.surfaceDim, - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius.only( - topLeft: - Radius.circular( - 16), - topRight: - Radius.circular( - 16)), - ), - context: context, - builder: (ctx) => ChangeNotifierProvider - .value( - value: context.read< - MeetingStore>(), - child: HMSRoomLayout - .isParticipantsListEnabled - ? const ChatParticipantsTabBar( - tabIndex: 0, - ) - : const ChatOnlyBottomSheet()), - ) - } - }, - enabledBorderColor: HMSThemeColors - .backgroundDim - .withAlpha(64), - onColor: HMSThemeColors.backgroundDim - .withAlpha(64), - isActive: !isChatOpened, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Selector( - selector: (_, meetingStore) => - meetingStore - .isNewMessageReceived, - builder: (_, - isNewMessageReceived, __) { - return isNewMessageReceived - ? Badge( - backgroundColor: - HMSThemeColors - .primaryDefault, - child: - SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/message_badge_off.svg", - semanticsLabel: - "chat_button", - colorFilter: - ColorFilter.mode( - HMSThemeColors - .onSurfaceHighEmphasis, - BlendMode - .srcIn), - ), - ) - : SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/message_badge_off.svg", - colorFilter: - ColorFilter.mode( - HMSThemeColors - .onSurfaceHighEmphasis, - BlendMode - .srcIn), - semanticsLabel: - "chat_button", - ); - })), - ); - }), - - if (HMSRoomLayout.chatData != null) - const SizedBox( - width: 24, - ), - - ///Menu Button - if (HMSRoomLayout.isParticipantsListEnabled || - Constant.prebuiltOptions?.userName == null) - HMSEmbeddedButton( - onTap: () async => { - showModalBottomSheet( - isScrollControlled: true, - backgroundColor: - HMSThemeColors.surfaceDim, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16)), + "packages/hms_room_kit/lib/src/assets/icons/red_dot.svg"), ), - context: context, - builder: (ctx) => - ChangeNotifierProvider.value( - value: - context.read(), - child: - const HLSAppUtilitiesBottomSheet()), - ) - }, - enabledBorderColor: HMSThemeColors - .backgroundDim - .withAlpha(64), - onColor: HMSThemeColors.backgroundDim - .withAlpha(64), - isActive: true, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/menu.svg", - colorFilter: ColorFilter.mode( - HMSThemeColors - .onSurfaceHighEmphasis, - BlendMode.srcIn), - semanticsLabel: "more_button"), + HMSTitleText( + text: "LIVE", + textColor: HMSThemeColors + .onSurfaceHighEmphasis) + ], ), - ), - ], - ) - : Container(), - ); + ) + ], + ), + + ///This renders the minimize/maximize button + ///to toggle the full screen mode + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.isFullScreen, + builder: (_, isFullScreen, __) { + return InkWell( + onTap: () => context + .read() + .toggleFullScreen(), + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/${isFullScreen ? "minimize" : "maximize"}.svg"), + ); + }) + ]) + ], + ) + : const SizedBox(); }), ], ), diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_header.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_header.dart index b38c21f69..121f4dcd3 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_header.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_header.dart @@ -1,27 +1,18 @@ library; -///Dart imports -import 'dart:developer'; -import 'dart:io'; - ///Package imports import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; ///Project imports -import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; +import 'package:hms_room_kit/src/common/utility_components.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; import 'package:hms_room_kit/src/layout_api/hms_theme_colors.dart'; -import 'package:hms_room_kit/src/meeting/meeting_store.dart'; -import 'package:hms_room_kit/src/widgets/common_widgets/hms_title_text.dart'; -import 'package:hms_room_kit/src/common/utility_functions.dart'; -import 'package:hms_room_kit/src/widgets/common_widgets/live_badge.dart'; ///[HLSViewerHeader] is the header of the HLS Viewer screen class HLSViewerHeader extends StatelessWidget { - const HLSViewerHeader({super.key}); + final bool hasHLSStarted; + const HLSViewerHeader({super.key, required this.hasHLSStarted}); @override Widget build(BuildContext context) { @@ -35,165 +26,81 @@ class HLSViewerHeader extends StatelessWidget { HMSThemeColors.backgroundDim.withAlpha(0) ])), child: Padding( - padding: - EdgeInsets.only(left: 15, right: 15, top: Platform.isIOS ? 55 : 45), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - ///We render the logo as set in the dashboard - HMSRoomLayout.roleLayoutData?.logo?.url == null - ? Container() - : HMSRoomLayout.roleLayoutData!.logo!.url!.contains("svg") - ? SvgPicture.network( - HMSRoomLayout.roleLayoutData!.logo!.url!, - height: 30, - width: 30, - ) - : Image.network( - HMSRoomLayout.roleLayoutData!.logo!.url!, - errorBuilder: (context, exception, _) { - log('Error is $exception'); - return const SizedBox( - width: 30, - height: 30, - ); - }, - height: 30, - width: 30, - ), - const SizedBox( - width: 12, - ), - - ///We render the LIVE icon based on the HLS streaming status - ///If the HLS streaming is started we show the LIVE icon - ///If the HLS streaming is not started we show nothing - Selector( - selector: (_, meetingStore) => - (meetingStore.streamingType['hls'] == - HMSStreamingState.started || - meetingStore.streamingType['rtmp'] == - HMSStreamingState.started), - builder: (_, isHLSStarted, __) { - return isHLSStarted ? const LiveBadge() : Container(); - }), - const SizedBox( - width: 8, - ), - - ///We render the recording icon based on the recording status - ///If the recording is started we show the recording icon - ///If the recording is not started we show nothing - /// - ///If recording initialising state is true we show the loader - Selector< - MeetingStore, - Tuple3>( - selector: (_, meetingStore) => Tuple3( - meetingStore.recordingType["browser"] ?? - HMSRecordingState.none, - meetingStore.recordingType["server"] ?? - HMSRecordingState.none, - meetingStore.recordingType["hls"] ?? - HMSRecordingState.none), - builder: (_, data, __) { - return (data.item1 == HMSRecordingState.started || - data.item1 == HMSRecordingState.resumed || - data.item2 == HMSRecordingState.started || - data.item2 == HMSRecordingState.resumed || - data.item3 == HMSRecordingState.started || - data.item3 == HMSRecordingState.resumed) - ? SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/record.svg", - height: 24, - width: 24, - colorFilter: ColorFilter.mode( - HMSThemeColors.alertErrorDefault, - BlendMode.srcIn), + padding: EdgeInsets.only(left: 12, right: 12), + child: Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.areStreamControlsVisible, + builder: (_, areStreamControlsVisible, __) { + return areStreamControlsVisible + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ///This renders the [Close Button] and is always visible iff the controls are visible + IconButton( + icon: Icon( + Icons.close, + color: HMSThemeColors.onSurfaceHighEmphasis, + size: 24, + ), + onPressed: () { + UtilityComponents.onBackPressed(context); + }, ) - : (data.item1 == HMSRecordingState.starting || - data.item2 == HMSRecordingState.starting || - data.item3 == HMSRecordingState.starting) - ? SizedBox( - height: 24, - width: 24, - child: CircularProgressIndicator( - strokeWidth: 1, - color: HMSThemeColors.onSurfaceHighEmphasis, - )) - : (data.item1 == HMSRecordingState.paused || - data.item2 == HMSRecordingState.paused || - data.item3 == HMSRecordingState.paused) - ? SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/recording_paused.svg", - height: 24, - width: 24, - colorFilter: ColorFilter.mode( - HMSThemeColors.onSurfaceHighEmphasis, - BlendMode.srcIn), - ) - : Container(); - }), - const SizedBox( - width: 8, - ), + ], + ), + + ///This renders the [Caption Button] and [Settings Button] only if the controls are visible + ///and the HLS has started + // if (hasHLSStarted) + // Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [ + // ///The caption button is only rendered when closed captions are supported + // ///and the HLS has started + // Selector>( + // selector: (_, hlsPlayerStore) => Tuple2( + // hlsPlayerStore.isCaptionEnabled, + // hlsPlayerStore.areCaptionsSupported), + // builder: (_, captionsData, __) { + // return captionsData.item2 + // ? InkWell( + // onTap: () { + // context + // .read() + // .toggleCaptions(); + // }, + // child: SvgPicture.asset( + // "packages/hms_room_kit/lib/src/assets/icons/caption_${captionsData.item1 ? "on" : "off"}.svg", + // colorFilter: ColorFilter.mode( + // HMSThemeColors + // .onSurfaceHighEmphasis, + // BlendMode.srcIn), + // semanticsLabel: + // "caption_toggle_button", + // ), + // ) + // : const SizedBox(); + // }), + // const SizedBox( + // width: 16, + // ), - ///This renders the number of peers - ///If the HLS or RTMP streaming is started, we render the number of peers - ///else we render an empty Container - Selector>( - selector: (_, meetingStore) => Tuple2( - meetingStore.streamingType['hls'] == - HMSStreamingState.started || - meetingStore.streamingType['rtmp'] == - HMSStreamingState.started, - meetingStore.peersInRoom), - builder: (_, data, __) { - return data.item1 - ? Container( - width: 59, - height: 24, - decoration: BoxDecoration( - border: Border.all( - color: HMSThemeColors.borderBright, - width: 1), - borderRadius: const BorderRadius.all( - Radius.circular(4)), - color: HMSThemeColors.backgroundDim - .withOpacity(0.64)), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/watching.svg", - width: 16, - height: 16, - colorFilter: ColorFilter.mode( - HMSThemeColors.onSurfaceHighEmphasis, - BlendMode.srcIn), - semanticsLabel: "fl_watching", - ), - const SizedBox( - width: 4, - ), - HMSTitleText( - text: Utilities.formatNumber(data.item2), - fontSize: 10, - lineHeight: 10, - letterSpacing: 1.5, - textColor: - HMSThemeColors.onSurfaceHighEmphasis) - ], - )) - : Container(); - }) - ], - ), - ], - ), + // ///This renders the settings button + // SvgPicture.asset( + // "packages/hms_room_kit/lib/src/assets/icons/settings.svg", + // colorFilter: ColorFilter.mode( + // HMSThemeColors.onSurfaceHighEmphasis, + // BlendMode.srcIn), + // semanticsLabel: "caption_toggle_button", + // ) + // ], + // ) + ], + ) + : const SizedBox(); + }), ), ); } diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_page.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_page.dart index 75ad91091..73bfda9fe 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_page.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_viewer_page.dart @@ -5,26 +5,24 @@ import 'dart:math'; ///Package imports import 'package:flutter/material.dart'; -import 'package:hms_room_kit/src/widgets/toasts/toast_widget.dart'; import 'package:provider/provider.dart'; import 'package:tuple/tuple.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; ///Project imports +import 'package:hms_room_kit/src/widgets/bottom_sheets/refresh_stream_bottom_sheet.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_player_desktop_controls.dart'; +import 'package:hms_room_kit/src/widgets/toasts/toast_widget.dart'; import 'package:hms_room_kit/src/widgets/app_dialogs/audio_device_change_dialog.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_left_room_screen.dart'; import 'package:hms_room_kit/src/widgets/toasts/hms_toast_model.dart'; -import 'package:hms_room_kit/src/hls_viewer/hls_viewer_bottom_navigation_bar.dart'; -import 'package:hms_room_kit/src/hls_viewer/hls_viewer_header.dart'; import 'package:hms_room_kit/src/layout_api/hms_theme_colors.dart'; import 'package:hms_room_kit/src/preview_for_role/preview_for_role_bottom_sheet.dart'; import 'package:hms_room_kit/src/preview_for_role/preview_for_role_header.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_circular_avatar.dart'; import 'package:hms_room_kit/src/common/utility_components.dart'; -import 'package:hms_room_kit/src/common/utility_functions.dart'; import 'package:hms_room_kit/src/hls_viewer/hls_player.dart'; import 'package:hms_room_kit/src/hls_viewer/hls_player_store.dart'; -import 'package:hms_room_kit/src/hls_viewer/hls_waiting_ui.dart'; import 'package:hms_room_kit/src/meeting/meeting_store.dart'; ///[HLSViewerPage] is the page that is used to render the HLS Viewer @@ -37,6 +35,10 @@ class HLSViewerPage extends StatefulWidget { } class _HLSViewerPageState extends State { + ///These variables keep track of height and width of the screen + double height = 0.0; + double width = 0.0; + @override void initState() { super.initState(); @@ -44,10 +46,19 @@ class _HLSViewerPageState extends State { ///We start the timer to hide the controls WidgetsBinding.instance.addPostFrameCallback((_) { context.read().startTimerToHideButtons(); + HMSHLSPlayerController.addHMSHLSPlaybackEventsListener( + context.read()); }); } } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + height = MediaQuery.of(context).size.height; + width = MediaQuery.of(context).size.width; + } + ///This function is used to set the stream status void _setStreamStatus(bool hasHlsStarted) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { @@ -55,6 +66,13 @@ class _HLSViewerPageState extends State { }); } + @override + void deactivate() { + HMSHLSPlayerController.removeHMSHLSPlaybackEventsListener( + context.read()); + super.deactivate(); + } + @override Widget build(BuildContext context) { return WillPopScope( @@ -83,18 +101,18 @@ class _HLSViewerPageState extends State { builder: (_, isPipActive, __) { return isPipActive ? HMSHLSPlayer() - : Scaffold( - backgroundColor: HMSThemeColors.backgroundDim, - body: Theme( - data: ThemeData( - brightness: Brightness.dark, - primaryColor: HMSThemeColors.primaryDefault, - scaffoldBackgroundColor: - HMSThemeColors.backgroundDim), - child: SingleChildScrollView( + : SafeArea( + child: Scaffold( + backgroundColor: HMSThemeColors.backgroundDim, + body: Theme( + data: ThemeData( + brightness: Brightness.dark, + primaryColor: HMSThemeColors.primaryDefault, + scaffoldBackgroundColor: + HMSThemeColors.backgroundDim), child: SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, + width: width, + height: height, child: Stack( children: [ Selector( @@ -102,69 +120,40 @@ class _HLSViewerPageState extends State { meetingStore.hasHlsStarted, builder: (_, hasHlsStarted, __) { _setStreamStatus(hasHlsStarted); - return (hasHlsStarted) - ? SizedBox( - width: MediaQuery.of(context) - .size - .width, - height: MediaQuery.of(context) - .size - .height, - child: HLSPlayer( - key: Key(context - .read< - MeetingStore>() - .localPeer - ?.peerId ?? - "HLS_PLAYER"), - ratio: Utilities - .getHLSPlayerDefaultRatio( - MediaQuery.of( - context) - .size), - ), - ) - : SizedBox( - width: MediaQuery.of(context) - .size - .width, - height: MediaQuery.of(context) - .size - .height, - child: const HLSWaitingUI()); - }), - ///Will only be displayed when the controls are visible - SizedBox( - height: - MediaQuery.of(context).size.height, - child: Column( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Selector( + return Selector( selector: (_, hlsPlayerStore) => - hlsPlayerStore - .areStreamControlsVisible, - builder: (_, - areStreamControlsVisible, - __) { - return AnimatedContainer( - duration: const Duration( - milliseconds: 200), - height: - areStreamControlsVisible - ? 100 - : 0, - child: areStreamControlsVisible - ? const HLSViewerHeader() - : Container(), + hlsPlayerStore.isFullScreen, + builder: (_, isFullScreen, __) { + double widgetHeight = height - + MediaQuery.of(context) + .viewPadding + .top - + MediaQuery.of(context) + .viewPadding + .bottom; + return Column( + mainAxisAlignment: + isFullScreen + ? MainAxisAlignment + .center + : MainAxisAlignment + .start, + children: [ + ///Renders HLS Player + SizedBox( + width: width, + height: isFullScreen + ? widgetHeight + : widgetHeight * 0.27, + child: const HLSPlayer(), + ), + if (!isFullScreen) + HLSPlayerDesktopControls() + ], ); - }), - const HLSViewerBottomNavigationBar() - ], - ), - ), + }); + }), ///This renders the preview for role component Selector< @@ -282,6 +271,7 @@ class _HLSViewerPageState extends State { return Container(); }), + ///This renders toasts Selector( selector: (_, meetingStore) => @@ -390,6 +380,7 @@ class _HLSViewerPageState extends State { }).toList()); }), + ///This renders the reconnection dialog Selector( selector: (_, meetingStore) => meetingStore.reconnecting, @@ -401,6 +392,21 @@ class _HLSViewerPageState extends State { } return const SizedBox(); }), + + ///This renders the bottom sheet for the stream error + ///with a button refresh the stream + Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore.isPlayerFailed, + builder: (_, isPlayerFailed, __) { + return Positioned( + bottom: 0, + child: isPlayerFailed?RefreshStreamBottomSheet():const SizedBox()); + }, + ), + + ///Renders the error toast with a leave button since the + ///error is irrecoverable if (failureData.item2 != null && (failureData.item2?.code?.errorCode == 1003 || diff --git a/packages/hms_room_kit/lib/src/hls_viewer/hls_waiting_ui.dart b/packages/hms_room_kit/lib/src/hls_viewer/hls_waiting_ui.dart index dd12f6881..15a0a4311 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/hls_waiting_ui.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/hls_waiting_ui.dart @@ -15,40 +15,43 @@ class HLSWaitingUI extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircleAvatar( - radius: 50, - backgroundColor: HMSThemeColors.surfaceDefault, - child: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/live.svg", - height: 56, - width: 56, - colorFilter: ColorFilter.mode( - HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), + return SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + radius: 50, + backgroundColor: HMSThemeColors.surfaceDefault, + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/live.svg", + height: 56, + width: 56, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), + ), ), - ), - const SizedBox( - height: 24, - ), - HMSTitleText( - text: "Stream yet to start", - textColor: HMSThemeColors.onSurfaceHighEmphasis, - fontSize: 24, - lineHeight: 32, - letterSpacing: 0.25, - ), - const SizedBox( - height: 8, - ), - HMSSubheadingText( - text: "Sit back and relax", - fontSize: 16, - lineHeight: 24, - letterSpacing: 0.5, - textColor: HMSThemeColors.onSurfaceMediumEmphasis) - ], + const SizedBox( + height: 24, + ), + HMSTitleText( + text: "Stream yet to start", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + fontSize: 24, + lineHeight: 32, + letterSpacing: 0.25, + ), + const SizedBox( + height: 8, + ), + HMSSubheadingText( + text: "Sit back and relax", + fontSize: 16, + lineHeight: 24, + letterSpacing: 0.5, + textColor: HMSThemeColors.onSurfaceMediumEmphasis) + ], + ), ); } } diff --git a/packages/hms_room_kit/lib/src/layout_api/hms_conferencing_items.dart b/packages/hms_room_kit/lib/src/layout_api/hms_conferencing_items.dart index 9c630c695..6ae2eefe4 100644 --- a/packages/hms_room_kit/lib/src/layout_api/hms_conferencing_items.dart +++ b/packages/hms_room_kit/lib/src/layout_api/hms_conferencing_items.dart @@ -77,7 +77,32 @@ class HlsLiveStreaming { } } +class Header { + String? title; + String? description; + + Header({this.title, this.description}); + + Header.fromJson(Map? json) { + if (json == null) { + title = null; + description = null; + return; + } + title = json['title']; + description = json['description']; + } + + Map toJson() { + final Map data = {}; + data['title'] = title; + data['description'] = description; + return data; + } +} + class Elements { + Header? header; Chat? chat; Map? participantList; VideoTileLayout? videoTileLayout; @@ -86,7 +111,8 @@ class Elements { Map? brb; Elements( - {this.chat, + {this.header, + this.chat, this.participantList, this.videoTileLayout, this.emojiReactions, @@ -95,6 +121,7 @@ class Elements { Elements.fromJson(Map? json) { if (json == null) { + header = null; chat = null; participantList = null; videoTileLayout = null; @@ -103,6 +130,10 @@ class Elements { brb = null; return; } + + header = (json.containsKey('header') && json['header'] != null) + ? Header.fromJson(json['header']) + : null; chat = (json.containsKey('chat') && json['chat'] != null) ? Chat.fromJson(json['chat']) : null; diff --git a/packages/hms_room_kit/lib/src/meeting/meeting_bottom_navigation_bar.dart b/packages/hms_room_kit/lib/src/meeting/meeting_bottom_navigation_bar.dart index f291cf09c..af3ed5d78 100644 --- a/packages/hms_room_kit/lib/src/meeting/meeting_bottom_navigation_bar.dart +++ b/packages/hms_room_kit/lib/src/meeting/meeting_bottom_navigation_bar.dart @@ -1,6 +1,6 @@ -///Package imports library; +///Package imports import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; @@ -8,7 +8,7 @@ import 'package:tuple/tuple.dart'; ///Project imports import 'package:hms_room_kit/src/meeting/meeting_navigation_visibility_controller.dart'; -import 'package:hms_room_kit/src/hls_viewer/overlay_chat_component.dart'; +import 'package:hms_room_kit/src/widgets/chat_widgets/overlay_chat_component.dart'; import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; import 'package:hms_room_kit/src/widgets/bottom_sheets/chat_only_bottom_sheet.dart'; import 'package:hms_room_kit/src/widgets/tab_widgets/chat_participants_tab_bar.dart'; diff --git a/packages/hms_room_kit/lib/src/meeting/meeting_store.dart b/packages/hms_room_kit/lib/src/meeting/meeting_store.dart index caecb2613..c205caf92 100644 --- a/packages/hms_room_kit/lib/src/meeting/meeting_store.dart +++ b/packages/hms_room_kit/lib/src/meeting/meeting_store.dart @@ -218,12 +218,6 @@ class MeetingStore extends ChangeNotifier String? spotlightMetadata; - ///HLS Player Stats - - HMSHLSPlayerStats? hlsPlayerStats; - - bool isHLSStatsEnabled = false; - bool isDefaultAspectRatioSelected = true; int currentPage = 0; @@ -2704,6 +2698,9 @@ class MeetingStore extends ChangeNotifier notifyListeners(); } + @override + void onVideoSizeChanged({required Size size}) {} + @override void onCue({required HMSHLSCue hlsCue}) { log("onCue -> payload:${hlsCue.startDate}"); @@ -2758,36 +2755,16 @@ class MeetingStore extends ChangeNotifier } @override - void onPlaybackFailure({required String? error}) { - Utilities.showToast("Playback failure $error"); - } + void onPlaybackFailure({required String? error}) {} @override - void onPlaybackStateChanged({required HMSHLSPlaybackState playbackState}) { - Utilities.showToast("Playback state changed to ${playbackState.name}"); - } + void onPlaybackStateChanged({required HMSHLSPlaybackState playbackState}) {} @override - void onHLSError({required HMSException hlsException}) { - // TODO: implement onHLSError - } + void onHLSError({required HMSException hlsException}) {} @override - void onHLSEventUpdate({required HMSHLSPlayerStats playerStats}) { - log("onHLSEventUpdate-> bitrate:${playerStats.averageBitrate} buffered duration: ${playerStats.bufferedDuration}"); - hlsPlayerStats = playerStats; - notifyListeners(); - } - - void setHLSPlayerStats(bool value) { - isHLSStatsEnabled = value; - if (!value) { - HMSHLSPlayerController.removeHLSStatsListener(); - } else { - HMSHLSPlayerController.addHLSStatsListener(); - } - notifyListeners(); - } + void onHLSEventUpdate({required HMSHLSPlayerStats playerStats}) {} ///Insert poll question void insertPollQuestion(HMSPollStore store) { diff --git a/packages/hms_room_kit/lib/src/preview/preview_bottom_button_section.dart b/packages/hms_room_kit/lib/src/preview/preview_bottom_button_section.dart index 4dae03d6b..7ade631bf 100644 --- a/packages/hms_room_kit/lib/src/preview/preview_bottom_button_section.dart +++ b/packages/hms_room_kit/lib/src/preview/preview_bottom_button_section.dart @@ -102,7 +102,15 @@ class PreviewBottomButtonSection extends StatelessWidget { ), Row( children: [ - if (previewStore.isNoiseCancellationAvailable && + ///This renders the [Noise Cancellation Button] only if the + ///Peer role has the permission to publish audio + ///and the Peer is not null + ///and the noise cancellation is available + ///and mic is unmuted + if ((previewStore.peer?.role.publishSettings?.allowed + .contains("audio") ?? + false) && + previewStore.isNoiseCancellationAvailable && previewStore.isAudioOn) Padding( padding: const EdgeInsets.only(right: 8.0), diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_bottom_sheet.dart index 8286a0368..d7d66bf06 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_bottom_sheet.dart @@ -1,5 +1,6 @@ //Package imports import 'package:flutter/material.dart'; +import 'package:hms_room_kit/src/hls_viewer/hls_hand_raise_menu.dart'; import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -20,7 +21,8 @@ import 'package:hms_room_kit/src/widgets/toasts/hms_toasts_type.dart'; ///[ChatBottomSheet] is a bottom sheet that is used to render the bottom sheet for chat class ChatBottomSheet extends StatefulWidget { - const ChatBottomSheet({super.key}); + final bool isHLSChat; + const ChatBottomSheet({super.key, this.isHLSChat = false}); @override State createState() => _ChatBottomSheetState(); @@ -110,106 +112,133 @@ class _ChatBottomSheetState extends State { return true; }, child: SafeArea( - child: Stack( - children: [ - Padding( - padding: MediaQuery.of(context).viewInsets, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 15, - ), - Selector, int, List, int>>( - selector: (_, meetingStore) => Tuple4( - meetingStore.messages, - meetingStore.messages.length, - meetingStore.pinnedMessages, - meetingStore.pinnedMessages.length), - builder: (context, data, _) { - _scrollToEnd(); - return - - ///If there are no chats and no pinned messages - (data.item2 == 0 && data.item3.isEmpty) - ? Expanded( - child: SingleChildScrollView( - physics: - const NeverScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: MediaQuery.of(context) - .size - .height * - 0.6, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: widget.isHLSChat ? 16 : 0), + child: Stack( + children: [ + Padding( + padding: MediaQuery.of(context).viewInsets, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + Selector, int, List, int>>( + selector: (_, meetingStore) => Tuple4( + meetingStore.messages, + meetingStore.messages.length, + meetingStore.pinnedMessages, + meetingStore.pinnedMessages.length), + builder: (context, data, _) { + _scrollToEnd(); + return + + ///If there are no chats and no pinned messages + (data.item2 == 0 && data.item3.isEmpty) + ? Expanded( + child: SingleChildScrollView( + physics: + const NeverScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: MediaQuery.of(context) + .size + .height * + 0.5, + ), + child: const HMSEmptyChatWidget()))) + : Expanded( + child: Column(children: [ + const PinChatWidget(), + + /// List containing chats + Expanded( + child: SingleChildScrollView( + reverse: true, + child: Column( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + ListView.builder( + controller: _scrollController, + shrinkWrap: true, + itemCount: data.item1.length, + itemBuilder: (_, index) { + return MessageContainer( + isHLSChat: + widget.isHLSChat, + message: + data.item1[index], + ); + }), + ], ), - child: const HMSEmptyChatWidget()))) - : Expanded( - child: Column(children: [ - const PinChatWidget(), - - /// List containing chats - Expanded( - child: SingleChildScrollView( - reverse: true, - child: Column( - mainAxisAlignment: - MainAxisAlignment.end, - children: [ - ListView.builder( - controller: _scrollController, - shrinkWrap: true, - itemCount: data.item1.length, - itemBuilder: (_, index) { - return MessageContainer( - message: data.item1[index], - ); - }), - ], ), - ), - ) - ]), - ); - }, - ), - - /// This draws the chip to select the roles or peers to send message to - if ((HMSRoomLayout.chatData?.isPrivateChatEnabled ?? false) || - (HMSRoomLayout.chatData?.isPublicChatEnabled ?? false) || - (HMSRoomLayout.chatData?.rolesWhitelist.isNotEmpty ?? - false)) - ReceipientSelectorChip( - currentlySelectedValue: currentlySelectedValue, - updateSelectedValue: _updateValueChoose), - - ///Text Field - if ((HMSRoomLayout.chatData?.isPrivateChatEnabled ?? false) || - (HMSRoomLayout.chatData?.isPublicChatEnabled ?? false) || - (HMSRoomLayout.chatData?.rolesWhitelist.isNotEmpty ?? - false)) - ChatTextField(sendMessage: sendMessage) - ], + ) + ]), + ); + }, + ), + + /// This draws the chip to select the roles or peers to send message to + if ((HMSRoomLayout.chatData?.isPrivateChatEnabled ?? + false) || + (HMSRoomLayout.chatData?.isPublicChatEnabled ?? + false) || + (HMSRoomLayout.chatData?.rolesWhitelist.isNotEmpty ?? + false)) + ReceipientSelectorChip( + currentlySelectedValue: currentlySelectedValue, + updateSelectedValue: _updateValueChoose), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + ///Text Field + if ((HMSRoomLayout.chatData?.isPrivateChatEnabled ?? + false) || + (HMSRoomLayout.chatData?.isPublicChatEnabled ?? + false) || + (HMSRoomLayout + .chatData?.rolesWhitelist.isNotEmpty ?? + false)) + Expanded( + child: Row( + children: [ + ChatTextField( + sendMessage: sendMessage, + isHLSChat: widget.isHLSChat, + ), + ], + ), + ), + if (widget.isHLSChat) HLSHandRaiseMenu() + ], + ) + ], + ), ), - ), - Selector, int>>( - selector: (_, meetingStore) => - Tuple2(meetingStore.toasts, meetingStore.toasts.length), - builder: (_, data, __) { - int errorToastIndex = data.item1.indexWhere((element) => - element.hmsToastType == HMSToastsType.errorToast); - - return (errorToastIndex != -1) - ? HMSErrorToast( - error: data.item1[errorToastIndex].toastData, - meetingStore: context.read(), - toastColor: HMSThemeColors.surfaceDefault, - ) - : const SizedBox(); - }) - ], + Selector, int>>( + selector: (_, meetingStore) => + Tuple2(meetingStore.toasts, meetingStore.toasts.length), + builder: (_, data, __) { + int errorToastIndex = data.item1.indexWhere((element) => + element.hmsToastType == HMSToastsType.errorToast); + + return (errorToastIndex != -1) + ? HMSErrorToast( + error: data.item1[errorToastIndex].toastData, + meetingStore: context.read(), + toastColor: HMSThemeColors.surfaceDefault, + ) + : const SizedBox(); + }) + ], + ), ), ), ); diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart index 8d8c42d20..e1eef8af9 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart @@ -15,7 +15,6 @@ import 'package:hms_room_kit/src/widgets/common_widgets/hms_cross_button.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/more_option_item.dart'; import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; import 'package:hms_room_kit/src/widgets/bottom_sheets/overlay_participants_bottom_sheet.dart'; -import 'package:hms_room_kit/src/widgets/tab_widgets/chat_participants_tab_bar.dart'; ///[HLSAppUtilitiesBottomSheet] is a bottom sheet that is used to show more options in the meeting class HLSAppUtilitiesBottomSheet extends StatefulWidget { @@ -100,13 +99,7 @@ class _HLSMoreOptionsBottomSheetBottomSheetState context: context, builder: (ctx) => ChangeNotifierProvider.value( value: meetingStore, - child: (HMSRoomLayout.chatData == null || - (HMSRoomLayout.chatData?.isOverlay ?? - true)) - ? const OverlayParticipantsBottomSheet() - : const ChatParticipantsTabBar( - tabIndex: 1, - )), + child: const OverlayParticipantsBottomSheet()), ); }, optionIcon: badge.Badge( diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/leave_session_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/leave_session_bottom_sheet.dart index b85347fb4..11cb839a6 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/leave_session_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/leave_session_bottom_sheet.dart @@ -1,11 +1,17 @@ +library; + +///Package imports import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +///Project imports import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/layout_api/hms_room_layout.dart'; import 'package:hms_room_kit/src/meeting/meeting_store.dart'; import 'package:hms_room_kit/src/widgets/bottom_sheets/end_service_bottom_sheet.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/leave_session_tile.dart'; -import 'package:provider/provider.dart'; class LeaveSessionBottomSheet extends StatefulWidget { final MeetingStore meetingStore; @@ -29,6 +35,8 @@ class _LeaveSessionBottomSheetState extends State { super.deactivate(); } + ///Here we render bottom sheet with leave and end options + @override Widget build(BuildContext context) { return ((widget.meetingStore.localPeer?.role.permissions.endRoom ?? @@ -74,7 +82,8 @@ class _LeaveSessionBottomSheetState extends State { widget.meetingStore.leave(), }, title: HMSTitleText( - text: "Leave Session", + text: + "Leave ${HMSRoomLayout.peerType == PeerRoleType.conferencing ? "Session" : "Stream"}", textColor: HMSThemeColors.alertErrorDefault, letterSpacing: 0.15, fontSize: 20, @@ -89,11 +98,12 @@ class _LeaveSessionBottomSheetState extends State { ), subTitle: HMSSubheadingText( text: - "Others will continue after you leave. You can join\n the session again.", + "Others will continue after you leave. You can join the session again.", maxLines: 2, textColor: HMSThemeColors.onSurfaceMediumEmphasis, ), - buttonText: "Leave Session", + buttonText: + "Leave ${HMSRoomLayout.peerType == PeerRoleType.conferencing ? "Session" : "Stream"}", ), ), ) @@ -200,7 +210,8 @@ class _LeaveSessionBottomSheetState extends State { widget.meetingStore.leave(), }, title: HMSTitleText( - text: "Leave Session", + text: + "Leave ${HMSRoomLayout.peerType == PeerRoleType.conferencing ? "Session" : "Stream"}", textColor: HMSThemeColors.alertErrorDefault, letterSpacing: 0.15, fontSize: 20, @@ -214,11 +225,12 @@ class _LeaveSessionBottomSheetState extends State { ), subTitle: HMSSubheadingText( text: - "Others will continue after you leave. You can join\n the session again.", + "Others will continue after you leave. You can join the session again.", maxLines: 2, textColor: HMSThemeColors.onSurfaceMediumEmphasis, ), - buttonText: "Leave Session", + buttonText: + "Leave ${HMSRoomLayout.peerType == PeerRoleType.conferencing ? "Session" : "Stream"}", ), ); } diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_bottom_sheet.dart index 08879449e..6709937d8 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_bottom_sheet.dart @@ -242,7 +242,7 @@ class _ParticipantsBottomSheetState extends State { ), if (mutePermission && peerTrackNode != null && - !peerTrackNode.peer.isLocal) + !peerTrackNode.peer.isLocal && peerTrackNode.peer.type == HMSPeerType.regular) PopupMenuItem( value: 3, child: Row(children: [ @@ -271,7 +271,7 @@ class _ParticipantsBottomSheetState extends State { ), if (mutePermission && peerTrackNode != null && - !peerTrackNode.peer.isLocal) + !peerTrackNode.peer.isLocal && peerTrackNode.peer.type == HMSPeerType.regular) PopupMenuItem( value: 4, child: Row(children: [ diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/refresh_stream_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/refresh_stream_bottom_sheet.dart new file mode 100644 index 000000000..f09159132 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/refresh_stream_bottom_sheet.dart @@ -0,0 +1,118 @@ +library; + +///Package imports +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:provider/provider.dart'; + +///Project imports +import 'package:hms_room_kit/src/layout_api/hms_theme_colors.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_title_text.dart'; + + +///[RefreshStreamBottomSheet] is a bottom sheet that is used to render the bottom sheet to refresh the stream +class RefreshStreamBottomSheet extends StatefulWidget { + const RefreshStreamBottomSheet({super.key}); + + @override + State createState() => + _RefreshStreamBottomSheetState(); +} + +class _RefreshStreamBottomSheetState extends State { + @override + void initState() { + super.initState(); + context.read().addBottomSheet(context); + } + + @override + void deactivate() { + context.read().removeBottomSheet(context); + super.deactivate(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 192, + decoration: BoxDecoration( + color: HMSThemeColors.surfaceDim, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), topRight: Radius.circular(16)), + ), + child: Padding( + padding: const EdgeInsets.only(top: 16.0, left: 20, right: 20), + child: Container( + width: MediaQuery.of(context).size.width - 40, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/end_warning.svg", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.alertErrorDefault, BlendMode.srcIn), + ), + const SizedBox( + width: 8, + ), + HMSTitleText( + text: "Playback Error", + textColor: HMSThemeColors.alertErrorDefault, + letterSpacing: 0.15, + fontSize: 20, + ) + ], + ), + ], + ), + const SizedBox( + height: 8, + ), + HMSSubheadingText( + text: + "Something went wrong with the stream. Please tap on ‘Refresh Stream’ to retry.", + maxLines: 3, + textColor: HMSThemeColors.onSurfaceMediumEmphasis, + ), + const SizedBox( + height: 16, + ), + ElevatedButton( + style: ButtonStyle( + shadowColor: + MaterialStateProperty.all(HMSThemeColors.surfaceDim), + backgroundColor: MaterialStateProperty.all( + HMSThemeColors.primaryDefault), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () { + HMSHLSPlayerController.start(); + }, + child: SizedBox( + height: 48, + child: Center( + child: HMSTitleText( + text: "Refresh Stream", + textColor: HMSThemeColors.baseWhite), + ), + )) + ], + ), + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/remote_peer_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/remote_peer_bottom_sheet.dart index 8ed1fec2e..3a10f7678 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/remote_peer_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/remote_peer_bottom_sheet.dart @@ -158,8 +158,8 @@ class _RemotePeerBottomSheetState extends State { // text: "Spotlight Tile for Everyone", // textColor: HMSThemeColors.onSurfaceHighEmphasis)), - if (widget.meetingStore.localPeer?.role.permissions.mute ?? - false) + if ((widget.meetingStore.localPeer?.role.permissions.mute ?? + false) && widget.peerTrackNode.peer.type == HMSPeerType.regular) ListTile( horizontalTitleGap: 2, onTap: () async { diff --git a/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_field.dart b/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_field.dart index cf25706c5..30fbdc3c8 100644 --- a/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_field.dart +++ b/packages/hms_room_kit/lib/src/widgets/chat_widgets/chat_text_field.dart @@ -23,8 +23,12 @@ import 'package:hms_room_kit/src/widgets/toasts/hms_toast_button.dart'; class ChatTextField extends StatefulWidget { final Function sendMessage; final Color? toastBackgroundColor; + final bool isHLSChat; const ChatTextField( - {Key? key, required this.sendMessage, this.toastBackgroundColor}) + {Key? key, + required this.sendMessage, + this.toastBackgroundColor, + this.isHLSChat = false}) : super(key: key); @override @@ -48,211 +52,212 @@ class _ChatTextFieldState extends State { @override Widget build(BuildContext context) { - return Selector>>( + double width = MediaQuery.of(context).size.width - 32; + return SizedBox( + height: 40, + width: widget.isHLSChat ? width * 0.7 : width, + child: Selector>>( - ///item1: whether chat is resumed or not - ///item2: number of blacklisted users - ///item3: list of blacklisted user ids - selector: (_, meetingStore) => Tuple3( - meetingStore.chatControls["enabled"], - meetingStore.blackListedUserIds.length, - meetingStore.blackListedUserIds), - builder: (_, chatControls, __) { - return chatControls.item1 - ? Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), - child: chatControls.item3.contains(context - .read() - .localPeer - ?.customerUserId) - ? Row( - children: [ - Expanded( - child: Container( - color: widget.toastBackgroundColor ?? - HMSThemeColors.surfaceDefault, - height: 36, - child: Center( - child: HMSSubheadingText( - text: - "You’ve been blocked from sending messages", - textColor: HMSThemeColors - .onSurfaceMediumEmphasis), - ), + ///item1: whether chat is resumed or not + ///item2: number of blacklisted users + ///item3: list of blacklisted user ids + selector: (_, meetingStore) => Tuple3( + meetingStore.chatControls["enabled"], + meetingStore.blackListedUserIds.length, + meetingStore.blackListedUserIds), + builder: (_, chatControls, __) { + return chatControls.item1 + + ///If chat is not paused we render the text field + ///else we render the paused chat toast + ? Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: chatControls.item3.contains(context + .read() + .localPeer + ?.customerUserId) + + ///If the user is blocked from sending messages + ? Row( + children: [ + Expanded( + child: Container( + color: widget.toastBackgroundColor ?? + HMSThemeColors.surfaceDefault, + height: 36, + child: Center( + child: HMSSubheadingText( + text: + "You’ve been blocked from sending messages", + textColor: HMSThemeColors + .onSurfaceMediumEmphasis), ), ), - ], - ) - : Row( - children: [ - Expanded( - child: Container( - color: HMSThemeColors.surfaceDefault, - child: Selector( - selector: (_, meetingStore) => - meetingStore.recipientSelectorValue, - builder: (_, selectedValue, __) { - return TextField( - enabled: selectedValue != - "Choose a Recipient", - textCapitalization: - TextCapitalization.sentences, - textInputAction: - TextInputAction.send, - onTapOutside: (event) => - FocusManager - .instance.primaryFocus - ?.unfocus(), - onSubmitted: (value) { - widget.sendMessage( - messageTextController); - messageTextController.clear(); - }, - onChanged: (value) { - setState(() {}); - }, - style: HMSTextStyle.setTextStyle( - color: HMSThemeColors - .onSurfaceHighEmphasis, - fontWeight: FontWeight.w400, - height: 20 / 14, - fontSize: 14, - letterSpacing: 0.25), - controller: messageTextController, - decoration: InputDecoration( - suffixIcon: IconButton( - splashRadius: 1, - onPressed: () { - if (messageTextController - .text - .trim() - .isEmpty) { - Utilities.showToast( - "Message can't be empty"); - } - widget.sendMessage( - messageTextController); - messageTextController - .clear(); - }, - icon: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/send_message.svg", - height: 24, - width: 24, - colorFilter: ColorFilter.mode( - messageTextController - .text - .trim() - .isEmpty - ? HMSThemeColors - .onSurfaceLowEmphasis - : HMSThemeColors - .onSurfaceHighEmphasis, - BlendMode.srcIn), - )), - border: InputBorder.none, - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - width: 2, - color: HMSThemeColors - .primaryDefault), - borderRadius: - const BorderRadius.all( - Radius.circular( - 8))), - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: - InputBorder.none, - hintStyle: - HMSTextStyle.setTextStyle( - color: HMSThemeColors - .onSurfaceLowEmphasis, - fontSize: 14, - height: 20 / 14, - letterSpacing: 0.25, - fontWeight: - FontWeight.w400), - contentPadding: - const EdgeInsets.only( - left: 16, - bottom: 8, - top: 12, - right: 8), - hintText: HMSRoomLayout.chatData - ?.messagePlaceholder ?? - "Send a message..."), - ); - }), - ), - ) - ], - ))) - : HMSToast( - toastColor: widget.toastBackgroundColor ?? - HMSThemeColors.surfaceDefault, - toastPosition: 0, - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - HMSSubheadingText( - text: "Chat paused", - textColor: HMSThemeColors.onSurfaceHighEmphasis, - lineHeight: 20, - letterSpacing: 0.1, - fontWeight: FontWeight.w400, - ), - HMSSubtitleText( - text: - "Chat has been paused by ${context.read().chatControls["updatedBy"].toString().substring(0, math.min(10, context.read().chatControls["updatedBy"].toString().length))}", - textColor: HMSThemeColors.onSurfaceMediumEmphasis, - ) - ], - ), - action: (HMSRoomLayout - .chatData?.realTimeControls?.canDisableChat ?? - false) - ? HMSToastButton( - buttonTitle: "Resume", - action: () { - context - .read() - .setSessionMetadataForKey( - key: - SessionStoreKeyValues.getNameFromMethod( - SessionStoreKey.chatState), - metadata: { - "enabled": true, - "updatedBy": { - "peerID": context - .read() - .localPeer - ?.peerId, - "userID": context - .read() - .localPeer - ?.customerUserId, - "userName": context - .read() - .localPeer - ?.name - }, - "updatedAt": DateTime.now() - .millisecondsSinceEpoch //unix timestamp in miliseconds - }); - }, - height: 36, - width: 88, - buttonColor: HMSThemeColors.primaryDefault, - textColor: HMSThemeColors.onPrimaryHighEmphasis, + ), + ], + ) + : Row( + children: [ + Expanded( + child: Container( + color: HMSThemeColors.surfaceDefault, + child: Selector( + selector: (_, meetingStore) => + meetingStore.recipientSelectorValue, + builder: (_, selectedValue, __) { + return TextField( + ///Here if the selected value is empty or equal to "Choose a Recipient" we disable the text field + enabled: selectedValue != + "Choose a Recipient", + textCapitalization: + TextCapitalization.sentences, + textInputAction: TextInputAction.send, + onTapOutside: (event) => FocusManager + .instance.primaryFocus + ?.unfocus(), + onSubmitted: (value) { + widget.sendMessage( + messageTextController); + messageTextController.clear(); + }, + onChanged: (value) { + setState(() {}); + }, + style: HMSTextStyle.setTextStyle( + color: HMSThemeColors + .onSurfaceHighEmphasis, + fontWeight: FontWeight.w400, + height: 20 / 14, + fontSize: 14, + letterSpacing: 0.25), + controller: messageTextController, + decoration: InputDecoration( + isDense: true, + suffixIcon: GestureDetector( + onTap: () { + if (messageTextController + .text + .trim() + .isEmpty) { + Utilities.showToast( + "Message can't be empty"); + } + widget.sendMessage( + messageTextController); + messageTextController + .clear(); + }, + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/send_message.svg", + fit: BoxFit.scaleDown, + colorFilter: ColorFilter.mode( + messageTextController + .text + .trim() + .isEmpty + ? HMSThemeColors + .onSurfaceLowEmphasis + : HMSThemeColors + .onSurfaceHighEmphasis, + BlendMode.srcIn), + )), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: HMSThemeColors + .primaryDefault), + borderRadius: + const BorderRadius.all( + Radius.circular(8))), + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + hintStyle: HMSTextStyle + .setTextStyle( + color: HMSThemeColors + .onSurfaceLowEmphasis, + fontSize: 14, + height: 20 / 14, + letterSpacing: 0.25, + fontWeight: + FontWeight.w400), + contentPadding: + const EdgeInsets.symmetric( + vertical: 8, + horizontal: 12), + hintText: HMSRoomLayout.chatData + ?.messagePlaceholder ?? + "Send a message..."), + ); + }), + ), + ) + ], + )) + : HMSToast( + toastColor: widget.toastBackgroundColor ?? + HMSThemeColors.surfaceDefault, + toastPosition: 0, + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + HMSSubheadingText( + text: "Chat paused", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + lineHeight: 20, + letterSpacing: 0.1, + fontWeight: FontWeight.w400, + ), + HMSSubtitleText( + text: + "Chat has been paused by ${context.read().chatControls["updatedBy"].toString().substring(0, math.min(10, context.read().chatControls["updatedBy"].toString().length))}", + textColor: HMSThemeColors.onSurfaceMediumEmphasis, ) - : null, - ); - }); + ], + ), + action: (HMSRoomLayout + .chatData?.realTimeControls?.canDisableChat ?? + false) + ? HMSToastButton( + buttonTitle: "Resume", + action: () { + context + .read() + .setSessionMetadataForKey( + key: SessionStoreKeyValues + .getNameFromMethod( + SessionStoreKey.chatState), + metadata: { + "enabled": true, + "updatedBy": { + "peerID": context + .read() + .localPeer + ?.peerId, + "userID": context + .read() + .localPeer + ?.customerUserId, + "userName": context + .read() + .localPeer + ?.name + }, + "updatedAt": DateTime.now() + .millisecondsSinceEpoch //unix timestamp in miliseconds + }); + }, + height: 36, + width: 88, + buttonColor: HMSThemeColors.primaryDefault, + textColor: HMSThemeColors.onPrimaryHighEmphasis, + ) + : null, + ); + }), + ); } } diff --git a/packages/hms_room_kit/lib/src/hls_viewer/overlay_chat_component.dart b/packages/hms_room_kit/lib/src/widgets/chat_widgets/overlay_chat_component.dart similarity index 100% rename from packages/hms_room_kit/lib/src/hls_viewer/overlay_chat_component.dart rename to packages/hms_room_kit/lib/src/widgets/chat_widgets/overlay_chat_component.dart diff --git a/packages/hms_room_kit/lib/src/widgets/chat_widgets/recipient_selector_chip.dart b/packages/hms_room_kit/lib/src/widgets/chat_widgets/recipient_selector_chip.dart index 7920e612e..9c2c962e0 100644 --- a/packages/hms_room_kit/lib/src/widgets/chat_widgets/recipient_selector_chip.dart +++ b/packages/hms_room_kit/lib/src/widgets/chat_widgets/recipient_selector_chip.dart @@ -71,7 +71,7 @@ class _ReceipientSelectorChipState extends State { } }, child: Padding( - padding: const EdgeInsets.only(bottom: 8.0, top: 16), + padding: const EdgeInsets.only(bottom: 8.0), child: Row( children: [ Padding( diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_subheading_text.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_subheading_text.dart index 8f511bb9c..f1f8fbf57 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_subheading_text.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_subheading_text.dart @@ -28,7 +28,7 @@ class HMSSubheadingText extends StatelessWidget { final FontWeight? fontWeight; final TextOverflow? textOverflow; final TextAlign? textAlign; - final int maxLines; + final int? maxLines; const HMSSubheadingText( {Key? key, required this.text, @@ -36,7 +36,7 @@ class HMSSubheadingText extends StatelessWidget { this.letterSpacing = 0.25, this.lineHeight = 20, this.fontSize = 14, - this.maxLines = 1, + this.maxLines, this.fontWeight = FontWeight.w400, this.textOverflow = TextOverflow.ellipsis, this.textAlign = TextAlign.start}) diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_subtitle_text.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_subtitle_text.dart index 6cd6e9bda..248eacedd 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_subtitle_text.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_subtitle_text.dart @@ -37,7 +37,7 @@ class HMSSubtitleText extends StatelessWidget { this.fontWeight = FontWeight.w400, this.textOverflow = TextOverflow.ellipsis, this.textAlign, - this.maxLines = 1}) + this.maxLines}) : super(key: key); @override diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_title_text.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_title_text.dart index 945a21979..fd10fa9e4 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_title_text.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_title_text.dart @@ -24,7 +24,7 @@ class HMSTitleText extends StatelessWidget { final double? fontSize; final FontWeight? fontWeight; final TextOverflow? textOverflow; - final int maxLines; + final int? maxLines; const HMSTitleText( {Key? key, @@ -35,7 +35,7 @@ class HMSTitleText extends StatelessWidget { this.fontSize = 16, this.fontWeight = FontWeight.w600, this.textOverflow = TextOverflow.ellipsis, - this.maxLines = 1}) + this.maxLines}) : super(key: key); @override diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/message_container.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/message_container.dart index 5b042ac56..d3753bba5 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/message_container.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/message_container.dart @@ -20,12 +20,11 @@ import 'package:hms_room_kit/src/meeting/meeting_store.dart'; ///[MessageContainer] is a widget that is used to render the message container class MessageContainer extends StatelessWidget { final HMSMessage message; + final bool isHLSChat; final DateFormat formatter = DateFormat('hh:mm a'); - MessageContainer({ - Key? key, - required this.message, - }) : super(key: key); + MessageContainer({Key? key, required this.message, this.isHLSChat = false}) + : super(key: key); String sender(HMSMessageRecipient? hmsMessageRecipient) { if (hmsMessageRecipient == null) return ""; @@ -50,9 +49,13 @@ class MessageContainer extends StatelessWidget { padding: const EdgeInsets.only(bottom: 8.0), child: Container( decoration: BoxDecoration( - color: sender(message.hmsMessageRecipient) != "" - ? HMSThemeColors.surfaceDefault - : HMSThemeColors.surfaceDim, + color: isHLSChat + ? sender(message.hmsMessageRecipient) != "" + ? HMSThemeColors.backgroundDefault + : HMSThemeColors.backgroundDim + : sender(message.hmsMessageRecipient) != "" + ? HMSThemeColors.surfaceDefault + : HMSThemeColors.surfaceDim, borderRadius: BorderRadius.circular(8), ), child: Padding( diff --git a/packages/hms_room_kit/pubspec.lock b/packages/hms_room_kit/pubspec.lock index 652504aca..4b623d248 100644 --- a/packages/hms_room_kit/pubspec.lock +++ b/packages/hms_room_kit/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.2" convert: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" url: "https://pub.dev" source: hosted - version: "0.3.4+1" + version: "0.3.3+7" crypto: dependency: transitive description: @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.0" file: dependency: transitive description: @@ -137,14 +137,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" - url: "https://pub.dev" - source: hosted - version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -195,20 +187,18 @@ packages: hmssdk_flutter: dependency: "direct main" description: - path: "packages/hmssdk_flutter" - ref: develop - resolved-ref: f23908cdd1254aab1ebe01df6dfc75bdbbd44ae8 - url: "https://github.com/100mslive/100ms-flutter.git" - source: git + path: "../hmssdk_flutter" + relative: true + source: path version: "1.10.0" http: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.1.0" http_parser: dependency: transitive description: @@ -229,34 +219,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf - url: "https://pub.dev" - source: hosted - version: "0.7.1" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "0.6.7" linkify: dependency: transitive description: @@ -285,34 +251,34 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.9.1" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.4" nested: dependency: transitive description: @@ -325,10 +291,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" path_parsing: dependency: transitive description: @@ -341,18 +307,18 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" path_provider_foundation: dependency: transitive description: @@ -389,58 +355,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" url: "https://pub.dev" source: hosted - version: "11.3.1" + version: "11.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474" + sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e url: "https://pub.dev" source: hosted - version: "12.0.5" + version: "11.1.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662 - url: "https://pub.dev" - source: hosted - version: "9.4.4" - permission_handler_html: - dependency: transitive - description: - name: permission_handler_html - sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d" + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "0.1.1" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20" + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "3.12.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.1.3" petitparser: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "5.4.0" platform: dependency: transitive description: @@ -461,10 +419,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" url: "https://pub.dev" source: hosted - version: "3.7.4" + version: "3.8.0" provider: dependency: "direct main" description: @@ -493,10 +451,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: @@ -533,10 +491,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.1" shared_preferences_windows: dependency: transitive description: @@ -570,18 +528,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -602,10 +560,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.0" tuple: dependency: "direct main" description: @@ -626,10 +584,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.2.4" url_launcher_android: dependency: transitive description: @@ -642,10 +600,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.2.4" url_launcher_linux: dependency: transitive description: @@ -674,10 +632,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d" + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.0" url_launcher_windows: dependency: transitive description: @@ -690,10 +648,10 @@ packages: dependency: transitive description: name: uuid - sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" url: "https://pub.dev" source: hosted - version: "4.3.3" + version: "4.2.2" vector_graphics: dependency: transitive description: @@ -734,30 +692,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.0+2" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.1.4-beta" win32: dependency: transitive description: name: win32 - sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.1.1" xdg_directories: dependency: transitive description: @@ -770,10 +720,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.3.0" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + dart: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" diff --git a/packages/hms_room_kit/pubspec.yaml b/packages/hms_room_kit/pubspec.yaml index cc9d7d6f0..e051445d5 100644 --- a/packages/hms_room_kit/pubspec.yaml +++ b/packages/hms_room_kit/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: sdk: flutter hmssdk_flutter: - path: ../hmssdk_flutter + path: ../hmssdk_flutter intl: ^0.18.0 permission_handler: ^11.0.0 provider: ^6.0.5 diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt index bebd5530e..7b8ff793d 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSHLSVariantExtension.kt @@ -1,7 +1,6 @@ package live.hms.hmssdk_flutter import live.hms.video.sdk.models.HMSHLSVariant -import java.text.SimpleDateFormat class HMSHLSVariantExtension { companion object { diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerExtension.kt index 8f57848fb..31222fbfb 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSPeerExtension.kt @@ -49,7 +49,7 @@ class HMSPeerExtension { } private fun getValueFromPeerType(peerType: HMSPeerType): String { - return when(peerType){ + return when (peerType) { HMSPeerType.SIP -> "sip" HMSPeerType.REGULAR -> "regular" else -> "regular" diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt index 04e62b4b2..6cd49555c 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt @@ -2,7 +2,6 @@ import live.hms.hmssdk_flutter.HMSExceptionExtension import live.hms.hmssdk_flutter.HMSHLSVariantExtension import live.hms.video.sdk.models.* -import java.text.SimpleDateFormat class HMSStreamingState { companion object { diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt index c810d7893..2a742e6a0 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSTrackSettingsExtension.kt @@ -54,7 +54,7 @@ class HMSTrackSettingsExtension { val enableNoiseCancellation = audioHashMap["enable_noise_cancellation"] as? Boolean enableNoiseCancellation?.let { - if(it){ + if (it) { hmsAudioTrackSettings.enableNoiseCancellation(true) } } diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt index a177ab114..6c7ff934b 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt @@ -38,7 +38,6 @@ import live.hms.video.error.HMSException import live.hms.video.events.AgentType import live.hms.video.interactivity.HmsPollUpdateListener import live.hms.video.media.tracks.* -import live.hms.video.polls.HMSPollBuilder import live.hms.video.polls.models.HMSPollUpdateType import live.hms.video.polls.models.HmsPoll import live.hms.video.sdk.* @@ -65,7 +64,7 @@ class HmssdkFlutterPlugin : private var rtcStatsChannel: EventChannel? = null private var sessionStoreChannel: EventChannel? = null var hlsPlayerChannel: EventChannel? = null - private var pollsEventChannel : EventChannel? = null + private var pollsEventChannel: EventChannel? = null private var eventSink: EventChannel.EventSink? = null private var previewSink: EventChannel.EventSink? = null private var logsSink: EventChannel.EventSink? = null @@ -122,7 +121,7 @@ class HmssdkFlutterPlugin : this.rtcStatsChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "RTC Stats channel not found") this.sessionStoreChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "Session Store channel not found") this.hlsPlayerChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "HLS Player channel not found") - this.pollsEventChannel?.setStreamHandler(this) ?: Log.e("Channel Error","polls events channel not found") + this.pollsEventChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "polls events channel not found") this.hmsVideoFactory = HMSVideoViewFactory(this) this.hmsHLSPlayerFactory = HMSHLSPlayerFactory(this) @@ -253,8 +252,8 @@ class HmssdkFlutterPlugin : "remove_key_change_listener" -> { removeKeyChangeListener(call, result) } - "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener" -> { - HMSHLSPlayerAction.hlsPlayerAction(call, result, activity) + "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener", "are_closed_captions_supported", "enable_closed_captions", "disable_closed_captions" -> { + HMSHLSPlayerAction.hlsPlayerAction(call, result) } "toggle_always_screen_on" -> { toggleAlwaysScreenOn(result) @@ -286,8 +285,8 @@ class HmssdkFlutterPlugin : HMSPeerListIteratorAction.peerListIteratorAction(call, result, hmssdk!!) } - "add_poll_update_listener", "remove_poll_update_listener", "quick_start_poll", "add_single_choice_poll_response", "add_multi_choice_poll_response", "stop_poll", "fetch_leaderboard","fetch_poll_list", "fetch_poll_questions", "get_poll_results" -> { - pollActions(call,result) + "add_poll_update_listener", "remove_poll_update_listener", "quick_start_poll", "add_single_choice_poll_response", "add_multi_choice_poll_response", "stop_poll", "fetch_leaderboard", "fetch_poll_list", "fetch_poll_questions", "get_poll_results" -> { + pollActions(call, result) } "enable_noise_cancellation", "disable_noise_cancellation", "is_noise_cancellation_enabled", "is_noise_cancellation_available" -> { @@ -472,17 +471,22 @@ class HmssdkFlutterPlugin : } private var currentPolls = ArrayList() - private fun pollActions(call: MethodCall, result: Result){ - when(call.method){ + + private fun pollActions( + call: MethodCall, + result: Result, + ) { + when (call.method) { "add_poll_update_listener" -> { hmssdk?.getHmsInteractivityCenter()?.pollUpdateListener = hmsPollListener } "remove_poll_update_listener" -> { hmssdk?.getHmsInteractivityCenter()?.pollUpdateListener = null } - else -> hmssdk?.let { - HMSPollAction.pollActions(call,result, it,currentPolls) - } + else -> + hmssdk?.let { + HMSPollAction.pollActions(call, result, it, currentPolls) + } } } @@ -497,7 +501,7 @@ class HmssdkFlutterPlugin : rtcStatsChannel?.setStreamHandler(null) ?: Log.e("Channel Error", "RTC Stats channel not found") sessionStoreChannel?.setStreamHandler(null) ?: Log.e("Channel Error", "Session Store channel not found") hlsPlayerChannel?.setStreamHandler(null) ?: Log.e("Channel Error", "HLS Player channel not found") - pollsEventChannel?.setStreamHandler(null)?: Log.e("Channel Error", "polls event channel not found") + pollsEventChannel?.setStreamHandler(null) ?: Log.e("Channel Error", "polls event channel not found") eventSink = null previewSink = null rtcSink = null @@ -635,7 +639,7 @@ class HmssdkFlutterPlugin : this.sessionStoreSink = events } else if (nameOfEventSink == "hls_player") { this.hlsPlayerSink = events - } else if(nameOfEventSink == "polls") { + } else if (nameOfEventSink == "polls") { this.pollsSink = events } } @@ -673,7 +677,12 @@ class HmssdkFlutterPlugin : Log.i("HMSTextureView", "Init Add Track called for track: ${track.trackId}") renderer.addTrack(videoTrack, disableAutoSimulcastLayerSelect) } ?: run { - HMSErrorLogger.returnHMSException("createTextureView", "No track with $trackId found", "Track not found error", result) + HMSErrorLogger.returnHMSException( + "createTextureView", + "No track with $trackId found", + "Track not found error", + result, + ) return } } ?: run { @@ -876,7 +885,8 @@ class HmssdkFlutterPlugin : authToken?.let { hmssdk!!.getRoomLayout( - authToken, layoutRequestOptions, + authToken, + layoutRequestOptions, object : HMSLayoutListener { override fun onError(error: HMSException) { result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error))) @@ -1582,8 +1592,12 @@ class HmssdkFlutterPlugin : private fun startScreenShare(result: Result) { androidScreenshareResult = result if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER"), Context.RECEIVER_EXPORTED) - }else { + activity.applicationContext?.registerReceiver( + activityBroadcastReceiver, + IntentFilter("ACTIVITY_RECEIVER"), + Context.RECEIVER_EXPORTED, + ) + } else { activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER")) } val mediaProjectionManager: MediaProjectionManager = @@ -1651,9 +1665,13 @@ class HmssdkFlutterPlugin : androidAudioShareResult = result mode = call.argument("audio_mixing_mode") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER"), Context.RECEIVER_EXPORTED) - }else { - activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER"),) + activity.applicationContext?.registerReceiver( + activityBroadcastReceiver, + IntentFilter("ACTIVITY_RECEIVER"), + Context.RECEIVER_EXPORTED, + ) + } else { + activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER")) } val mediaProjectionManager: MediaProjectionManager? = activity.getSystemService( @@ -2042,7 +2060,6 @@ class HmssdkFlutterPlugin : private val audioPreviewDeviceChangeListener = object : AudioManagerDeviceChangeListener { - override fun onAudioDeviceChanged( selectedAudioDevice: AudioDevice, availableAudioDevices: Set, @@ -2075,7 +2092,6 @@ class HmssdkFlutterPlugin : } } } - } private val audioDeviceChangeListener = @@ -2087,12 +2103,12 @@ class HmssdkFlutterPlugin : val args = HashMap() args["event_name"] = "on_audio_device_changed" val dict = HashMap() - dict["current_audio_device"] = selectedAudioDevice.name - val audioDevicesList = ArrayList() - availableAudioDevices.forEach { device -> - audioDevicesList.add(device.name) - } - dict["available_audio_device"] = audioDevicesList + dict["current_audio_device"] = selectedAudioDevice.name + val audioDevicesList = ArrayList() + availableAudioDevices.forEach { device -> + audioDevicesList.add(device.name) + } + dict["available_audio_device"] = audioDevicesList args["data"] = dict if (args["data"] != null) { CoroutineScope(Dispatchers.Main).launch { @@ -2114,36 +2130,37 @@ class HmssdkFlutterPlugin : } } - private val hmsPollListener = object : HmsPollUpdateListener{ - override fun onPollUpdate(hmsPoll: HmsPoll, hmsPollUpdateType: HMSPollUpdateType) { - - if(hmsPollUpdateType == HMSPollUpdateType.started){ - currentPolls.add(hmsPoll) - } else if(hmsPollUpdateType == HMSPollUpdateType.resultsupdated){ - val index = currentPolls.indexOfFirst { it.pollId == hmsPoll.pollId } - if(index != -1){ - currentPolls[index] = hmsPoll - } - } else if(hmsPollUpdateType == HMSPollUpdateType.stopped){ - val index = currentPolls.indexOfFirst { it.pollId == hmsPoll.pollId } - if(index != -1){ - currentPolls.removeAt(index) + private val hmsPollListener = + object : HmsPollUpdateListener { + override fun onPollUpdate( + hmsPoll: HmsPoll, + hmsPollUpdateType: HMSPollUpdateType, + ) { + if (hmsPollUpdateType == HMSPollUpdateType.started) { + currentPolls.add(hmsPoll) + } else if (hmsPollUpdateType == HMSPollUpdateType.resultsupdated) { + val index = currentPolls.indexOfFirst { it.pollId == hmsPoll.pollId } + if (index != -1) { + currentPolls[index] = hmsPoll + } + } else if (hmsPollUpdateType == HMSPollUpdateType.stopped) { + val index = currentPolls.indexOfFirst { it.pollId == hmsPoll.pollId } + if (index != -1) { + currentPolls.removeAt(index) + } } - } - val args = HashMap() - args["event_name"] = "on_poll_update" + val args = HashMap() + args["event_name"] = "on_poll_update" - val pollHashMap = HashMap() - pollHashMap["poll"] = HMSPollExtension.toDictionary(hmsPoll) - pollHashMap["poll_update_type"] = HMSPollExtension.getPollUpdateType(hmsPollUpdateType) - args["data"] = pollHashMap + val pollHashMap = HashMap() + pollHashMap["poll"] = HMSPollExtension.toDictionary(hmsPoll) + pollHashMap["poll_update_type"] = HMSPollExtension.getPollUpdateType(hmsPollUpdateType) + args["data"] = pollHashMap - CoroutineScope(Dispatchers.Main).launch { - pollsSink?.success(args) + CoroutineScope(Dispatchers.Main).launch { + pollsSink?.success(args) + } } } - } - - } diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HLSStatsHandler.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HLSStatsHandler.kt index 208a7630c..e0e11fada 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HLSStatsHandler.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HLSStatsHandler.kt @@ -66,7 +66,11 @@ class HLSStatsHandler { } }, ) ?: run { - HMSErrorLogger.logError("addHLSStatsListener", "hlsPlayer is null, Consider calling this method after attaching the HMSHLSPlayer or sending isHLSStatsRequired as true to get the stats", "NULL_ERROR") + HMSErrorLogger.logError( + "addHLSStatsListener", + "hlsPlayer is null, Consider calling this method after attaching the HMSHLSPlayer or sending isHLSStatsRequired as true to get the stats", + "NULL_ERROR", + ) } } diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt index 5da7aff21..5cac07bb0 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/HMSHLSPlayerAction.kt @@ -1,12 +1,9 @@ package live.hms.hmssdk_flutter.hls_player -import android.app.Activity -import android.content.Intent import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel.Result -import live.hms.hmssdk_flutter.Constants.Companion.HLS_PLAYER_INTENT -import live.hms.hmssdk_flutter.Constants.Companion.METHOD_CALL import live.hms.hmssdk_flutter.HMSErrorLogger +import java.lang.ref.WeakReference /** * This class is used to send actions from flutter plugin to HLS Player @@ -14,99 +11,104 @@ import live.hms.hmssdk_flutter.HMSErrorLogger */ class HMSHLSPlayerAction { companion object { + private var hlsActions: WeakReference? = null + fun hlsPlayerAction( call: MethodCall, result: Result, - activity: Activity, ) { when (call.method) { - "start_hls_player" -> start(call, result, activity) - "stop_hls_player" -> stop(result, activity) - "pause_hls_player" -> pause(result, activity) - "resume_hls_player" -> resume(result, activity) - "seek_to_live_position" -> seekToLivePosition(result, activity) - "seek_forward" -> seekForward(call, result, activity) - "seek_backward" -> seekBackward(call, result, activity) - "set_hls_player_volume" -> setVolume(call, result, activity) - "add_hls_stats_listener" -> addHLSStatsListener(result, activity) - "remove_hls_stats_listener" -> removeHLSStatsListener(result, activity) + "start_hls_player" -> start(call, result) + "stop_hls_player" -> stop(result) + "pause_hls_player" -> pause(result) + "resume_hls_player" -> resume(result) + "seek_to_live_position" -> seekToLivePosition(result) + "seek_forward" -> seekForward(call, result) + "seek_backward" -> seekBackward(call, result) + "set_hls_player_volume" -> setVolume(call, result) + "add_hls_stats_listener" -> addHLSStatsListener(result) + "remove_hls_stats_listener" -> removeHLSStatsListener(result) + "are_closed_captions_supported" -> areClosedCaptionsSupported(result) + "enable_closed_captions" -> enableClosedCaptions(result) + "disable_closed_captions" -> disableClosedCaptions(result) else -> { result.notImplemented() } } } + fun assignInterfaceObject(actionObject: WeakReference) { + hlsActions = actionObject + } + /** * Starts the HLS player by sending a broadcast intent with the specified method call and HLS URL. * * @param call The method call object containing the HLS URL as an argument. * @param result The result object to be returned after starting the player. - * @param activity The current activity from which the method is called. */ private fun start( call: MethodCall, result: Result, - activity: Activity, ) { val hlsUrl = call.argument("hls_url") - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "start_hls_player").putExtra("hls_url", hlsUrl)) - result.success(null) + hlsActions?.let { + it.get()?.start(hlsUrl, result) + }?:run{ + HMSErrorLogger.logError("start", "hlsActions is NULL", "NULL Error") + } } /** * Stops the HLS player by sending a broadcast intent with the specified method call. * * @param result The result object to be returned after stopping the player. - * @param activity The current activity from which the method is called. */ - private fun stop( - result: Result, - activity: Activity, - ) { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "stop_hls_player")) - result.success(null) + private fun stop(result: Result) { + hlsActions?.let { + it.get()?.stop(result) + }?:run{ + HMSErrorLogger.logError("stop", "hlsActions is NULL", "NULL Error") + } } /** * Pauses the HLS player by sending a broadcast intent with the specified method call. * * @param result The result object to be returned after pausing the player. - * @param activity The current activity from which the method is called. */ - private fun pause( - result: Result, - activity: Activity, - ) { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "pause_hls_player")) - result.success(null) + private fun pause(result: Result) { + hlsActions?.let { + it.get()?.pause(result) + }?:run{ + HMSErrorLogger.logError("pause", "hlsActions is NULL", "NULL Error") + } } /** * Resumes the HLS player by sending a broadcast intent with the specified method call. * * @param result The result object to be returned after resuming the player. - * @param activity The current activity from which the method is called. */ - private fun resume( - result: Result, - activity: Activity, - ) { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "resume_hls_player")) - result.success(null) + private fun resume(result: Result) { + hlsActions?.let { + it.get()?.resume(result) + }?:run{ + HMSErrorLogger.logError("resume", "hlsActions is NULL", "NULL Error") + } } /** * Seeks to the live position in the HLS player by sending a broadcast intent with the specified method call. * * @param result The result object to be returned after seeking to the live position. - * @param activity The current activity from which the method is called. */ - private fun seekToLivePosition( - result: Result, - activity: Activity, - ) { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "seek_to_live_position")) - result.success(null) + private fun seekToLivePosition(result: Result) { + hlsActions?.let { + it.get()?.seekToLivePosition(result) + }?:run{ + HMSErrorLogger.logError("seekToLivePosition", "hlsActions is NULL", "NULL Error") + } } /** @@ -114,12 +116,10 @@ class HMSHLSPlayerAction { * * @param call The method call object containing the number of seconds to seek forward as an argument. * @param result The result object to be returned after seeking forward. - * @param activity The current activity from which the method is called. */ private fun seekForward( call: MethodCall, result: Result, - activity: Activity, ) { val seconds: Int? = call.argument("seconds") ?: run { @@ -128,9 +128,12 @@ class HMSHLSPlayerAction { } seconds?.let { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "seek_forward").putExtra("seconds", seconds)) + hlsActions?.let {_hlsActions -> + _hlsActions.get()?.seekForward(it, result) + }?:run{ + HMSErrorLogger.logError("seekForward", "hlsActions is NULL", "NULL Error") + } } - result.success(null) } /** @@ -138,12 +141,10 @@ class HMSHLSPlayerAction { * * @param call The method call object containing the number of seconds to seek backward as an argument. * @param result The result object to be returned after seeking backward. - * @param activity The current activity from which the method is called. */ private fun seekBackward( call: MethodCall, result: Result, - activity: Activity, ) { val seconds: Int? = call.argument("seconds") ?: run { @@ -152,9 +153,12 @@ class HMSHLSPlayerAction { } seconds?.let { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "seek_backward").putExtra("seconds", seconds)) + hlsActions?.let { _hlsActions -> + _hlsActions.get()?.seekBackward(it, result) + }?:run{ + HMSErrorLogger.logError("seekBackward", "hlsActions is NULL", "NULL Error") + } } - result.success(null) } /** @@ -162,12 +166,10 @@ class HMSHLSPlayerAction { * * @param call The method call object containing the volume level as an argument. * @param result The result object to be returned after setting the volume. - * @param activity The current activity from which the method is called. */ private fun setVolume( call: MethodCall, result: Result, - activity: Activity, ) { val volume: Int? = call.argument("volume") ?: run { @@ -176,37 +178,79 @@ class HMSHLSPlayerAction { } volume?.let { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "set_volume").putExtra("volume", volume)) + hlsActions?.let {_hlsActions -> + _hlsActions.get()?.setVolume(it,result) + }?:run{ + HMSErrorLogger.logError("setVolume", "hlsActions is NULL", "NULL Error") + } } - result.success(null) } /** * Adds a listener to receive HLS player statistics by sending a broadcast intent with the corresponding method call. * * @param result The result object to be returned after adding the HLS stats listener. - * @param activity The current activity from which the method is called. */ - private fun addHLSStatsListener( - result: Result, - activity: Activity, - ) { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "add_hls_stats_listener")) - result.success(null) + private fun addHLSStatsListener(result: Result) { + + hlsActions?.let { + it.get()?.addHLSStatsListener(result) + }?:run{ + HMSErrorLogger.logError("addHLSStatsListener", "hlsActions is NULL", "NULL Error") + } } /** * Removes the listener for HLS player statistics by sending a broadcast intent with the corresponding method call. * * @param result The result object to be returned after removing the HLS stats listener. - * @param activity The current activity from which the method is called. */ - private fun removeHLSStatsListener( - result: Result, - activity: Activity, - ) { - activity.sendBroadcast(Intent(HLS_PLAYER_INTENT).putExtra(METHOD_CALL, "remove_hls_stats_listener")) - result.success(null) + private fun removeHLSStatsListener(result: Result) { + hlsActions?.let { + it.get()?.removeHLSStatsListener(result) + }?:run{ + HMSErrorLogger.logError("removeHLSStatsListener", "hlsActions is NULL", "NULL Error") + } + } + + /** + * Checks whether closed captions are supported or not + * This can be enabled/disabled from 100ms dashboard + * + * @param result The result object used to send response regarding closed captions + */ + private fun areClosedCaptionsSupported(result: Result) { + hlsActions?.let { + it.get()?.areClosedCaptionsSupported(result) + }?:run{ + HMSErrorLogger.logError("areClosedCaptionsSupported", "hlsActions is NULL", "NULL Error") + } + } + + /** + * Enable closed captions in the player + * + * @param result is the object to be returned after enabling closed captions + */ + private fun enableClosedCaptions(result: Result) { + hlsActions?.let { + it.get()?.enableClosedCaptions(result) + }?:run{ + HMSErrorLogger.logError("enableClosedCaptions", "hlsActions is NULL", "NULL Error") + } + } + + /** + * Disable closed captions in the player + * + * @param result is the object to be returned after disabling closed captions + */ + private fun disableClosedCaptions(result: Result) { + hlsActions?.let { + it.get()?.disableClosedCaptions(result) + }?:run{ + HMSErrorLogger.logError("disableClosedCaptions", "hlsActions is NULL", "NULL Error") + } } } } diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/IHLSPlayerActionInterface.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/IHLSPlayerActionInterface.kt new file mode 100644 index 000000000..4a3b20ef1 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hls_player/IHLSPlayerActionInterface.kt @@ -0,0 +1,48 @@ +package live.hms.hmssdk_flutter.hls_player + +import io.flutter.plugin.common.MethodChannel.Result + +/** + * [IHLSPlayerActionInterface] contains HLS Player methods + * This is implemented in HMSHLSPlayer view to execute the method calls + * from flutter + */ +interface IHLSPlayerActionInterface { + fun start( + hlsUrl: String?, + result: Result, + ) + + fun stop(result: Result) + + fun pause(result: Result) + + fun resume(result: Result) + + fun seekToLivePosition(result: Result) + + fun seekForward( + seconds: Int, + result: Result, + ) + + fun seekBackward( + seconds: Int, + result: Result, + ) + + fun setVolume( + volume: Int, + result: Result, + ) + + fun addHLSStatsListener(result: Result) + + fun removeHLSStatsListener(result: Result) + + fun areClosedCaptionsSupported(result: Result) + + fun enableClosedCaptions(result: Result) + + fun disableClosedCaptions(result: Result) +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSCameraControlsAction.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSCameraControlsAction.kt index e39727c85..7a1040118 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSCameraControlsAction.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSCameraControlsAction.kt @@ -98,7 +98,11 @@ class HMSCameraControlsAction { if (cameraControl.isFlashSupported()) { cameraControl.setFlash(true) } else { - HMSErrorLogger.logError("captureImageAtMaxSupportedResolution", "Flash is not supported for current facing camera", "Compatibility error") + HMSErrorLogger.logError( + "captureImageAtMaxSupportedResolution", + "Flash is not supported for current facing camera", + "Compatibility error", + ) } } cameraControl.captureImageAtMaxSupportedResolution(imageFile) { isSuccess -> @@ -107,7 +111,9 @@ class HMSCameraControlsAction { if (isSuccess) { result.success(HMSResultExtension.toDictionary(true, filePath)) } else { - result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("Error in capturing image"))) + result.success( + HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("Error in capturing image")), + ) } if (withFlash) { cameraControl.setFlash(false) @@ -177,7 +183,14 @@ class HMSCameraControlsAction { result.success(HMSResultExtension.toDictionary(true, null)) return } else { - result.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.getError("Flash is not supported for current facing camera, Also please ensure camera is turned ON"))) + result.success( + HMSResultExtension.toDictionary( + false, + HMSExceptionExtension.getError( + "Flash is not supported for current facing camera, Also please ensure camera is turned ON", + ), + ), + ) return } } ?: run { diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSNoiseCancellationControllerAction.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSNoiseCancellationControllerAction.kt index 6cea949ba..90a267057 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSNoiseCancellationControllerAction.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSNoiseCancellationControllerAction.kt @@ -8,24 +8,24 @@ import live.hms.video.factories.noisecancellation.AvailabilityStatus import live.hms.video.sdk.HMSSDK class HMSNoiseCancellationControllerAction { - companion object { - - fun noiseCancellationActions(call: MethodCall, - result: MethodChannel.Result, - hmssdk: HMSSDK,){ - when(call.method){ + fun noiseCancellationActions( + call: MethodCall, + result: MethodChannel.Result, + hmssdk: HMSSDK, + ) { + when (call.method) { "enable_noise_cancellation" -> { - enable(result,hmssdk) + enable(result, hmssdk) } "disable_noise_cancellation" -> { - disable(result,hmssdk) + disable(result, hmssdk) } "is_noise_cancellation_enabled" -> { - isEnabled(result,hmssdk) + isEnabled(result, hmssdk) } "is_noise_cancellation_available" -> { - isAvailable(result,hmssdk) + isAvailable(result, hmssdk) } else -> { result.notImplemented() @@ -36,7 +36,10 @@ class HMSNoiseCancellationControllerAction { /** * [enable] method enables noise cancellation for the user */ - private fun enable(result: MethodChannel.Result, hmssdk: HMSSDK){ + private fun enable( + result: MethodChannel.Result, + hmssdk: HMSSDK, + ) { hmssdk.setNoiseCancellationEnabled(true) result.success(null) } @@ -44,7 +47,10 @@ class HMSNoiseCancellationControllerAction { /** * [disable] method disables noise cancellation for the user */ - private fun disable(result: MethodChannel.Result, hmssdk: HMSSDK){ + private fun disable( + result: MethodChannel.Result, + hmssdk: HMSSDK, + ) { hmssdk.setNoiseCancellationEnabled(false) result.success(null) } @@ -52,7 +58,10 @@ class HMSNoiseCancellationControllerAction { /** * [isEnabled] method returns whether noise cancellation is enabled or not */ - private fun isEnabled(result: MethodChannel.Result, hmssdk: HMSSDK){ + private fun isEnabled( + result: MethodChannel.Result, + hmssdk: HMSSDK, + ) { val isEnabled = hmssdk.getNoiseCancellationEnabled() result.success(HMSResultExtension.toDictionary(true, isEnabled)) } @@ -60,17 +69,18 @@ class HMSNoiseCancellationControllerAction { /** * [isAvailable] method returns whether noise cancellation is available in the room */ - private fun isAvailable(result: MethodChannel.Result, hmssdk: HMSSDK){ + private fun isAvailable( + result: MethodChannel.Result, + hmssdk: HMSSDK, + ) { val availabilityStatus = hmssdk.isNoiseCancellationAvailable() - if(availabilityStatus == AvailabilityStatus.Available){ + if (availabilityStatus == AvailabilityStatus.Available) { result.success(HMSResultExtension.toDictionary(true, data = true)) - }else{ + } else { val reason = (availabilityStatus as AvailabilityStatus.NotAvailable).reason - HMSErrorLogger.logError("isAvailable",reason,"NoiseCancellation Error") + HMSErrorLogger.logError("isAvailable", reason, "NoiseCancellation Error") result.success(HMSResultExtension.toDictionary(true, data = false)) } } - } - -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPeerListIteratorAction.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPeerListIteratorAction.kt index 8bc585269..c28a7b9c1 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPeerListIteratorAction.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPeerListIteratorAction.kt @@ -60,10 +60,16 @@ class HMSPeerListIteratorAction { peerListIteratorOptions = PeerListIteratorOptions( byRoleName = it["by_role_name"] as String?, - byPeerIds = it["by_peer_ids"] as ArrayList?, limit = limitValue, + byPeerIds = it["by_peer_ids"] as ArrayList?, + limit = limitValue, ) } ?: run { - HMSErrorLogger.returnHMSException("getPeerListIterator", "limit parameter is null while peerListIteratorOptions is non-null", "NULL Error", result) + HMSErrorLogger.returnHMSException( + "getPeerListIterator", + "limit parameter is null while peerListIteratorOptions is non-null", + "NULL Error", + result, + ) } } @@ -100,7 +106,12 @@ class HMSPeerListIteratorAction { peerListIterator?.let { iterator -> result.success(HMSResultExtension.toDictionary(true, iterator.hasNext())) } ?: run { - HMSErrorLogger.returnHMSException("peerListIteratorHasNext", "No peerListIterator with given uid found", "NULL Error", result) + HMSErrorLogger.returnHMSException( + "peerListIteratorHasNext", + "No peerListIterator with given uid found", + "NULL Error", + result, + ) } } ?: run { HMSErrorLogger.returnHMSException("peerListIteratorHasNext", "uid is null", "NULL Error", result) @@ -128,7 +139,9 @@ class HMSPeerListIteratorAction { iterator.next( object : PeerListResultListener { override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error))) + methodChannelResult.success( + HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error)), + ) } override fun onSuccess(result: ArrayList) { @@ -144,7 +157,12 @@ class HMSPeerListIteratorAction { }, ) } ?: run { - HMSErrorLogger.returnHMSException("peerListIteratorNext", "No peerListIterator with given uid found", "NULL Error", methodChannelResult) + HMSErrorLogger.returnHMSException( + "peerListIteratorNext", + "No peerListIterator with given uid found", + "NULL Error", + methodChannelResult, + ) } } ?: run { HMSErrorLogger.returnHMSException("peerListIteratorNext", "uid is null", "NULL Error", methodChannelResult) diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPollAction.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPollAction.kt index a18054604..712493445 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPollAction.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPollAction.kt @@ -6,8 +6,8 @@ import live.hms.hmssdk_flutter.HMSCommonAction import live.hms.hmssdk_flutter.HMSErrorLogger import live.hms.hmssdk_flutter.HMSExceptionExtension import live.hms.hmssdk_flutter.HMSResultExtension -import live.hms.hmssdk_flutter.poll_extension.HMSPollBuilderExtension import live.hms.hmssdk_flutter.poll_extension.HMSPollAnswerResponseExtension +import live.hms.hmssdk_flutter.poll_extension.HMSPollBuilderExtension import live.hms.hmssdk_flutter.poll_extension.HMSPollExtension import live.hms.hmssdk_flutter.poll_extension.HMSPollLeaderboardResponseExtension import live.hms.hmssdk_flutter.poll_extension.HMSPollQuestionExtension @@ -23,53 +23,64 @@ import live.hms.video.sdk.HMSSDK import live.hms.video.sdk.HmsTypedActionResultListener class HMSPollAction { - - companion object{ - fun pollActions(call: MethodCall, result: MethodChannel.Result, hmssdk: HMSSDK, polls: ArrayList?){ - when(call.method){ - "quick_start_poll" -> quickStartPoll(call,result,hmssdk) - "add_single_choice_poll_response" -> addSingleChoicePollResponse(call,result,hmssdk,polls) - "add_multi_choice_poll_response" -> addMultiChoicePollResponse(call,result,hmssdk,polls) - "stop_poll" -> stopPoll(call,result,hmssdk,polls) - "fetch_leaderboard" -> fetchLeaderboard(call,result,hmssdk) - "fetch_poll_list" -> fetchPollList(call,result,hmssdk) - "fetch_poll_questions" -> fetchPollQuestions(call,result,hmssdk) - "get_poll_results" -> getPollResults(call,result,hmssdk) + companion object { + fun pollActions( + call: MethodCall, + result: MethodChannel.Result, + hmssdk: HMSSDK, + polls: ArrayList?, + ) { + when (call.method) { + "quick_start_poll" -> quickStartPoll(call, result, hmssdk) + "add_single_choice_poll_response" -> addSingleChoicePollResponse(call, result, hmssdk, polls) + "add_multi_choice_poll_response" -> addMultiChoicePollResponse(call, result, hmssdk, polls) + "stop_poll" -> stopPoll(call, result, hmssdk, polls) + "fetch_leaderboard" -> fetchLeaderboard(call, result, hmssdk) + "fetch_poll_list" -> fetchPollList(call, result, hmssdk) + "fetch_poll_questions" -> fetchPollQuestions(call, result, hmssdk) + "get_poll_results" -> getPollResults(call, result, hmssdk) } } - private fun quickStartPoll(call: MethodCall, result: MethodChannel.Result, hmssdk: HMSSDK){ + private fun quickStartPoll( + call: MethodCall, + result: MethodChannel.Result, + hmssdk: HMSSDK, + ) { + val pollBuilderMap = call.argument?>("poll_builder") - val pollBuilderMap = call.argument?>("poll_builder") - - val pollBuilder = HMSPollBuilderExtension.toHMSPollBuilder(pollBuilderMap,hmssdk) + val pollBuilder = HMSPollBuilderExtension.toHMSPollBuilder(pollBuilderMap, hmssdk) pollBuilder?.let { - hmssdk.getHmsInteractivityCenter().quickStartPoll( pollBuilder, HMSCommonAction.getActionListener(result)) - }?:run{ + hmssdk.getHmsInteractivityCenter().quickStartPoll(pollBuilder, HMSCommonAction.getActionListener(result)) + } ?: run { HMSErrorLogger.returnArgumentsError("pollBuilder parsing failed") } - } - private fun addSingleChoicePollResponse(call: MethodCall, methodChannelResult: MethodChannel.Result, hmssdk: HMSSDK, currentPolls: ArrayList?){ - + private fun addSingleChoicePollResponse( + call: MethodCall, + methodChannelResult: MethodChannel.Result, + hmssdk: HMSSDK, + currentPolls: ArrayList?, + ) { val pollId = call.argument("poll_id") val index = call.argument("question_index") val userId = call.argument("user_id") - val answer = call.argument>("answer") + val answer = call.argument>("answer") val timeTakenToAnswer = call.argument("time_taken_to_answer") /* * Here we get index for the option selected by the user * if the option doesn't exist we return the arguments error */ - val optionIndex = answer?.let { - it["index"] as Int - }?:run { - HMSErrorLogger.returnArgumentsError("Invalid option index") - return - } + val optionIndex = + answer?.let { + it["index"] as Int + } ?: run { + HMSErrorLogger.returnArgumentsError("Invalid option index") + return + } /* * We fetch the polls which are currently active and find the poll matching the pollId @@ -82,54 +93,67 @@ class HMSPollAction { * * If anywhere the sdk is unable to find the property we return the error */ - currentPolls?.find { it.pollId == pollId }?.let {poll -> - index?.let {questionIndex -> + currentPolls?.find { it.pollId == pollId }?.let { poll -> + index?.let { questionIndex -> - poll.questions?.get(questionIndex)?.let { currentQuestion -> + poll.questions?.get(questionIndex)?.let { currentQuestion -> /* * Here the index needs to be subtracted by 1 * since the HMSPollQuestionOption object has indexing with 1 */ - val questionOption = currentQuestion.options?.get(optionIndex - 1) - questionOption?.let {selectedOption -> - val response = - timeTakenToAnswer?.let {_timeTakenToAnswer -> - HMSPollResponseBuilder(poll, userId).addResponse(currentQuestion,selectedOption, - _timeTakenToAnswer.toLong() - ) - }?: run { - HMSPollResponseBuilder(poll, userId).addResponse(currentQuestion,selectedOption) - } - hmssdk.getHmsInteractivityCenter().add(response, object : HmsTypedActionResultListener{ + val questionOption = currentQuestion.options?.get(optionIndex - 1) + questionOption?.let { selectedOption -> + val response = + timeTakenToAnswer?.let { _timeTakenToAnswer -> + HMSPollResponseBuilder(poll, userId).addResponse( + currentQuestion, + selectedOption, + _timeTakenToAnswer.toLong(), + ) + } ?: run { + HMSPollResponseBuilder(poll, userId).addResponse(currentQuestion, selectedOption) + } + hmssdk.getHmsInteractivityCenter().add( + response, + object : HmsTypedActionResultListener { override fun onSuccess(result: PollAnswerResponse) { - methodChannelResult.success(HMSResultExtension.toDictionary(true,HMSPollAnswerResponseExtension.toDictionary(result))) + methodChannelResult.success( + HMSResultExtension.toDictionary(true, HMSPollAnswerResponseExtension.toDictionary(result)), + ) } + override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) + methodChannelResult.success( + HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error)), + ) } - }) - } - - }?:run { - HMSErrorLogger.returnArgumentsError("Question not found") - return + }, + ) } - }?: run { - HMSErrorLogger.returnArgumentsError("Incorrect question index") + } ?: run { + HMSErrorLogger.returnArgumentsError("Question not found") return } - }?:run { + } ?: run { + HMSErrorLogger.returnArgumentsError("Incorrect question index") + return + } + } ?: run { HMSErrorLogger.returnArgumentsError("No poll with given pollId found") return } } - private fun addMultiChoicePollResponse(call: MethodCall, methodChannelResult: MethodChannel.Result, hmssdk: HMSSDK, currentPolls: ArrayList?){ - + private fun addMultiChoicePollResponse( + call: MethodCall, + methodChannelResult: MethodChannel.Result, + hmssdk: HMSSDK, + currentPolls: ArrayList?, + ) { val pollId = call.argument("poll_id") val index = call.argument("question_index") val userId = call.argument("user_id") - val answer = call.argument?>>("answer") + val answer = call.argument?>>("answer") val timeTakenToAnswer = call.argument("time_taken_to_answer") /* @@ -144,121 +168,154 @@ class HMSPollAction { * If anywhere the sdk is unable to find the property we return the error */ currentPolls?.find { it.pollId == pollId }?.let { poll -> - index?.let {questionIndex -> + index?.let { questionIndex -> poll.questions?.get(questionIndex)?.let { currentQuestion -> val questionOptions = ArrayList() answer?.forEach { selectedOptions -> - selectedOptions as HashMap + selectedOptions as HashMap /* * Here the index needs to be subtracted by 1 * since the HMSPollQuestionOption object has indexing with 1 */ selectedOptions["index"]?.let { - index -> index as Int + index -> + index as Int val questionOption = currentQuestion.options?.get(index - 1) - questionOption?.let {option -> + questionOption?.let { option -> questionOptions.add(option) } } } val response = - timeTakenToAnswer?.let {_timeTakenToAnswer -> - HMSPollResponseBuilder(poll, userId).addResponse(currentQuestion,questionOptions, - _timeTakenToAnswer.toLong() + timeTakenToAnswer?.let { _timeTakenToAnswer -> + HMSPollResponseBuilder(poll, userId).addResponse( + currentQuestion, + questionOptions, + _timeTakenToAnswer.toLong(), ) - }?: run { - HMSPollResponseBuilder(poll, userId).addResponse(currentQuestion,questionOptions) - } - hmssdk.getHmsInteractivityCenter().add(response, object : HmsTypedActionResultListener{ - override fun onSuccess(result: PollAnswerResponse) { - methodChannelResult.success(HMSResultExtension.toDictionary(true,HMSPollAnswerResponseExtension.toDictionary(result))) - } - override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) + } ?: run { + HMSPollResponseBuilder(poll, userId).addResponse(currentQuestion, questionOptions) } - }) + hmssdk.getHmsInteractivityCenter().add( + response, + object : HmsTypedActionResultListener { + override fun onSuccess(result: PollAnswerResponse) { + methodChannelResult.success( + HMSResultExtension.toDictionary(true, HMSPollAnswerResponseExtension.toDictionary(result)), + ) + } - }?: run { + override fun onError(error: HMSException) { + methodChannelResult.success( + HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error)), + ) + } + }, + ) + } ?: run { HMSErrorLogger.returnArgumentsError("Question not found") return } - }?:run{ + } ?: run { HMSErrorLogger.returnArgumentsError("Incorrect question index") return } - - }?:run { - HMSErrorLogger.returnArgumentsError("No poll with given pollId found") - return + } ?: run { + HMSErrorLogger.returnArgumentsError("No poll with given pollId found") + return } } - private fun stopPoll(call: MethodCall, result: MethodChannel.Result, hmssdk: HMSSDK, currentPolls: ArrayList?){ + private fun stopPoll( + call: MethodCall, + result: MethodChannel.Result, + hmssdk: HMSSDK, + currentPolls: ArrayList?, + ) { val pollId = call.argument("poll_id") - val poll = currentPolls?.first{ - it.pollId == pollId - }?:run { - HMSErrorLogger.returnArgumentsError("No Poll with given pollId found") - return - } - hmssdk.getHmsInteractivityCenter().stop(poll,HMSCommonAction.getActionListener(result)) - + val poll = + currentPolls?.first { + it.pollId == pollId + } ?: run { + HMSErrorLogger.returnArgumentsError("No Poll with given pollId found") + return + } + hmssdk.getHmsInteractivityCenter().stop(poll, HMSCommonAction.getActionListener(result)) } - private fun fetchLeaderboard(call: MethodCall, methodChannelResult: MethodChannel.Result, hmssdk: HMSSDK){ + private fun fetchLeaderboard( + call: MethodCall, + methodChannelResult: MethodChannel.Result, + hmssdk: HMSSDK, + ) { val pollId = call.argument("poll_id") val count = call.argument("count") val startIndex = call.argument("start_index") val includeCurrentPeer = call.argument("include_current_peer") - if(pollId == null || count == null || startIndex == null || includeCurrentPeer == null){ + if (pollId == null || count == null || startIndex == null || includeCurrentPeer == null) { HMSErrorLogger.returnArgumentsError("Either pollId, count, startIndex or includeCurrentPeer is null") return } - hmssdk.getHmsInteractivityCenter().fetchLeaderboard(pollId,count.toLong(),startIndex.toLong(),includeCurrentPeer, object : HmsTypedActionResultListener { - override fun onSuccess(result: PollLeaderboardResponse) { - methodChannelResult.success(HMSResultExtension.toDictionary(true,HMSPollLeaderboardResponseExtension.toDictionary(result))) - } + hmssdk.getHmsInteractivityCenter().fetchLeaderboard( + pollId, + count.toLong(), + startIndex.toLong(), + includeCurrentPeer, + object : HmsTypedActionResultListener { + override fun onSuccess(result: PollLeaderboardResponse) { + methodChannelResult.success( + HMSResultExtension.toDictionary(true, HMSPollLeaderboardResponseExtension.toDictionary(result)), + ) + } - override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) - } - }) + override fun onError(error: HMSException) { + methodChannelResult.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error))) + } + }, + ) } - private fun fetchPollList(call: MethodCall, methodChannelResult: MethodChannel.Result, hmssdk: HMSSDK){ + private fun fetchPollList( + call: MethodCall, + methodChannelResult: MethodChannel.Result, + hmssdk: HMSSDK, + ) { val state = call.argument("poll_state") val pollState = getPollState(state) pollState?.let { - hmssdk.getHmsInteractivityCenter().fetchPollList(it,object : HmsTypedActionResultListener>{ - override fun onSuccess(result: List) { - - val map = ArrayList>() + hmssdk.getHmsInteractivityCenter().fetchPollList( + it, + object : HmsTypedActionResultListener> { + override fun onSuccess(result: List) { + val map = ArrayList>() - result.forEach { poll -> - map.add(HMSPollExtension.toDictionary(poll)) + result.forEach { poll -> + map.add(HMSPollExtension.toDictionary(poll)) + } + methodChannelResult.success(HMSResultExtension.toDictionary(true, map)) } - methodChannelResult.success(HMSResultExtension.toDictionary(true, map)) - } - - override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) - } - }) - - }?: run { - HMSErrorLogger.returnHMSException("fetchPollList","No poll state matched", "ARGUMENTS_ERROR",methodChannelResult) + override fun onError(error: HMSException) { + methodChannelResult.success(HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error))) + } + }, + ) + } ?: run { + HMSErrorLogger.returnHMSException("fetchPollList", "No poll state matched", "ARGUMENTS_ERROR", methodChannelResult) } - } - private fun fetchPollQuestions(call: MethodCall, methodChannelResult: MethodChannel.Result, hmssdk: HMSSDK){ + private fun fetchPollQuestions( + call: MethodCall, + methodChannelResult: MethodChannel.Result, + hmssdk: HMSSDK, + ) { val pollId = call.argument("poll_id") val state = call.argument("poll_state") @@ -266,51 +323,63 @@ class HMSPollAction { val pollState = getPollState(state) pollState?.let { - hmssdk.getHmsInteractivityCenter().fetchPollList(it,object : HmsTypedActionResultListener>{ - override fun onSuccess(result: List) { - - val poll = result.find { - _poll -> - _poll.pollId == pollId - } - poll?.let { _poll -> - hmssdk.getHmsInteractivityCenter().fetchPollQuestions(_poll, object : HmsTypedActionResultListener>{ - override fun onSuccess(result: List) { - val map = ArrayList>() - - result.forEach { pollQuestion -> - val pollQuestionMap = HMSPollQuestionExtension.toDictionary(pollQuestion) - pollQuestionMap?.let { _pollQuestionMap -> - map.add(_pollQuestionMap) - } - } - methodChannelResult.success(HMSResultExtension.toDictionary(true, map)) - } - - override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) + hmssdk.getHmsInteractivityCenter().fetchPollList( + it, + object : HmsTypedActionResultListener> { + override fun onSuccess(result: List) { + val poll = + result.find { + _poll -> + _poll.pollId == pollId } + poll?.let { _poll -> + hmssdk.getHmsInteractivityCenter().fetchPollQuestions( + _poll, + object : HmsTypedActionResultListener> { + override fun onSuccess(result: List) { + val map = ArrayList>() + + result.forEach { pollQuestion -> + val pollQuestionMap = HMSPollQuestionExtension.toDictionary(pollQuestion) + pollQuestionMap?.let { _pollQuestionMap -> + map.add(_pollQuestionMap) + } + } + methodChannelResult.success(HMSResultExtension.toDictionary(true, map)) + } - }) - }?:run{ - HMSErrorLogger.logError("fetchPollQuestions","No poll with given pollId found","NULL_ERROR") + override fun onError(error: HMSException) { + methodChannelResult.success( + HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error)), + ) + } + }, + ) + } ?: run { + HMSErrorLogger.logError("fetchPollQuestions", "No poll with given pollId found", "NULL_ERROR") + } } - } - override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) - } - }) - }?:run{ + override fun onError(error: HMSException) { + methodChannelResult.success( + HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error)), + ) + } + }, + ) + } ?: run { HMSErrorLogger.returnArgumentsError("No state matched with given state") } - }?:run{ + } ?: run { HMSErrorLogger.returnArgumentsError("pollId is null") } } - private fun getPollResults(call: MethodCall, methodChannelResult: MethodChannel.Result, hmssdk: HMSSDK){ - + private fun getPollResults( + call: MethodCall, + methodChannelResult: MethodChannel.Result, + hmssdk: HMSSDK, + ) { val pollId = call.argument("poll_id") val state = call.argument("poll_state") @@ -318,48 +387,61 @@ class HMSPollAction { val pollState = getPollState(state) pollState?.let { - hmssdk.getHmsInteractivityCenter().fetchPollList(it,object : HmsTypedActionResultListener>{ - override fun onSuccess(result: List) { - - val poll = result.find { - _poll -> - _poll.pollId == pollId - } - poll?.let { _poll -> - hmssdk.getHmsInteractivityCenter().getPollResults(_poll, object : HmsTypedActionResultListener{ - override fun onSuccess(result: HmsPoll) { - methodChannelResult.success(HMSResultExtension.toDictionary(true, HMSPollExtension.toDictionary(result))) - } - - override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) + hmssdk.getHmsInteractivityCenter().fetchPollList( + it, + object : HmsTypedActionResultListener> { + override fun onSuccess(result: List) { + val poll = + result.find { + _poll -> + _poll.pollId == pollId } + poll?.let { _poll -> + hmssdk.getHmsInteractivityCenter().getPollResults( + _poll, + object : HmsTypedActionResultListener { + override fun onSuccess(result: HmsPoll) { + methodChannelResult.success( + HMSResultExtension.toDictionary(true, HMSPollExtension.toDictionary(result)), + ) + } - }) - }?:run{ - HMSErrorLogger.logError("getPollResults","No poll with given pollId found","NULL_ERROR") + override fun onError(error: HMSException) { + methodChannelResult.success( + HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error)), + ) + } + }, + ) + } ?: run { + HMSErrorLogger.logError("getPollResults", "No poll with given pollId found", "NULL_ERROR") + } } - } - override fun onError(error: HMSException) { - methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) - } - }) - }?:run{ + override fun onError(error: HMSException) { + methodChannelResult.success( + HMSResultExtension.toDictionary(false, HMSExceptionExtension.toDictionary(error)), + ) + } + }, + ) + } ?: run { HMSErrorLogger.returnArgumentsError("No state matched with given state") } - }?:run{ + } ?: run { HMSErrorLogger.returnArgumentsError("pollId is null") } } - private fun getPollState(pollState: String?):HmsPollState?{ - return when(pollState){ - "created"-> HmsPollState.CREATED - "started" -> HmsPollState.STARTED + private fun getPollState(pollState: String?): HmsPollState? { + return when (pollState) { + "created" -> HmsPollState.CREATED + "started" -> HmsPollState.STARTED "stopped" -> HmsPollState.STOPPED - else -> {null} + else -> { + null + } } } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionStoreAction.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionStoreAction.kt index a6bec486b..6eecc5f13 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionStoreAction.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSSessionStoreAction.kt @@ -1,9 +1,6 @@ package live.hms.hmssdk_flutter.methods -import com.google.gson.JsonArray import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonParser import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel.Result import live.hms.hmssdk_flutter.HMSCommonAction diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerExtension.kt index d623f20ad..b02ac910c 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerExtension.kt @@ -3,12 +3,9 @@ package live.hms.hmssdk_flutter.poll_extension import live.hms.hmssdk_flutter.HMSErrorLogger import live.hms.hmssdk_flutter.poll_extension.HMSPollQuestionExtension.Companion.getStringFromPollQuestionType import live.hms.video.polls.models.answer.HmsPollAnswer -import live.hms.video.polls.models.network.HMSPollQuestionResponse class HMSPollAnswerExtension { - companion object { - fun toDictionary(answer: HmsPollAnswer?): HashMap? { answer?.let { val map = HashMap() @@ -26,72 +23,76 @@ class HMSPollAnswerExtension { } } - fun toHMSPollAnswer(answerMap: HashMap?): HmsPollAnswer?{ - + fun toHMSPollAnswer(answerMap: HashMap?): HmsPollAnswer? { answerMap?.let { - - val answer = answerMap["answer"]?.let { - it as String - }?:run { - HMSErrorLogger.returnArgumentsError("answer should not be null") - return null - } - - val duration = answerMap["duration"]?.let { - (it as Int).toLong() - }?:run { - HMSErrorLogger.returnArgumentsError("duration should not be null") - return null - } - - val questionId = answerMap["question_id"]?.let { - it as Int - }?:run { - HMSErrorLogger.returnArgumentsError("questionId should not be null") - return null - } - - val questionType = answerMap["question_type"]?.let { - HMSPollQuestionExtension.getPollQuestionTypeFromString(it as String) - }?:run { - HMSErrorLogger.returnArgumentsError("questionType should not be null") - return null - } - - val selectedOption = answerMap["selected_option"]?.let { - it as Int - }?:run { - HMSErrorLogger.returnArgumentsError("selectedOption should not be null") - return null - } - - val selectedOptions = answerMap["selected_options"]?.let { - it as ArrayList - }?:run { - HMSErrorLogger.returnArgumentsError("selectedOptions should not be null") - return null - } - - val skipped = answerMap["skipped"]?.let { - it as Boolean - }?:run { - HMSErrorLogger.returnArgumentsError("skipped should not be null") - return null - } - - val update = answerMap["update"]?.let { - it as Boolean - }?:run { - HMSErrorLogger.returnArgumentsError("update should not be null") - return null - } - - return HmsPollAnswer(questionId,questionType,skipped,selectedOption,selectedOptions,answer,update,duration) - - - }?:run { + val answer = + answerMap["answer"]?.let { + it as String + } ?: run { + HMSErrorLogger.returnArgumentsError("answer should not be null") + return null + } + + val duration = + answerMap["duration"]?.let { + (it as Int).toLong() + } ?: run { + HMSErrorLogger.returnArgumentsError("duration should not be null") + return null + } + + val questionId = + answerMap["question_id"]?.let { + it as Int + } ?: run { + HMSErrorLogger.returnArgumentsError("questionId should not be null") + return null + } + + val questionType = + answerMap["question_type"]?.let { + HMSPollQuestionExtension.getPollQuestionTypeFromString(it as String) + } ?: run { + HMSErrorLogger.returnArgumentsError("questionType should not be null") + return null + } + + val selectedOption = + answerMap["selected_option"]?.let { + it as Int + } ?: run { + HMSErrorLogger.returnArgumentsError("selectedOption should not be null") + return null + } + + val selectedOptions = + answerMap["selected_options"]?.let { + it as ArrayList + } ?: run { + HMSErrorLogger.returnArgumentsError("selectedOptions should not be null") + return null + } + + val skipped = + answerMap["skipped"]?.let { + it as Boolean + } ?: run { + HMSErrorLogger.returnArgumentsError("skipped should not be null") + return null + } + + val update = + answerMap["update"]?.let { + it as Boolean + } ?: run { + HMSErrorLogger.returnArgumentsError("update should not be null") + return null + } + + return HmsPollAnswer(questionId, questionType, skipped, selectedOption, selectedOptions, answer, update, duration) + } ?: run { return null } } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerResponseExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerResponseExtension.kt index cf0c2e38e..852d72f3b 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerResponseExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerResponseExtension.kt @@ -4,14 +4,12 @@ import live.hms.hmssdk_flutter.HMSExceptionExtension import live.hms.video.polls.models.answer.PollAnswerResponse class HMSPollAnswerResponseExtension { - - companion object{ - fun toDictionary(pollAnswerResponse: PollAnswerResponse):HashMap?{ - - val map = HashMap() - val resultMapList = ArrayList>() - pollAnswerResponse.result.forEach{ - val resultMap = HashMap() + companion object { + fun toDictionary(pollAnswerResponse: PollAnswerResponse): HashMap? { + val map = HashMap() + val resultMapList = ArrayList>() + pollAnswerResponse.result.forEach { + val resultMap = HashMap() resultMap["correct"] = it.correct resultMap["error"] = HMSExceptionExtension.toDictionary(it.error) resultMap["question_index"] = it.questionIndex @@ -24,4 +22,4 @@ class HMSPollAnswerResponseExtension { return map } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollBuilderExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollBuilderExtension.kt index e53f9765a..d62a8fe98 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollBuilderExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollBuilderExtension.kt @@ -1,26 +1,23 @@ package live.hms.hmssdk_flutter.poll_extension import live.hms.hmssdk_flutter.HMSErrorLogger -import live.hms.hmssdk_flutter.HMSRoleExtension import live.hms.video.polls.HMSPollBuilder import live.hms.video.polls.HMSPollQuestionBuilder import live.hms.video.polls.models.HmsPollCategory import live.hms.video.polls.models.HmsPollUserTrackingMode -import live.hms.video.polls.models.question.HMSPollQuestion import live.hms.video.polls.models.question.HMSPollQuestionType import live.hms.video.sdk.HMSSDK import live.hms.video.sdk.models.role.HMSRole -import okhttp3.internal.notify class HMSPollBuilderExtension { - - companion object{ - - fun toHMSPollBuilder(pollBuilderMap: HashMap?,hmssdk: HMSSDK):HMSPollBuilder?{ - + companion object { + fun toHMSPollBuilder( + pollBuilderMap: HashMap?, + hmssdk: HMSSDK, + ): HMSPollBuilder? { pollBuilderMap?.let { val pollBuilder = HMSPollBuilder.Builder() - pollBuilderMap["anonymous"]?.let{ + pollBuilderMap["anonymous"]?.let { pollBuilder.withAnonymous(it as Boolean) } pollBuilderMap["duration"]?.let { @@ -41,13 +38,12 @@ class HMSPollBuilderExtension { val questions = it as ArrayList<*> val pollQuestions = ArrayList() questions.forEach { pollQuestion -> - pollQuestion as HashMap? + pollQuestion as HashMap? val pollQuestionBuilder = getPollQuestionBuilder(pollQuestion) - pollQuestionBuilder?.let {questionBuilder -> + pollQuestionBuilder?.let { questionBuilder -> pollBuilder.addQuestion(questionBuilder) } } - } val availableRoles = hmssdk.getRoles() @@ -66,9 +62,9 @@ class HMSPollBuilderExtension { pollBuilderMap["roles_that_can_vote"]?.let { val roles = it as ArrayList<*> val rolesThatCanVote = ArrayList() - roles.forEach{ forEveryRole -> + roles.forEach { forEveryRole -> val role = availableRoles.find { role -> role.name == forEveryRole } - role?.let {currentRole -> + role?.let { currentRole -> rolesThatCanVote.add(currentRole) } } @@ -80,122 +76,123 @@ class HMSPollBuilderExtension { } return pollBuilder.build() - - }?: run { + } ?: run { return null } - - } private fun getPollCategoryFromString(category: String): HmsPollCategory { - return when(category) { + return when (category) { "poll" -> HmsPollCategory.POLL "quiz" -> HmsPollCategory.QUIZ else -> HmsPollCategory.POLL } } - - private fun getPollUserTrackingModeFromString(pollUserTrackingMode: String):HmsPollUserTrackingMode{ - return when(pollUserTrackingMode){ + private fun getPollUserTrackingModeFromString(pollUserTrackingMode: String): HmsPollUserTrackingMode { + return when (pollUserTrackingMode) { "user_id" -> HmsPollUserTrackingMode.USER_ID "peer_id" -> HmsPollUserTrackingMode.PEER_ID - "username"-> HmsPollUserTrackingMode.USERNAME + "username" -> HmsPollUserTrackingMode.USERNAME else -> HmsPollUserTrackingMode.USER_ID - } } - private fun getPollQuestionTypeFromString(pollQuestionType: String): HMSPollQuestionType{ - return when(pollQuestionType){ + private fun getPollQuestionTypeFromString(pollQuestionType: String): HMSPollQuestionType { + return when (pollQuestionType) { "multi_choice" -> HMSPollQuestionType.multiChoice "short_answer" -> HMSPollQuestionType.shortAnswer - "long_answer" -> HMSPollQuestionType.longAnswer + "long_answer" -> HMSPollQuestionType.longAnswer "single_choice" -> HMSPollQuestionType.singleChoice else -> HMSPollQuestionType.singleChoice } } - private fun getPollQuestionBuilder(pollQuestion: HashMap?):HMSPollQuestionBuilder?{ - - val pollQuestionBuilder : HMSPollQuestionBuilder.Builder + private fun getPollQuestionBuilder(pollQuestion: HashMap?): HMSPollQuestionBuilder? { + val pollQuestionBuilder: HMSPollQuestionBuilder.Builder pollQuestion?.let { + val type = + pollQuestion["type"]?.let { type -> + getPollQuestionTypeFromString(type as String) + } ?: run { + HMSErrorLogger.returnArgumentsError("type should not be null") + return null + } - val type = pollQuestion["type"]?.let {type -> - getPollQuestionTypeFromString(type as String) - }?:run{ - HMSErrorLogger.returnArgumentsError("type should not be null") - return null - } - - type.let {questionType -> + type.let { questionType -> pollQuestionBuilder = HMSPollQuestionBuilder.Builder(questionType) } - val canSkip = pollQuestion["can_skip"]?.let {canSkipQuestion -> - canSkipQuestion as Boolean - } + val canSkip = + pollQuestion["can_skip"]?.let { canSkipQuestion -> + canSkipQuestion as Boolean + } canSkip?.let { canSkipQuestion -> pollQuestionBuilder.withCanBeSkipped(canSkipQuestion) } - val text = pollQuestion["text"]?.let { text -> - text as String - } + val text = + pollQuestion["text"]?.let { text -> + text as String + } text?.let { pollQuestionBuilder.withTitle(text) } - val duration = pollQuestion["duration"]?.let { duration -> - (duration as Int).toLong() - } + val duration = + pollQuestion["duration"]?.let { duration -> + (duration as Int).toLong() + } duration?.let { duration -> pollQuestionBuilder.withDuration(duration) } - val weight = pollQuestion["weight"]?.let {weight -> - weight as Int - } + val weight = + pollQuestion["weight"]?.let { weight -> + weight as Int + } weight?.let { pollQuestionBuilder.withWeight(weight) } - val answerHidden = pollQuestion["answer_hidden"]?.let {answerHidden -> - answerHidden as Boolean - } + val answerHidden = + pollQuestion["answer_hidden"]?.let { answerHidden -> + answerHidden as Boolean + } answerHidden?.let { pollQuestionBuilder.withAnswerHidden(answerHidden) } - val maxLength = pollQuestion["max_length"]?.let { maxLength -> - (maxLength as Int).toLong() - } + val maxLength = + pollQuestion["max_length"]?.let { maxLength -> + (maxLength as Int).toLong() + } maxLength?.let { pollQuestionBuilder.withMaxLength(maxLength) } - - val minLength = pollQuestion["min_length"]?.let { minLength -> - (minLength as Int).toLong() - } + val minLength = + pollQuestion["min_length"]?.let { minLength -> + (minLength as Int).toLong() + } minLength?.let { pollQuestionBuilder.withMinLength(minLength) } - val pollOptions = pollQuestion["poll_options"]?.let { options -> - options as ArrayList - }?:run { - HMSErrorLogger.returnArgumentsError("pollOptions should not be null") - null - } + val pollOptions = + pollQuestion["poll_options"]?.let { options -> + options as ArrayList + } ?: run { + HMSErrorLogger.returnArgumentsError("pollOptions should not be null") + null + } pollOptions?.let { pollOptions.forEach { @@ -203,45 +200,46 @@ class HMSPollBuilderExtension { } } - val option = pollQuestion["options"]?.let { options -> + val option = + pollQuestion["options"]?.let { options -> - options as ArrayList> - val optionMap = ArrayList>() + options as ArrayList> + val optionMap = ArrayList>() - options.forEach { - val text = it["text"] as String? - text?.let { optionText -> - val isCorrect = it["is_correct"] - isCorrect?.let { isCorrectOption -> - optionMap.add(Pair(optionText,isCorrectOption)) + options.forEach { + val text = it["text"] as String? + text?.let { optionText -> + val isCorrect = it["is_correct"] + isCorrect?.let { isCorrectOption -> + optionMap.add(Pair(optionText, isCorrectOption)) + } } } + optionMap + } ?: run { + HMSErrorLogger.returnArgumentsError("options should not be null") + null } - optionMap - }?:run { - HMSErrorLogger.returnArgumentsError("options should not be null") - null - } option?.let { option.forEach { - pollQuestionBuilder.addQuizOption(it.first,it.second) + pollQuestionBuilder.addQuizOption(it.first, it.second) } } - val canChangeResponse = pollQuestion["can_change_response"]?.let { canChangeResponse -> - canChangeResponse as Boolean - } + val canChangeResponse = + pollQuestion["can_change_response"]?.let { canChangeResponse -> + canChangeResponse as Boolean + } canChangeResponse?.let { pollQuestionBuilder.withCanChangeResponse(it) } return pollQuestionBuilder.build() - - }?:run{ + } ?: run { return null } } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollExtension.kt index e70358047..7852b20fb 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollExtension.kt @@ -5,11 +5,9 @@ import live.hms.hmssdk_flutter.HMSRoleExtension import live.hms.video.polls.models.* class HMSPollExtension { - companion object { fun toDictionary(poll: HmsPoll): HashMap { - - val map = HashMap() + val map = HashMap() map["anonymous"] = poll.anonymous map["category"] = getPollCategory(poll.category) @@ -19,22 +17,22 @@ class HMSPollExtension { map["poll_id"] = poll.pollId map["question_count"] = poll.questionCount - val questions = ArrayList?>() - poll.questions?.forEach{ + val questions = ArrayList?>() + poll.questions?.forEach { questions.add(HMSPollQuestionExtension.toDictionary(it)) } map["questions"] = questions map["result"] = HMSPollResultDisplayExtension.toDictionary(poll.result) - val rolesThatCanViewResponses = ArrayList?>() - poll.rolesThatCanViewResponses.forEach{ + val rolesThatCanViewResponses = ArrayList?>() + poll.rolesThatCanViewResponses.forEach { rolesThatCanViewResponses.add(HMSRoleExtension.toDictionary(it)) } map["roles_that_can_view_responses"] = rolesThatCanViewResponses - val rolesThatCanVote = ArrayList?>() - poll.rolesThatCanVote.forEach{ + val rolesThatCanVote = ArrayList?>() + poll.rolesThatCanVote.forEach { rolesThatCanVote.add(HMSRoleExtension.toDictionary(it)) } map["roles_that_can_vote"] = rolesThatCanVote @@ -44,7 +42,7 @@ class HMSPollExtension { map["state"] = getPollState(poll.state) map["stopped_at"] = poll.stoppedAt?.let { it * 1000 - }?:run { + } ?: run { null } map["title"] = poll.title @@ -52,16 +50,16 @@ class HMSPollExtension { return map } - private fun getPollCategory(pollCategory: HmsPollCategory):String?{ - return when(pollCategory){ + private fun getPollCategory(pollCategory: HmsPollCategory): String? { + return when (pollCategory) { HmsPollCategory.POLL -> "poll" HmsPollCategory.QUIZ -> "quiz" else -> null } } - private fun getPollUserTrackingMode(pollUserTrackingMode: HmsPollUserTrackingMode?):String?{ - return when(pollUserTrackingMode){ + private fun getPollUserTrackingMode(pollUserTrackingMode: HmsPollUserTrackingMode?): String? { + return when (pollUserTrackingMode) { HmsPollUserTrackingMode.USER_ID -> "user_id" HmsPollUserTrackingMode.PEER_ID -> "peer_id" HmsPollUserTrackingMode.USERNAME -> "username" @@ -69,8 +67,8 @@ class HMSPollExtension { } } - private fun getPollState(pollState: HmsPollState):String?{ - return when(pollState){ + private fun getPollState(pollState: HmsPollState): String? { + return when (pollState) { HmsPollState.CREATED -> "created" HmsPollState.STARTED -> "started" HmsPollState.STOPPED -> "stopped" @@ -78,8 +76,8 @@ class HMSPollExtension { } } - fun getPollUpdateType(hmsPollUpdateType: HMSPollUpdateType):String?{ - return when(hmsPollUpdateType){ + fun getPollUpdateType(hmsPollUpdateType: HMSPollUpdateType): String? { + return when (hmsPollUpdateType) { HMSPollUpdateType.started -> "started" HMSPollUpdateType.stopped -> "stopped" HMSPollUpdateType.resultsupdated -> "results_updated" @@ -88,4 +86,3 @@ class HMSPollExtension { } } } - diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardEntryExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardEntryExtension.kt index 335542faa..39d73eae0 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardEntryExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardEntryExtension.kt @@ -3,12 +3,9 @@ package live.hms.hmssdk_flutter.poll_extension import live.hms.video.polls.network.HMSPollLeaderboardEntry class HMSPollLeaderboardEntryExtension { - - companion object{ - - fun toDictionary(hmsPollLeaderboardEntry: HMSPollLeaderboardEntry):HashMap{ - - val map = HashMap() + companion object { + fun toDictionary(hmsPollLeaderboardEntry: HMSPollLeaderboardEntry): HashMap { + val map = HashMap() map["correct_responses"] = hmsPollLeaderboardEntry.correctResponses map["duration"] = hmsPollLeaderboardEntry.duration @@ -19,4 +16,4 @@ class HMSPollLeaderboardEntryExtension { return map } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardResponseExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardResponseExtension.kt index a87ca438c..47fafbbf4 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardResponseExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardResponseExtension.kt @@ -3,14 +3,11 @@ package live.hms.hmssdk_flutter.poll_extension import live.hms.video.polls.network.PollLeaderboardResponse class HMSPollLeaderboardResponseExtension { + companion object { + fun toDictionary(pollLeaderboardResponse: PollLeaderboardResponse): HashMap { + val map = HashMap() - companion object{ - - fun toDictionary(pollLeaderboardResponse: PollLeaderboardResponse):HashMap{ - - val map = HashMap() - - val entryMap = ArrayList>() + val entryMap = ArrayList>() pollLeaderboardResponse.entries?.forEach { pollLeaderboardResponse -> entryMap.add(HMSPollLeaderboardEntryExtension.toDictionary(pollLeaderboardResponse)) @@ -22,4 +19,4 @@ class HMSPollLeaderboardResponseExtension { return map } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardSummaryExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardSummaryExtension.kt index b5eee64d7..22b3692a2 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardSummaryExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollLeaderboardSummaryExtension.kt @@ -3,16 +3,13 @@ package live.hms.hmssdk_flutter.poll_extension import live.hms.video.polls.network.HMSPollLeaderboardSummary class HMSPollLeaderboardSummaryExtension { - - companion object{ - - fun toDictionary(pollLeaderboardSummary: HMSPollLeaderboardSummary?):HashMap?{ - - if(pollLeaderboardSummary == null){ + companion object { + fun toDictionary(pollLeaderboardSummary: HMSPollLeaderboardSummary?): HashMap? { + if (pollLeaderboardSummary == null) { return null } - val map = HashMap() + val map = HashMap() map["average_score"] = pollLeaderboardSummary.averageScore map["average_time"] = pollLeaderboardSummary.averageTime @@ -23,4 +20,4 @@ class HMSPollLeaderboardSummaryExtension { return map } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionAnswerExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionAnswerExtension.kt index e46e44a35..bcff675d3 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionAnswerExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionAnswerExtension.kt @@ -4,47 +4,47 @@ import live.hms.hmssdk_flutter.HMSErrorLogger import live.hms.video.polls.models.answer.HMSPollQuestionAnswer class HMSPollQuestionAnswerExtension { - companion object { - fun toDictionary(answer: HMSPollQuestionAnswer?):HashMap?{ + fun toDictionary(answer: HMSPollQuestionAnswer?): HashMap? { answer?.let { - val map = HashMap() + val map = HashMap() map["hidden"] = it.hidden map["option"] = it.option map["options"] = it.options return map - }?:run{ + } ?: run { return null } } - fun toPollQuestionAnswer(pollQuestionAnswer: HashMap?): HMSPollQuestionAnswer?{ - + fun toPollQuestionAnswer(pollQuestionAnswer: HashMap?): HMSPollQuestionAnswer? { pollQuestionAnswer?.let { + val hidden = + pollQuestionAnswer["hidden"]?.let { + it as Boolean + } ?: run { + HMSErrorLogger.returnArgumentsError("hidden should not be null") + return null + } + val option = + pollQuestionAnswer["option"]?.let { + it as Int + } ?: run { + HMSErrorLogger.returnArgumentsError("option should not be null") + return null + } + val options = + pollQuestionAnswer["options"]?.let { + it as ArrayList + } ?: run { + HMSErrorLogger.returnArgumentsError("options should not be null") + return null + } - val hidden = pollQuestionAnswer["hidden"]?.let { - it as Boolean - }?:run { - HMSErrorLogger.returnArgumentsError("hidden should not be null") - return null - } - val option = pollQuestionAnswer["option"]?.let { - it as Int - }?:run { - HMSErrorLogger.returnArgumentsError("option should not be null") - return null - } - val options = pollQuestionAnswer["options"]?.let { - it as ArrayList - }?:run { - HMSErrorLogger.returnArgumentsError("options should not be null") - return null - } - - return HMSPollQuestionAnswer(hidden,option,options) - }?:run { + return HMSPollQuestionAnswer(hidden, option, options) + } ?: run { return null } } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionExtension.kt index 765e12443..acef20211 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionExtension.kt @@ -7,130 +7,150 @@ import live.hms.video.polls.models.question.HMSPollQuestionOption import live.hms.video.polls.models.question.HMSPollQuestionType class HMSPollQuestionExtension { - companion object{ - fun toDictionary(question:HMSPollQuestion?):HashMap?{ - - question?.let { - val map = HashMap() - map["question_id"] = it.questionID - map["can_skip"] = it.canSkip - map["correct_answer"] = HMSPollQuestionAnswerExtension.toDictionary(it.correctAnswer) - map["duration"] = it.duration - - val responses = ArrayList?>() - it.myResponses.forEach { - response -> responses.add(HMSPollAnswerExtension.toDictionary(response)) - } - map["my_responses"] = responses - - val options = ArrayList?>() - it.options?.forEach{ - option -> options.add(HMSPollQuestionOptionExtension.toDictionary(option)) - } - map["options"] = options + companion object { + fun toDictionary(question: HMSPollQuestion?): HashMap? { + question?.let { + val map = HashMap() + map["question_id"] = it.questionID + map["can_skip"] = it.canSkip + map["correct_answer"] = HMSPollQuestionAnswerExtension.toDictionary(it.correctAnswer) + map["duration"] = it.duration + + val responses = ArrayList?>() + it.myResponses.forEach { + response -> + responses.add(HMSPollAnswerExtension.toDictionary(response)) + } + map["my_responses"] = responses - map["text"] = it.text - map["type"] = getStringFromPollQuestionType(it.type) - map["voted"] = it.voted - map["weight"] = it.weight - map["can_change_response"] = it.canChangeResponse - return map - }?:run { - return null + val options = ArrayList?>() + it.options?.forEach { + option -> + options.add(HMSPollQuestionOptionExtension.toDictionary(option)) } + map["options"] = options + + map["text"] = it.text + map["type"] = getStringFromPollQuestionType(it.type) + map["voted"] = it.voted + map["weight"] = it.weight + map["can_change_response"] = it.canChangeResponse + return map + } ?: run { + return null } + } - fun toHMSPollQuestion(pollQuestion: HashMap?):HMSPollQuestion?{ - + fun toHMSPollQuestion(pollQuestion: HashMap?): HMSPollQuestion? { pollQuestion?.let { + val questionID = + pollQuestion["question_id"]?.let { + it as Int + } ?: run { + HMSErrorLogger.returnArgumentsError("questionID should not be null") + return null + } - val questionID = pollQuestion["question_id"]?.let { - it as Int - }?:run { - HMSErrorLogger.returnArgumentsError("questionID should not be null") - return null - } - - val canSkip = pollQuestion["can_skip"]?.let { - it as Boolean - }?:run { - HMSErrorLogger.returnArgumentsError("canSkip should not be null") - return null - } - val correctAnswer = pollQuestion["correct_answer"]?.let { - HMSPollQuestionAnswerExtension.toPollQuestionAnswer(it as HashMap?) - }?:run { - HMSErrorLogger.returnArgumentsError("correctAnswer should not be null") - return null - } - val duration = pollQuestion["duration"]?.let { - (it as Int).toLong() - }?:run { - HMSErrorLogger.returnArgumentsError("duration should not be null") - return null - } + val canSkip = + pollQuestion["can_skip"]?.let { + it as Boolean + } ?: run { + HMSErrorLogger.returnArgumentsError("canSkip should not be null") + return null + } + val correctAnswer = + pollQuestion["correct_answer"]?.let { + HMSPollQuestionAnswerExtension.toPollQuestionAnswer(it as HashMap?) + } ?: run { + HMSErrorLogger.returnArgumentsError("correctAnswer should not be null") + return null + } + val duration = + pollQuestion["duration"]?.let { + (it as Int).toLong() + } ?: run { + HMSErrorLogger.returnArgumentsError("duration should not be null") + return null + } - val myResponses = pollQuestion["my_responses"]?.let { - val responses = it as ArrayList<*> - val myResponses = ArrayList() - responses.forEach { response -> - val answer = HMSPollAnswerExtension.toHMSPollAnswer(response as HashMap?) - answer?.let { value -> - myResponses.add(value) + val myResponses = + pollQuestion["my_responses"]?.let { + val responses = it as ArrayList<*> + val myResponses = ArrayList() + responses.forEach { response -> + val answer = HMSPollAnswerExtension.toHMSPollAnswer(response as HashMap?) + answer?.let { value -> + myResponses.add(value) + } } + myResponses + } ?: run { + HMSErrorLogger.returnArgumentsError("myResponses should not be null") + return null } - myResponses - }?:run { - HMSErrorLogger.returnArgumentsError("myResponses should not be null") - return null - } - val options = pollQuestion["options"]?.let { - val options = it as ArrayList<*> - val pollOptions = ArrayList() - options.forEach { option -> - val pollOption = HMSPollQuestionOptionExtension.toHMSPollQuestionOption(option as HashMap?) - pollOption?.let { value -> - pollOptions.add(value) + val options = + pollQuestion["options"]?.let { + val options = it as ArrayList<*> + val pollOptions = ArrayList() + options.forEach { option -> + val pollOption = HMSPollQuestionOptionExtension.toHMSPollQuestionOption(option as HashMap?) + pollOption?.let { value -> + pollOptions.add(value) + } } + pollOptions + } ?: run { + HMSErrorLogger.returnArgumentsError("options should not be null") + return null } - pollOptions - }?:run { - HMSErrorLogger.returnArgumentsError("options should not be null") - return null - } - - val text = pollQuestion["text"]?.let { - it as String - }?:run { - HMSErrorLogger.returnArgumentsError("text should not be null") - return null - } - - val type = pollQuestion["type"]?.let { - getPollQuestionTypeFromString(it as String) - }?:run { - HMSErrorLogger.returnArgumentsError("type should not be null") - return null - } - val weight = pollQuestion["weight"]?.let { - it as Int - }?:run { - HMSErrorLogger.returnArgumentsError("weight should not be null") - return null - } + val text = + pollQuestion["text"]?.let { + it as String + } ?: run { + HMSErrorLogger.returnArgumentsError("text should not be null") + return null + } - return HMSPollQuestion(questionID,type,text,canSkip,false,duration,weight,null,null,options,correctAnswer,false,myResponses) + val type = + pollQuestion["type"]?.let { + getPollQuestionTypeFromString(it as String) + } ?: run { + HMSErrorLogger.returnArgumentsError("type should not be null") + return null + } + val weight = + pollQuestion["weight"]?.let { + it as Int + } ?: run { + HMSErrorLogger.returnArgumentsError("weight should not be null") + return null + } - }?:run { + return HMSPollQuestion( + questionID, + type, + text, + canSkip, + false, + duration, + weight, + null, + null, + options, + correctAnswer, + false, + myResponses, + ) + } ?: run { return null } } - fun getStringFromPollQuestionType(pollQuestionType: HMSPollQuestionType):String?{ - return when(pollQuestionType){ + fun getStringFromPollQuestionType(pollQuestionType: HMSPollQuestionType): String? { + return when (pollQuestionType) { HMSPollQuestionType.multiChoice -> "multi_choice" HMSPollQuestionType.shortAnswer -> "short_answer" HMSPollQuestionType.longAnswer -> "long_answer" @@ -139,8 +159,8 @@ class HMSPollQuestionExtension { } } - fun getPollQuestionTypeFromString(pollQuestionType: String):HMSPollQuestionType?{ - return when(pollQuestionType){ + fun getPollQuestionTypeFromString(pollQuestionType: String): HMSPollQuestionType? { + return when (pollQuestionType) { "multi_choice" -> HMSPollQuestionType.multiChoice "short_answer" -> HMSPollQuestionType.shortAnswer "long_answer" -> HMSPollQuestionType.longAnswer @@ -149,4 +169,4 @@ class HMSPollQuestionExtension { } } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionOptionExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionOptionExtension.kt index 81df38fd6..b5665a7a6 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionOptionExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionOptionExtension.kt @@ -4,64 +4,65 @@ import live.hms.hmssdk_flutter.HMSErrorLogger import live.hms.video.polls.models.question.HMSPollQuestionOption class HMSPollQuestionOptionExtension { - - companion object{ - - fun toDictionary(pollOptions: HMSPollQuestionOption?): HashMap?{ - + companion object { + fun toDictionary(pollOptions: HMSPollQuestionOption?): HashMap? { pollOptions?.let { - val map = HashMap() + val map = HashMap() map["index"] = it.index map["text"] = it.text map["vote_count"] = it.voteCount map["weight"] = it.weight return map - }?:run { + } ?: run { return null } } - fun toHMSPollQuestionOption(pollQuestionOptionMap: HashMap?):HMSPollQuestionOption?{ - + fun toHMSPollQuestionOption(pollQuestionOptionMap: HashMap?): HMSPollQuestionOption? { pollQuestionOptionMap?.let { + val index = + pollQuestionOptionMap["index"]?.let { + it as Int + } ?: run { + HMSErrorLogger.returnArgumentsError("index should not be null") + return null + } - val index = pollQuestionOptionMap["index"]?.let { - it as Int - }?:run { - HMSErrorLogger.returnArgumentsError("index should not be null") - return null - } - - val text = pollQuestionOptionMap["text"]?.let { - it as String - }?:run { - HMSErrorLogger.returnArgumentsError("text should not be null") - return null - } + val text = + pollQuestionOptionMap["text"]?.let { + it as String + } ?: run { + HMSErrorLogger.returnArgumentsError("text should not be null") + return null + } - val voteCount = pollQuestionOptionMap["vote_count"]?.let { - (it as Int).toLong() - }?:run { - HMSErrorLogger.returnArgumentsError("voteCount should not be null") - return null - } + val voteCount = + pollQuestionOptionMap["vote_count"]?.let { + (it as Int).toLong() + } ?: run { + HMSErrorLogger.returnArgumentsError("voteCount should not be null") + return null + } - val weight = pollQuestionOptionMap["weight"]?.let { - it as Int - }?:run { - HMSErrorLogger.returnArgumentsError("weight should not be null") - return null - } + val weight = + pollQuestionOptionMap["weight"]?.let { + it as Int + } ?: run { + HMSErrorLogger.returnArgumentsError("weight should not be null") + return null + } - return HMSPollQuestionOption(index,text,weight, + return HMSPollQuestionOption( + index, + text, + weight, case = false, trim = false, - voteCount = voteCount + voteCount = voteCount, ) - - }?: run { + } ?: run { return null } } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResponsePeerInfoExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResponsePeerInfoExtension.kt index 630f6437c..f39535695 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResponsePeerInfoExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResponsePeerInfoExtension.kt @@ -3,16 +3,13 @@ package live.hms.hmssdk_flutter.poll_extension import live.hms.video.polls.models.network.HMSPollResponsePeerInfo class HMSPollResponsePeerInfoExtension { - - companion object{ - - fun toDictionary(hmsPollResponsePeerInfo: HMSPollResponsePeerInfo?):HashMap?{ - - if(hmsPollResponsePeerInfo == null){ + companion object { + fun toDictionary(hmsPollResponsePeerInfo: HMSPollResponsePeerInfo?): HashMap? { + if (hmsPollResponsePeerInfo == null) { return null } - val map = HashMap() + val map = HashMap() map["hash"] = hmsPollResponsePeerInfo.hash map["peer_id"] = hmsPollResponsePeerInfo.peerid @@ -22,4 +19,4 @@ class HMSPollResponsePeerInfoExtension { return map } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResultDisplayExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResultDisplayExtension.kt index 18aeef3b9..0de0ed709 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResultDisplayExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResultDisplayExtension.kt @@ -3,16 +3,13 @@ package live.hms.hmssdk_flutter.poll_extension import live.hms.video.polls.network.PollResultsDisplay class HMSPollResultDisplayExtension { - - companion object{ - - fun toDictionary(pollResultsDisplay: PollResultsDisplay?):HashMap?{ - + companion object { + fun toDictionary(pollResultsDisplay: PollResultsDisplay?): HashMap? { pollResultsDisplay?.let { - val map = HashMap() + val map = HashMap() - val questions = ArrayList?>() - it.questions.forEach{ question -> questions.add(HMSPollStatsQuestionsExtension.toDictionary(question))} + val questions = ArrayList?>() + it.questions.forEach { question -> questions.add(HMSPollStatsQuestionsExtension.toDictionary(question)) } map["questions"] = questions map["total_distinct_users"] = it.totalDistinctUsers @@ -20,9 +17,9 @@ class HMSPollResultDisplayExtension { map["voting_users"] = it.votingUsers return map - }?:run { + } ?: run { return null } } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollStatsQuestionsExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollStatsQuestionsExtension.kt index 104976624..f3b5aa218 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollStatsQuestionsExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollStatsQuestionsExtension.kt @@ -3,22 +3,19 @@ package live.hms.hmssdk_flutter.poll_extension import live.hms.video.polls.models.PollStatsQuestions class HMSPollStatsQuestionsExtension { - - companion object{ - - fun toDictionary(pollStatsQuestions: PollStatsQuestions?):HashMap?{ - + companion object { + fun toDictionary(pollStatsQuestions: PollStatsQuestions?): HashMap? { pollStatsQuestions?.let { - val map = HashMap() + val map = HashMap() map["attempted_times"] = pollStatsQuestions.attemptedTimes map["correct"] = pollStatsQuestions.correct map["options"] = pollStatsQuestions.options map["question_type"] = HMSPollQuestionExtension.getStringFromPollQuestionType(pollStatsQuestions.questionType) map["skipped"] = pollStatsQuestions.skipped return map - }?:run { + } ?: run { return null } } } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt index 063391769..727956d47 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSHLSPlayer.kt @@ -1,31 +1,28 @@ package live.hms.hmssdk_flutter.views -import android.content.BroadcastReceiver import android.content.Context -import android.content.Context.RECEIVER_NOT_EXPORTED -import android.content.Intent -import android.content.IntentFilter -import android.os.Build -import android.util.Log import android.view.LayoutInflater import android.widget.FrameLayout import androidx.media3.common.Player import androidx.media3.common.VideoSize +import androidx.media3.common.text.CueGroup import androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT import androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_ZOOM import androidx.media3.ui.PlayerView +import io.flutter.plugin.common.MethodChannel.Result import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import live.hms.hls_player.* -import live.hms.hmssdk_flutter.Constants -import live.hms.hmssdk_flutter.Constants.Companion.HLS_PLAYER_INTENT import live.hms.hmssdk_flutter.HMSErrorLogger import live.hms.hmssdk_flutter.HmssdkFlutterPlugin import live.hms.hmssdk_flutter.R import live.hms.hmssdk_flutter.hls_player.HLSStatsHandler import live.hms.hmssdk_flutter.hls_player.HMSHLSCueExtension import live.hms.hmssdk_flutter.hls_player.HMSHLSPlaybackStateExtension +import live.hms.hmssdk_flutter.hls_player.HMSHLSPlayerAction +import live.hms.hmssdk_flutter.hls_player.IHLSPlayerActionInterface +import java.lang.ref.WeakReference import java.util.concurrent.TimeUnit /** @@ -42,6 +39,7 @@ class HMSHLSPlayer( ) : FrameLayout(context, null) { var hlsPlayer: HmsHlsPlayer? = null private var hlsPlayerView: PlayerView? = null + private var actions: IHLSPlayerActionInterface? = null /** * Inflate the HLS player view and initialize the HLS player. @@ -51,8 +49,21 @@ class HMSHLSPlayer( * passed from flutter i.e. whether to show controls or not. */ init { + + /** + * [initializeMethodHandler] initializes the interface object + * After this we assign the object to [HMSHLSPlayerAction] using the [assignInterfaceObject] method + */ + initializeMethodHandler() + HMSHLSPlayerAction.assignInterfaceObject(WeakReference(actions)) + // Inflate the HLS player view using the layout inflater service. - hlsPlayerView = (context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater).inflate(R.layout.hms_hls_player, this)?.findViewById(R.id.hlsView) + hlsPlayerView = + ( + context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE, + ) as LayoutInflater + ).inflate(R.layout.hms_hls_player, this)?.findViewById(R.id.hlsView) hlsPlayerView?.let { // Set the HLS player controller visibility based on the showHLSControls flag. @@ -80,14 +91,32 @@ class HMSHLSPlayer( val width = videoSize.width val height = videoSize.height - // landscape play + /** + * This ensures that the stream is fit in case of landscape orientation + * If the orientation is landscape i.e. width >= height we set the mode to + * FIT else it's ZOOM by default + */ if (width >= height) { hlsPlayerView?.resizeMode = RESIZE_MODE_FIT } else { hlsPlayerView?.resizeMode = RESIZE_MODE_ZOOM } + + val hashMap = HashMap() + val args = HashMap() + hashMap["event_name"] = "on_video_size_changed" + args["height"] = height + args["width"] = width + hashMap["data"] = args + CoroutineScope(Dispatchers.Main).launch { + hmssdkFlutterPlugin?.hlsPlayerSink?.success(hashMap) + } } } + + override fun onCues(cueGroup: CueGroup) { + super.onCues(cueGroup) + } }, ) } ?: run { @@ -118,12 +147,6 @@ class HMSHLSPlayer( * onRoomUpdate or onJoin */ player.play(streamUrl) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver(broadcastReceiver, IntentFilter(HLS_PLAYER_INTENT), RECEIVER_NOT_EXPORTED) - }else { - context.registerReceiver(broadcastReceiver, IntentFilter(HLS_PLAYER_INTENT)) - } - /** * Here we add the event listener to listen to the events * of HLS Player @@ -154,10 +177,9 @@ class HMSHLSPlayer( */ fun dispose() { hlsPlayer?.stop() + HLSStatsHandler.removeStatsListener(hlsPlayer) hlsPlayer = null hlsPlayerView = null - context.unregisterReceiver(broadcastReceiver) - HLSStatsHandler.removeStatsListener(hlsPlayer) } /** @@ -221,107 +243,100 @@ class HMSHLSPlayer( } /** - * An object implementing the BroadcastReceiver interface. - * It handles the events related to HLS Controller. + * This handles the method call from flutter channel + * and return the values */ - private val broadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive( - contxt: Context?, - intent: Intent?, - ) { - if (intent?.action == HLS_PLAYER_INTENT) { - when (intent.extras?.getString(Constants.METHOD_CALL)) { - "start_hls_player" -> { - return start(intent.extras?.getString("hls_url")) - } - "stop_hls_player" -> { - return stop() - } - "pause_hls_player" -> { - return pause() - } - "resume_hls_player" -> { - return resume() - } - "seek_to_live_position" -> { - return seekToLivePosition() - } - "seek_forward" -> { - return seekForward(intent.extras?.getInt("seconds")) - } - "seek_backward" -> { - return seekBackward(intent.extras?.getInt("seconds")) - } - "set_volume" -> { - return setVolume(intent.extras?.getInt("volume")) - } - "add_hls_stats_listener" -> { - return HLSStatsHandler.addHLSStatsListener(hmssdkFlutterPlugin, hlsPlayer) - } - "remove_hls_stats_listener" -> { - return HLSStatsHandler.removeStatsListener(hlsPlayer) + private fun initializeMethodHandler() { + actions = + object : IHLSPlayerActionInterface { + /** + * Starts the HLS player with the specified HLS URL. + * - Parameters: + * - hlsUrl: The HLS URL to play. If nil, the HLS URL from hmssdkFlutterPlugin will be used. + */ + override fun start( + hlsUrl: String?, + result: Result, + ) { + hlsUrl?.let { + hlsPlayer?.play(hlsUrl) + } ?: run { + hmssdkFlutterPlugin?.hlsStreamUrl?.let { + hlsPlayer?.play(it) + } ?: run { + HMSErrorLogger.logError("start", "HLS Stream URL is null", "NULL Error") } } - } else { - Log.e("Receiver error", "No receiver found for given action") + result.success(null) } - } - } - /** - * Below methods handles the HLS Player controller calls - */ - private fun seekBackward(seconds: Int?) { - seconds?.let { time -> - hlsPlayer?.seekBackward(time.toLong(), TimeUnit.SECONDS) - } - } + override fun stop(result: Result) { + hlsPlayer?.stop() + result.success(null) + } - private fun seekForward(seconds: Int?) { - seconds?.let { time -> - hlsPlayer?.seekForward(time.toLong(), TimeUnit.SECONDS) - } - } + override fun pause(result: Result) { + hlsPlayer?.pause() + result.success(null) + } - private fun seekToLivePosition() { - hlsPlayer?.seekToLivePosition() - } + override fun resume(result: Result) { + hlsPlayer?.resume() + result.success(null) + } - private fun resume() { - hlsPlayer?.resume() - } + override fun seekToLivePosition(result: Result) { + hlsPlayer?.seekToLivePosition() + result.success(null) + } - private fun pause() { - hlsPlayer?.pause() - } + override fun seekForward( + seconds: Int, + result: Result, + ) { + hlsPlayer?.seekForward(seconds.toLong(), TimeUnit.SECONDS) + result.success(null) + } - private fun stop() { - hlsPlayer?.stop() - } + override fun seekBackward( + seconds: Int, + result: Result, + ) { + hlsPlayer?.seekBackward(seconds.toLong(), TimeUnit.SECONDS) + result.success(null) + } - /** - * Starts the HLS player with the specified HLS URL. - * - Parameters: - * - hlsUrl: The HLS URL to play. If nil, the HLS URL from hmssdkFlutterPlugin will be used. - */ - private fun start(hlsUrl: String?) { - hlsUrl?.let { - hlsPlayer?.play(hlsUrl) - } ?: run { - hmssdkFlutterPlugin?.hlsStreamUrl?.let { - hlsPlayer?.play(it) - } ?: run { - HMSErrorLogger.logError("start", "HLS Stream URL is null", "NULL Error") - } - } - } + override fun setVolume( + volume: Int, + result: Result, + ) { + hlsPlayer?.volume = volume + result.success(null) + } - private fun setVolume(volume: Int?) { - volume?.let { - hlsPlayer?.volume = it - } - } + override fun addHLSStatsListener(result: Result) { + HLSStatsHandler.addHLSStatsListener(hmssdkFlutterPlugin, hlsPlayer) + result.success(null) + } + + override fun removeHLSStatsListener(result: Result) { + HLSStatsHandler.removeStatsListener(hlsPlayer) + result.success(null) + } + override fun areClosedCaptionsSupported(result: Result) { + val areCaptionsSupported = hlsPlayer?.areClosedCaptionsSupported() + result.success(areCaptionsSupported) + } + + override fun enableClosedCaptions(result: Result) { + TODO("Not yet implemented") + } + + override fun disableClosedCaptions(result: Result) { + TODO("Not yet implemented") + } + } + } /********************************************/ } diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSTextureView.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSTextureView.kt index 82186a16e..c328c689f 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSTextureView.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSTextureView.kt @@ -8,70 +8,81 @@ import io.flutter.view.TextureRegistry import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import live.hms.hmssdk_flutter.HMSTrackUpdateExtension import live.hms.video.media.tracks.HMSVideoTrack import live.hms.videoview.VideoViewStateChangeListener import live.hms.videoview.textureview.HMSTextureRenderer class HMSTextureView( texture: SurfaceTexture, - private var entry: TextureRegistry.SurfaceTextureEntry? -):EventChannel.StreamHandler{ - + private var entry: TextureRegistry.SurfaceTextureEntry?, +) : EventChannel.StreamHandler { private var eventChannel: EventChannel? = null private var eventSink: EventSink? = null private var hmsTextureRenderer: HMSTextureRenderer? = null private var uid: Long? = null + init { hmsTextureRenderer = HMSTextureRenderer(texture) uid = entry?.id() } - private val videoViewStateChangeListener = object : VideoViewStateChangeListener{ - override fun onResolutionChange(newWidth: kotlin.Int, newHeight: kotlin.Int) { - val args = HashMap() - args["event_name"] = "on_resolution_changed" - val data = HashMap() - data["height"] = newHeight - data["width"] = newWidth - args["data"] = data - if (args["data"] != null) { - CoroutineScope(Dispatchers.Main).launch { - eventSink?.success(args) + private val videoViewStateChangeListener = + object : VideoViewStateChangeListener { + override fun onResolutionChange( + newWidth: kotlin.Int, + newHeight: kotlin.Int, + ) { + val args = HashMap() + args["event_name"] = "on_resolution_changed" + val data = HashMap() + data["height"] = newHeight + data["width"] = newWidth + args["data"] = data + if (args["data"] != null) { + CoroutineScope(Dispatchers.Main).launch { + eventSink?.success(args) + } } } - } - override fun onFirstFrameRendered() { - super.onFirstFrameRendered() + override fun onFirstFrameRendered() { + super.onFirstFrameRendered() + } } - } - fun addTrack(track: HMSVideoTrack, disableAutoSimulcastLayerSelect: Boolean, height: Int? = null, width: Int? = null){ - Log.i("HMSTextureView","Add Track called for track: ${track.trackId}") + fun addTrack( + track: HMSVideoTrack, + disableAutoSimulcastLayerSelect: Boolean, + height: Int? = null, + width: Int? = null, + ) { + Log.i("HMSTextureView", "Add Track called for track: ${track.trackId}") hmsTextureRenderer?.addVideoViewStateChangeListener(videoViewStateChangeListener) hmsTextureRenderer?.disableAutoSimulcastLayerSelect(disableAutoSimulcastLayerSelect) - if(!disableAutoSimulcastLayerSelect){ + if (!disableAutoSimulcastLayerSelect) { height?.let { videoViewHeight -> width?.let { videoViewWidth -> - hmsTextureRenderer?.displayResolution(videoViewWidth,videoViewHeight) + hmsTextureRenderer?.displayResolution(videoViewWidth, videoViewHeight) } } } - hmsTextureRenderer?.addTrack(track,true) + hmsTextureRenderer?.addTrack(track, true) } - fun setDisplayResolution(width: Int, height: Int){ - hmsTextureRenderer?.displayResolution(width,height) + fun setDisplayResolution( + width: Int, + height: Int, + ) { + hmsTextureRenderer?.displayResolution(width, height) } - fun removeTrack(){ - Log.i("HMSTextureView","Remove Track called") + fun removeTrack() { + Log.i("HMSTextureView", "Remove Track called") hmsTextureRenderer?.removeTrack() } - fun disposeTextureView(){ - Log.i("HMSTextureView","disposeTextureView called") + fun disposeTextureView() { + Log.i("HMSTextureView", "disposeTextureView called") removeTrack() entry?.release() entry = null @@ -80,15 +91,18 @@ class HMSTextureView( eventSink = null } - fun setTextureViewEventChannel(eventChannel:EventChannel){ - this.eventChannel = eventChannel + fun setTextureViewEventChannel(eventChannel: EventChannel) { + this.eventChannel = eventChannel } - override fun onListen(arguments: Any?, events: EventSink?) { + override fun onListen( + arguments: Any?, + events: EventSink?, + ) { eventSink = events } override fun onCancel(arguments: Any?) { eventSink = null } -} \ No newline at end of file +} diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt index a015e7822..50ecf4119 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt @@ -2,7 +2,6 @@ package live.hms.hmssdk_flutter.views import android.content.BroadcastReceiver import android.content.Context -import android.content.Context.RECEIVER_EXPORTED import android.content.Context.RECEIVER_NOT_EXPORTED import android.content.Intent import android.content.IntentFilter @@ -111,7 +110,7 @@ class HMSVideoView( hmsVideoView?.addTrack(track) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { context.registerReceiver(broadcastReceiver, IntentFilter(track.trackId), RECEIVER_NOT_EXPORTED) - }else { + } else { context.registerReceiver(broadcastReceiver, IntentFilter(track.trackId)) } } else { diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt index cf903c056..21cd984ee 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt @@ -81,7 +81,14 @@ class HMSVideoViewFactory(private val plugin: HmssdkFlutterPlugin) : requireNotNull( context, ), - viewId, creationParams, track, setMirror!!, scaleType, matchParent, disableAutoSimulcastLayerSelect, plugin, + viewId, + creationParams, + track, + setMirror!!, + scaleType, + matchParent, + disableAutoSimulcastLayerSelect, + plugin, ) } } diff --git a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt index 35ad1f0ba..593ea824a 100644 --- a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt +++ b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt @@ -12,7 +12,23 @@ https://100ms.atlassian.net/browse/FLUT-279 - Removed `flutter_foreground_task` from `hms_room_kit` https://100ms.atlassian.net/browse/FLUT-280 +- Main Player UI with Chat below +https://100ms.atlassian.net/browse/FLUT-262 + +- Description Pane +https://100ms.atlassian.net/browse/FLUT-263 + +- Remove webrtc header and footer + Bottom toolbar from HLS screen +https://100ms.atlassian.net/browse/FLUT-264 + +- New Player Controls (Auto hide/overlay) +https://100ms.atlassian.net/browse/FLUT-266 + +- Header + Description from Layout API in Description Pane +https://100ms.atlassian.net/browse/FLUT-271 + + Room Kit: 1.1.0 Core SDK: 1.10.0 -Android SDK: 2.9.52 +Android SDK: 2.9.53 iOS SDK: 1.8.0 \ No newline at end of file diff --git a/packages/hmssdk_flutter/example/android/Gemfile b/packages/hmssdk_flutter/example/android/Gemfile index bf7ea486e..bbe6e4229 100644 --- a/packages/hmssdk_flutter/example/android/Gemfile +++ b/packages/hmssdk_flutter/example/android/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -gem "fastlane" +gem "fastlane", "~> 2.220.0" gem "activesupport", "= 7.0.8" diff --git a/packages/hmssdk_flutter/example/android/Gemfile.lock b/packages/hmssdk_flutter/example/android/Gemfile.lock index 9097321f9..b491244fd 100644 --- a/packages/hmssdk_flutter/example/android/Gemfile.lock +++ b/packages/hmssdk_flutter/example/android/Gemfile.lock @@ -15,8 +15,8 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.904.0) - aws-sdk-core (3.191.5) + aws-partitions (1.913.0) + aws-sdk-core (3.191.6) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) @@ -73,15 +73,15 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.0) - fastlane (2.218.0) + fastimage (2.3.1) + fastlane (2.220.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -93,6 +93,7 @@ GEM gh_inspector (>= 1.1.2, < 2.0.0) google-apis-androidpublisher_v3 (~> 0.3) google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) google-cloud-storage (~> 1.31) highline (~> 2.0) http-cookie (~> 1.0.5) @@ -101,10 +102,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -113,16 +114,16 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-firebase_app_distribution (0.9.0) google-apis-firebaseappdistribution_v1 (~> 0.3.0) google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.60.0) - google-apis-core (>= 0.14.0, < 2.a) - google-apis-core (0.14.1) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) - googleauth (~> 1.9) + googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) @@ -132,30 +133,28 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-firebaseappdistribution_v1alpha (0.2.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-iamcredentials_v1 (0.20.0) - google-apis-core (>= 0.14.0, < 2.a) - google-apis-playcustomapp_v1 (0.15.0) - google-apis-core (>= 0.14.0, < 2.a) - google-apis-storage_v1 (0.37.0) - google-apis-core (>= 0.14.0, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) google-cloud-core (1.7.0) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.1.1) - faraday (>= 1.0, < 3.a) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.4.0) - google-cloud-storage (1.49.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) - google-apis-core (~> 0.13) - google-apis-iamcredentials_v1 (~> 0.18) - google-apis-storage_v1 (~> 0.33) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) - googleauth (~> 1.9) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.11.0) - faraday (>= 1.0, < 3.a) - google-cloud-env (~> 2.1) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) @@ -167,7 +166,7 @@ GEM i18n (1.14.4) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.7.1) + json (2.7.2) jwt (2.8.1) base64 mini_magick (4.12.0) @@ -181,8 +180,8 @@ GEM optparse (0.4.0) os (1.1.4) plist (3.7.1) - public_suffix (5.0.4) - rake (13.1.0) + public_suffix (5.0.5) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -192,7 +191,7 @@ GEM rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) + security (0.1.5) signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) @@ -231,7 +230,7 @@ PLATFORMS DEPENDENCIES activesupport (= 7.0.8) - fastlane + fastlane (~> 2.220.0) fastlane-plugin-firebase_app_distribution BUNDLED WITH diff --git a/packages/hmssdk_flutter/example/android/app/build.gradle b/packages/hmssdk_flutter/example/android/app/build.gradle index c8eb15279..8e8b72d59 100644 --- a/packages/hmssdk_flutter/example/android/app/build.gradle +++ b/packages/hmssdk_flutter/example/android/app/build.gradle @@ -36,8 +36,8 @@ android { applicationId "live.hms.flutter" minSdkVersion 21 targetSdkVersion 34 - versionCode 463 - versionName "1.5.163" + versionCode 473 + versionName "1.5.173" } signingConfigs { diff --git a/packages/hmssdk_flutter/example/ios/Gemfile b/packages/hmssdk_flutter/example/ios/Gemfile index b288471e6..a24614677 100644 --- a/packages/hmssdk_flutter/example/ios/Gemfile +++ b/packages/hmssdk_flutter/example/ios/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -gem "fastlane" +gem "fastlane", "~> 2.220.0" gem "activesupport", "= 7.0.8" plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') diff --git a/packages/hmssdk_flutter/example/ios/Gemfile.lock b/packages/hmssdk_flutter/example/ios/Gemfile.lock index c311b5449..59ff0e0e1 100644 --- a/packages/hmssdk_flutter/example/ios/Gemfile.lock +++ b/packages/hmssdk_flutter/example/ios/Gemfile.lock @@ -15,8 +15,8 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.904.0) - aws-sdk-core (3.191.5) + aws-partitions (1.913.0) + aws-sdk-core (3.191.6) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) @@ -73,15 +73,15 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.3.0) - fastlane (2.218.0) + fastimage (2.3.1) + fastlane (2.220.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) - colored + colored (~> 1.2) commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) @@ -93,6 +93,7 @@ GEM gh_inspector (>= 1.1.2, < 2.0.0) google-apis-androidpublisher_v3 (~> 0.3) google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) google-cloud-storage (~> 1.31) highline (~> 2.0) http-cookie (~> 1.0.5) @@ -101,10 +102,10 @@ GEM mini_magick (>= 4.9.4, < 5.0.0) multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) - optparse (>= 0.1.1) + optparse (>= 0.1.1, < 1.0.0) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) + security (= 0.1.5) simctl (~> 1.6.3) terminal-notifier (>= 2.0.0, < 3.0.0) terminal-table (~> 3) @@ -113,17 +114,17 @@ GEM word_wrap (~> 1.0.0) xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) fastlane-plugin-firebase_app_distribution (0.9.0) google-apis-firebaseappdistribution_v1 (~> 0.3.0) google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) fastlane-plugin-versioning (0.5.2) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.60.0) - google-apis-core (>= 0.14.0, < 2.a) - google-apis-core (0.14.1) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) - googleauth (~> 1.9) + googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) @@ -133,30 +134,28 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-firebaseappdistribution_v1alpha (0.2.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-iamcredentials_v1 (0.20.0) - google-apis-core (>= 0.14.0, < 2.a) - google-apis-playcustomapp_v1 (0.15.0) - google-apis-core (>= 0.14.0, < 2.a) - google-apis-storage_v1 (0.37.0) - google-apis-core (>= 0.14.0, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) google-cloud-core (1.7.0) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.1.1) - faraday (>= 1.0, < 3.a) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.4.0) - google-cloud-storage (1.49.0) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) - google-apis-core (~> 0.13) - google-apis-iamcredentials_v1 (~> 0.18) - google-apis-storage_v1 (~> 0.33) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) - googleauth (~> 1.9) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.11.0) - faraday (>= 1.0, < 3.a) - google-cloud-env (~> 2.1) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) @@ -168,7 +167,7 @@ GEM i18n (1.14.4) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.7.1) + json (2.7.2) jwt (2.8.1) base64 mini_magick (4.12.0) @@ -182,8 +181,8 @@ GEM optparse (0.4.0) os (1.1.4) plist (3.7.1) - public_suffix (5.0.4) - rake (13.1.0) + public_suffix (5.0.5) + rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -193,7 +192,7 @@ GEM rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) - security (0.1.3) + security (0.1.5) signet (0.19.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) @@ -232,7 +231,7 @@ PLATFORMS DEPENDENCIES activesupport (= 7.0.8) - fastlane + fastlane (~> 2.220.0) fastlane-plugin-firebase_app_distribution fastlane-plugin-versioning diff --git a/packages/hmssdk_flutter/example/ios/Podfile.lock b/packages/hmssdk_flutter/example/ios/Podfile.lock index f0bc617be..13f5497b9 100644 --- a/packages/hmssdk_flutter/example/ios/Podfile.lock +++ b/packages/hmssdk_flutter/example/ios/Podfile.lock @@ -27,15 +27,15 @@ PODS: - Firebase/Performance (= 10.18.0) - firebase_core - Flutter - - FirebaseABTesting (10.23.0): + - FirebaseABTesting (10.24.0): - FirebaseCore (~> 10.0) - FirebaseCore (10.18.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreExtension (10.23.0): + - FirebaseCoreExtension (10.24.0): - FirebaseCore (~> 10.0) - - FirebaseCoreInternal (10.23.0): + - FirebaseCoreInternal (10.24.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - FirebaseCrashlytics (10.18.0): - FirebaseCore (~> 10.5) @@ -47,7 +47,7 @@ PODS: - PromisesObjC (~> 2.1) - FirebaseDynamicLinks (10.18.0): - FirebaseCore (~> 10.0) - - FirebaseInstallations (10.23.0): + - FirebaseInstallations (10.24.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) @@ -62,7 +62,7 @@ PODS: - GoogleUtilities/ISASwizzler (~> 7.8) - GoogleUtilities/MethodSwizzler (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseRemoteConfig (10.23.0): + - FirebaseRemoteConfig (10.24.0): - FirebaseABTesting (~> 10.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) @@ -70,8 +70,8 @@ PODS: - FirebaseSharedSwift (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseRemoteConfigInterop (10.23.0) - - FirebaseSessions (10.23.0): + - FirebaseRemoteConfigInterop (10.24.0) + - FirebaseSessions (10.24.0): - FirebaseCore (~> 10.5) - FirebaseCoreExtension (~> 10.0) - FirebaseInstallations (~> 10.0) @@ -79,7 +79,7 @@ PODS: - GoogleUtilities/Environment (~> 7.10) - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesSwift (~> 2.1) - - FirebaseSharedSwift (10.23.0) + - FirebaseSharedSwift (10.24.0) - Flutter (1.0.0) - flutter_foreground_task (0.0.1): - Flutter @@ -275,18 +275,18 @@ SPEC CHECKSUMS: firebase_crashlytics: 4b91b8ad60ee7c168fe88979f84c9573a729de7a firebase_dynamic_links: b626a11f5eb02033981ae377377c3f297eb4c1b0 firebase_performance: 2183122a3c7a650c80d8c164e9e28f13c4c62fc7 - FirebaseABTesting: aec61ed9a34d85a95e2013a3fdf051426a2419df + FirebaseABTesting: 4431c2c56ac6e56f463b9cab05cc111078639f99 FirebaseCore: 2322423314d92f946219c8791674d2f3345b598f - FirebaseCoreExtension: cb88851781a24e031d1b58e0bd01eb1f46b044b5 - FirebaseCoreInternal: 6a292e6f0bece1243a737e81556e56e5e19282e3 + FirebaseCoreExtension: af5fd85e817ea9d19f9a2659a376cf9cf99f03c0 + FirebaseCoreInternal: bcb5acffd4ea05e12a783ecf835f2210ce3dc6af FirebaseCrashlytics: 86d5bce01f42fa1db265f87ff1d591f04db610ec FirebaseDynamicLinks: c37307441c53838d66a9650dabca9e0459502527 - FirebaseInstallations: 42d6ead4605d6eafb3b6683674e80e18eb6f2c35 + FirebaseInstallations: 8f581fca6478a50705d2bd2abd66d306e0f5736e FirebasePerformance: c406a9198d8aabfbac281b42855f5122fc1bcf69 - FirebaseRemoteConfig: 70ebe9542cf5242d762d1c0b4d53bfc472e0a4ce - FirebaseRemoteConfigInterop: cbc87ffa4932719a7911a08e94510f18f026f5a7 - FirebaseSessions: f06853e30f99fe42aa511014d7ee6c8c319f08a3 - FirebaseSharedSwift: c92645b392db3c41a83a0aa967de16f8bad25568 + FirebaseRemoteConfig: 95dddc50496b37eef199dadce850d5652b534b43 + FirebaseRemoteConfigInterop: 6c349a466490aeace3ce9c091c86be1730711634 + FirebaseSessions: 2651b464e241c93fd44112f995d5ab663c970487 + FirebaseSharedSwift: 76e1529c32101d80e4f1ca2fba7c39d59f0a390a Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a @@ -319,4 +319,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9fb9f6e431a2c6c79942252e94b241ac7972ac90 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj index dd0e12912..c6527dce3 100644 --- a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ EC1052B52858A77D005EAB9E /* SampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1052B42858A77D005EAB9E /* SampleHandler.swift */; }; EC1052B92858A77D005EAB9E /* FlutterBroadcastUploadExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = EC1052B02858A77D005EAB9E /* FlutterBroadcastUploadExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; EC9BCD2B26B1CFDB00D378A0 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = EC9BCD2A26B1CFDB00D378A0 /* GoogleService-Info.plist */; }; + ECAB35012BC53D4000AC25F4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = ECAB35002BC53D4000AC25F4 /* PrivacyInfo.xcprivacy */; }; + ECAB35022BC53D4000AC25F4 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = ECAB35002BC53D4000AC25F4 /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -84,6 +86,7 @@ EC3386D4296EBF6B00F565A8 /* Runner.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; EC638D182858ABE8005D0AF2 /* FlutterBroadcastUploadExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FlutterBroadcastUploadExtension.entitlements; sourceTree = ""; }; EC9BCD2A26B1CFDB00D378A0 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + ECAB35002BC53D4000AC25F4 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; FF549B17CAF9408297610536 /* Pods-FlutterBroadcastUploadExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlutterBroadcastUploadExtension.profile.xcconfig"; path = "Target Support Files/Pods-FlutterBroadcastUploadExtension/Pods-FlutterBroadcastUploadExtension.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -153,6 +156,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + ECAB35002BC53D4000AC25F4 /* PrivacyInfo.xcprivacy */, B44CF4C02A83BCF0003B3D5E /* RunnerRelease.entitlements */, EC9BCD2A26B1CFDB00D378A0 /* GoogleService-Info.plist */, EC3386D4296EBF6B00F565A8 /* Runner.entitlements */, @@ -280,6 +284,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + ECAB35012BC53D4000AC25F4 /* PrivacyInfo.xcprivacy in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, EC9BCD2B26B1CFDB00D378A0 /* GoogleService-Info.plist in Resources */, @@ -292,6 +297,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + ECAB35022BC53D4000AC25F4 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/packages/hmssdk_flutter/example/ios/Runner/AppDelegate.swift b/packages/hmssdk_flutter/example/ios/Runner/AppDelegate.swift index 0e33de35f..3196f6df3 100644 --- a/packages/hmssdk_flutter/example/ios/Runner/AppDelegate.swift +++ b/packages/hmssdk_flutter/example/ios/Runner/AppDelegate.swift @@ -8,12 +8,12 @@ import Flutter didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) - + SwiftFlutterForegroundTaskPlugin.setPluginRegistrantCallback(registerPlugins) if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } - + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/packages/hmssdk_flutter/example/ios/Runner/Info.plist b/packages/hmssdk_flutter/example/ios/Runner/Info.plist index 84d0a9938..3140781a7 100644 --- a/packages/hmssdk_flutter/example/ios/Runner/Info.plist +++ b/packages/hmssdk_flutter/example/ios/Runner/Info.plist @@ -21,7 +21,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.5.163 + 1.5.173 CFBundleSignature ???? CFBundleURLTypes @@ -48,7 +48,7 @@ CFBundleVersion - 463 + 473 ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/packages/hmssdk_flutter/example/ios/Runner/PrivacyInfo.xcprivacy b/packages/hmssdk_flutter/example/ios/Runner/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..d8f064411 --- /dev/null +++ b/packages/hmssdk_flutter/example/ios/Runner/PrivacyInfo.xcprivacy @@ -0,0 +1,59 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyTracking + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + 3B52.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + + diff --git a/packages/hmssdk_flutter/example/lib/foreground_task_handler.dart b/packages/hmssdk_flutter/example/lib/foreground_task_handler.dart index 1459ffc0e..1648758f1 100644 --- a/packages/hmssdk_flutter/example/lib/foreground_task_handler.dart +++ b/packages/hmssdk_flutter/example/lib/foreground_task_handler.dart @@ -1,6 +1,7 @@ import 'dart:isolate'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; +import 'package:hms_room_kit/hms_room_kit.dart'; import 'package:hmssdk_flutter_example/main.dart'; ///[ForegroundTaskHandler] is a class that extends [TaskHandler] @@ -50,29 +51,34 @@ class ForegroundTaskHandler extends TaskHandler { } } -void initForegroundTask() { - FlutterForegroundTask.init( - androidNotificationOptions: AndroidNotificationOptions( - channelId: '100ms_flutter_notification', - channelName: '100ms Flutter Notification', - channelDescription: - 'This notification appears when the foreground service is running.', - channelImportance: NotificationChannelImportance.LOW, - priority: NotificationPriority.LOW, - iconData: NotificationIconData( - resType: ResourceType.mipmap, - resPrefix: ResourcePrefix.ic, - name: "launcher")), - iosNotificationOptions: - const IOSNotificationOptions(showNotification: false), - foregroundTaskOptions: const ForegroundTaskOptions(), - ); +Future initForegroundTask() async { + bool isPermissionsGiven = await Utilities.getPermissions(); + if (isPermissionsGiven) { + FlutterForegroundTask.init( + androidNotificationOptions: AndroidNotificationOptions( + channelId: '100ms_flutter_notification', + channelName: '100ms Flutter Notification', + channelDescription: + 'This notification appears when the foreground service is running.', + channelImportance: NotificationChannelImportance.LOW, + priority: NotificationPriority.LOW, + iconData: NotificationIconData( + resType: ResourceType.mipmap, + resPrefix: ResourcePrefix.ic, + name: "launcher")), + iosNotificationOptions: + const IOSNotificationOptions(showNotification: false), + foregroundTaskOptions: const ForegroundTaskOptions(), + ); - ///[startService] starts the foreground task - FlutterForegroundTask.startService( - notificationTitle: 'Foreground Service is running', - notificationText: 'Tap to return to the app', - callback: startCallback); + ///[startService] starts the foreground task + FlutterForegroundTask.startService( + notificationTitle: 'Foreground Service is running', + notificationText: 'Tap to return to the app', + callback: startCallback); + return true; + } + return false; } ///[stopForegroundTask] stops the foreground task diff --git a/packages/hmssdk_flutter/example/lib/main.dart b/packages/hmssdk_flutter/example/lib/main.dart index e83e67305..a2c64b49f 100644 --- a/packages/hmssdk_flutter/example/lib/main.dart +++ b/packages/hmssdk_flutter/example/lib/main.dart @@ -329,7 +329,7 @@ class _HomePageState extends State { Utilities.saveStringData( key: "meetingLink", value: meetingLinkController.text.trim()); FocusManager.instance.primaryFocus?.unfocus(); - initForegroundTask(); + await initForegroundTask(); Navigator.push( context, MaterialPageRoute( diff --git a/packages/hmssdk_flutter/example/lib/qr_code_screen.dart b/packages/hmssdk_flutter/example/lib/qr_code_screen.dart index d01d5f469..6d4c04bba 100644 --- a/packages/hmssdk_flutter/example/lib/qr_code_screen.dart +++ b/packages/hmssdk_flutter/example/lib/qr_code_screen.dart @@ -75,7 +75,7 @@ class _QRCodeScreenState extends State { Constant.roomCode = rawValue.trim(); } Utilities.saveStringData(key: "meetingLink", value: rawValue.trim()); - initForegroundTask(); + await initForegroundTask(); Navigator.of(context).pushReplacement(MaterialPageRoute( builder: (_) => WithForegroundTask( child: HMSPrebuilt( diff --git a/packages/hmssdk_flutter/example/pubspec.lock b/packages/hmssdk_flutter/example/pubspec.lock index 10a9a3805..9a59fc2e1 100644 --- a/packages/hmssdk_flutter/example/pubspec.lock +++ b/packages/hmssdk_flutter/example/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.2" convert: dependency: transitive description: @@ -342,30 +342,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 - url: "https://pub.dev" - source: hosted - version: "2.0.1" linkify: dependency: transitive description: @@ -386,26 +362,26 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.9.1" mime: dependency: transitive description: @@ -450,10 +426,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" path_parsing: dependency: transitive description: @@ -466,18 +442,18 @@ packages: dependency: transitive description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" path_provider_foundation: dependency: transitive description: @@ -578,10 +554,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" + sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" url: "https://pub.dev" source: hosted - version: "3.7.4" + version: "3.8.0" provider: dependency: transitive description: @@ -610,10 +586,10 @@ packages: dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.3" shared_preferences_android: dependency: transitive description: @@ -687,18 +663,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -719,10 +695,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.0" tuple: dependency: transitive description: @@ -851,14 +827,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.0+2" - vm_service: + web: dependency: transitive description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "0.1.4-beta" win32: dependency: transitive description: @@ -884,5 +860,5 @@ packages: source: hosted version: "6.3.0" sdks: - dart: ">=3.2.0-0 <4.0.0" + dart: ">=3.1.0 <4.0.0" flutter: ">=3.13.0" diff --git a/packages/hmssdk_flutter/ios/Classes/Actions/HMSNoiseCancellationControllerAction.swift b/packages/hmssdk_flutter/ios/Classes/Actions/HMSNoiseCancellationControllerAction.swift index cbe5fab33..c7e708265 100644 --- a/packages/hmssdk_flutter/ios/Classes/Actions/HMSNoiseCancellationControllerAction.swift +++ b/packages/hmssdk_flutter/ios/Classes/Actions/HMSNoiseCancellationControllerAction.swift @@ -9,12 +9,11 @@ import Foundation import HMSSDK import HMSNoiseCancellationModels -class HMSNoiseCancellationController{ +class HMSNoiseCancellationController { static var noiseCancellationController: HMSNoiseCancellationPlugin? - - - static func noiseCancellationActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult){ - switch call.method{ + + static func noiseCancellationActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + switch call.method { case "enable_noise_cancellation": enable(result) case "disable_noise_cancellation": @@ -27,77 +26,75 @@ class HMSNoiseCancellationController{ result(FlutterMethodNotImplemented) } } - + /** * [createPlugin] creates the noiseCancellationPlugin based on the parameter in audio track settings */ - static func createPlugin(){ + static func createPlugin() { noiseCancellationController = { if let pathForNCModel = HMSNoiseCancellationModels.path(for: .smallFullBand) { return HMSNoiseCancellationPlugin(modelPath: pathForNCModel, initialState: .enabled) - } - else { + } else { assertionFailure("Noise cancellation model was not found") } return nil }() } - + /** * [enable] method enables noise cancellation for the user */ - private static func enable(_ result: @escaping FlutterResult){ - if let controller = noiseCancellationController{ + private static func enable(_ result: @escaping FlutterResult) { + if let controller = noiseCancellationController { do { try controller.enable() result(nil) } catch { print("An error occurred: \(error)") } - }else{ + } else { HMSErrorLogger.logError(#function, "Noise cancellation controller is not initialised. Please enable noise cancellation in audio track settings while initialising HMSSDK", "NULL_ERROR") } } - + /** * [disable] method disables noise cancellation for the user */ - private static func disable(_ result: @escaping FlutterResult){ - if let controller = noiseCancellationController{ + private static func disable(_ result: @escaping FlutterResult) { + if let controller = noiseCancellationController { do { try controller.disable() result(nil) } catch { print("An error occurred: \(error)") } - }else{ + } else { HMSErrorLogger.logError(#function, "Noise cancellation controller is not initialised. Please enable noise cancellation in audio track settings while initialising HMSSDK", "NULL_ERROR") } } - + /** * [isEnabled] method returns whether noise cancellation is enabled or not */ - private static func isEnabled(_ result: @escaping FlutterResult){ - if let controller = noiseCancellationController{ + private static func isEnabled(_ result: @escaping FlutterResult) { + if let controller = noiseCancellationController { let isNoiseCancellationEnabled = controller.isEnabled() - result(HMSResultExtension.toDictionary(true,isNoiseCancellationEnabled)) - }else{ + result(HMSResultExtension.toDictionary(true, isNoiseCancellationEnabled)) + } else { HMSErrorLogger.logError(#function, "Noise cancellation controller is not initialised. Please enable noise cancellation in audio track settings while initialising HMSSDK", "NULL_ERROR") } } - + /** * [isAvailable] method returns whether noise cancellation is available in the room */ - private static func isAvailable(_ result: @escaping FlutterResult){ - if let controller = noiseCancellationController{ + private static func isAvailable(_ result: @escaping FlutterResult) { + if let controller = noiseCancellationController { let isNoiseCancellationAvailable = controller.isNoiseCancellationAvailable - result(HMSResultExtension.toDictionary(true,isNoiseCancellationAvailable)) - }else{ + result(HMSResultExtension.toDictionary(true, isNoiseCancellationAvailable)) + } else { HMSErrorLogger.logError(#function, "Noise cancellation controller is not initialised. Please enable noise cancellation in audio track settings while initialising HMSSDK", "NULL_ERROR") } } - - + } diff --git a/packages/hmssdk_flutter/ios/Classes/Actions/HMSPollAction.swift b/packages/hmssdk_flutter/ios/Classes/Actions/HMSPollAction.swift index 18e9201bb..63b28d493 100644 --- a/packages/hmssdk_flutter/ios/Classes/Actions/HMSPollAction.swift +++ b/packages/hmssdk_flutter/ios/Classes/Actions/HMSPollAction.swift @@ -8,10 +8,10 @@ import Foundation import HMSSDK -class HMSPollAction{ - - static func pollActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?, _ polls: [HMSPoll]?){ - switch call.method{ +class HMSPollAction { + + static func pollActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?, _ polls: [HMSPoll]?) { + switch call.method { case "quick_start_poll": quickStartPoll(call, result, hmsSDK) break @@ -28,91 +28,90 @@ class HMSPollAction{ case "fetch_poll_list": fetchPollList(call, result, hmsSDK) case "fetch_poll_questions": - fetchPollQuestions(call,result,hmsSDK) + fetchPollQuestions(call, result, hmsSDK) case "get_poll_results": getPollResults(call, result, hmsSDK) default: result(FlutterMethodNotImplemented) } } - - static private func quickStartPoll(_ call: FlutterMethodCall,_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?){ - + + static private func quickStartPoll(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + let arguments = call.arguments as? [AnyHashable: Any] - - guard let pollBuilderMap = arguments?["poll_builder"] as? [String:Any?] else{ + + guard let pollBuilderMap = arguments?["poll_builder"] as? [String: Any?] else { result(HMSErrorExtension.getError("No pollBuilder found in \(#function)")) return } - - if let hmsSDK{ + + if let hmsSDK { guard let pollBuilder = HMSPollBuilderExtension.toHMSPollBuilder(pollBuilderMap, hmsSDK) - else{ + else { HMSErrorLogger.returnArgumentsError("pollBuilder parsing failed") return } - - hmsSDK.interactivityCenter.quickStartPoll(with: (pollBuilder), completion: {_ , error in + + hmsSDK.interactivityCenter.quickStartPoll(with: (pollBuilder), completion: {_, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) } else { result(nil) }}) - } else{ + } else { result(HMSErrorExtension.getError("hmsSDK is nil in \(#function)")) return } - + } - - - static private func addSingleChoicePollResponse(_ call: FlutterMethodCall,_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?,_ currentPolls: [HMSPoll]?){ - + + static private func addSingleChoicePollResponse(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?, _ currentPolls: [HMSPoll]?) { + let arguments = call.arguments as? [AnyHashable: Any] - + guard let pollId = arguments?["poll_id"] as? String, let index = arguments?["question_index"] as? Int, - let answer = arguments?["answer"] as? [String:Any?] + let answer = arguments?["answer"] as? [String: Any?] else { HMSErrorLogger.returnArgumentsError("Invalid arguments") return } - + let timeTakenToAnswer = arguments?["time_taken_to_answer"] as? Int - + if let optionIndex = answer["index"] as? Int { - - if let poll = currentPolls?.first(where: {$0.pollID == pollId}){ - - if let question = poll.questions?[index]{ - - if let optionSelected = question.options?[optionIndex - 1]{ + + if let poll = currentPolls?.first(where: {$0.pollID == pollId}) { + + if let question = poll.questions?[index] { + + if let optionSelected = question.options?[optionIndex - 1] { let response = HMSPollResponseBuilder(poll: poll) - response.addResponse(for: question, options: [optionSelected],duration: timeTakenToAnswer) - hmsSDK?.interactivityCenter.add(response: response){ pollResult, error in - - if let error = error{ + response.addResponse(for: question, options: [optionSelected], duration: timeTakenToAnswer) + hmsSDK?.interactivityCenter.add(response: response) { pollResult, error in + + if let error = error { result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) - }else{ + } else { var pollResults = [[String: Any?]]() - - pollResult?.forEach{ + + pollResult?.forEach { pollResults.append(HMSPollAnswerResponseExtension.toDictionary(pollAnswerResponse: $0)) } - var map = [String:Any?]() + var map = [String: Any?]() map["result"] = pollResults result(HMSResultExtension.toDictionary(true, map)) } } - }else{ + } else { HMSErrorLogger.returnArgumentsError("No option found at given index") } - - }else{ + + } else { HMSErrorLogger.returnArgumentsError("No question found at given index") } - - }else{ + + } else { HMSErrorLogger.returnArgumentsError("No poll with given pollId found") return } @@ -121,251 +120,250 @@ class HMSPollAction{ return } } - - - static private func addMultiChoicePollResponse(_ call: FlutterMethodCall,_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?,_ currentPolls: [HMSPoll]?){ - + + static private func addMultiChoicePollResponse(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?, _ currentPolls: [HMSPoll]?) { + let arguments = call.arguments as? [AnyHashable: Any] - + guard let pollId = arguments?["poll_id"] as? String, let index = arguments?["question_index"] as? Int, - let answer = arguments?["answer"] as? [[String:Any?]] + let answer = arguments?["answer"] as? [[String: Any?]] else { HMSErrorLogger.returnArgumentsError("Invalid arguments") return } - + let timeTakenToAnswer = arguments?["time_taken_to_answer"] as? Int - - if let poll = currentPolls?.first(where: {$0.pollID == pollId}){ - - if let question = poll.questions?[index]{ + + if let poll = currentPolls?.first(where: {$0.pollID == pollId}) { + + if let question = poll.questions?[index] { var selectedOptions = [HMSPollQuestionOption]() - answer.forEach{ - if let optionIndex = $0["index"] as? Int{ - if let option = question.options?[optionIndex - 1] as? HMSPollQuestionOption{ + answer.forEach { + if let optionIndex = $0["index"] as? Int { + if let option = question.options?[optionIndex - 1] as? HMSPollQuestionOption { selectedOptions.append(option) - }else{ + } else { HMSErrorLogger.returnArgumentsError("Invalid option index") return } - }else{ + } else { HMSErrorLogger.returnArgumentsError("Invalid index") return } } let response = HMSPollResponseBuilder(poll: poll) - response.addResponse(for: question, options: selectedOptions,duration: timeTakenToAnswer) - hmsSDK?.interactivityCenter.add(response: response){ pollResult, error in - - if let error = error{ + response.addResponse(for: question, options: selectedOptions, duration: timeTakenToAnswer) + hmsSDK?.interactivityCenter.add(response: response) { pollResult, error in + + if let error = error { result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) - }else{ + } else { var pollResults = [[String: Any?]]() - - pollResult?.forEach{ + + pollResult?.forEach { pollResults.append(HMSPollAnswerResponseExtension.toDictionary(pollAnswerResponse: $0)) } - var map = [String:Any?]() + var map = [String: Any?]() map["result"] = pollResults result(HMSResultExtension.toDictionary(true, map)) } } - }else{ + } else { HMSErrorLogger.returnArgumentsError("No question found at given index") return } - }else{ + } else { HMSErrorLogger.returnArgumentsError("No poll with given pollId found") return } } - - static private func stopPoll(_ call: FlutterMethodCall,_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?,_ currentPolls: [HMSPoll]?){ - + + static private func stopPoll(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?, _ currentPolls: [HMSPoll]?) { + let arguments = call.arguments as? [AnyHashable: Any] - + guard let pollId = arguments?["poll_id"] as? String - else{ + else { HMSErrorLogger.returnArgumentsError("pollId can't be null") return } - + if let poll = currentPolls?.first(where: { $0.pollID == pollId - }){ - hmsSDK?.interactivityCenter.stop(poll: poll){ + }) { + hmsSDK?.interactivityCenter.stop(poll: poll) { _, error in - if let error = error{ + if let error = error { result(HMSErrorExtension.toDictionary(error)) - }else{ + } else { result(nil) } } } } - - static private func fetchLeaderboard(_ call: FlutterMethodCall,_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?,_ currentPolls: [HMSPoll]?){ - + + static private func fetchLeaderboard(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?, _ currentPolls: [HMSPoll]?) { + let arguments = call.arguments as? [AnyHashable: Any] - + guard let pollId = arguments?["poll_id"] as? String, let count = arguments?["count"] as? Int, let startIndex = arguments?["start_index"] as? Int, let includeCurrentPeer = arguments?["include_current_peer"] as? Bool - else{ + else { HMSErrorLogger.returnArgumentsError("Either pollId, count, startIndex or includeCurrentPeer is null") return } - - if let poll = hmsSDK?.interactivityCenter.polls.first(where: {$0.pollID == pollId}){ - hmsSDK?.interactivityCenter.fetchLeaderboard(for: poll, offset: startIndex, count: count,includeCurrentPeer: includeCurrentPeer){ + + if let poll = hmsSDK?.interactivityCenter.polls.first(where: {$0.pollID == pollId}) { + hmsSDK?.interactivityCenter.fetchLeaderboard(for: poll, offset: startIndex, count: count, includeCurrentPeer: includeCurrentPeer) { pollLeaderboardResponse, error in - - if let error = error{ + + if let error = error { result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) - }else{ + } else { result(HMSResultExtension.toDictionary(true, HMSPollLeaderboardResponseExtension.toDictionary(pollLeaderboardResponse: pollLeaderboardResponse))) } } - }else{ + } else { HMSErrorLogger.returnArgumentsError("No poll with given pollId found") return } } - - static private func fetchPollList(_ call: FlutterMethodCall,_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?){ - + + static private func fetchPollList(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + let arguments = call.arguments as? [AnyHashable: Any] - + guard let state = arguments?["poll_state"] as? String - else{ + else { HMSErrorLogger.returnArgumentsError("state is null") return } - - if let state = getPollState(pollState: state){ - hmsSDK?.interactivityCenter.fetchPollList(state: state){ + + if let state = getPollState(pollState: state) { + hmsSDK?.interactivityCenter.fetchPollList(state: state) { pollList, error in - - if let error = error{ + + if let error = error { result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) - }else{ + } else { var map = [[String: Any?]]() - - pollList?.forEach{ + + pollList?.forEach { map.append(HMSPollExtension.toDictionary(poll: $0)) } result(HMSResultExtension.toDictionary(true, map)) } } - }else{ - HMSErrorLogger.logError(#function, "No poll state matched","ARGUMENTS_ERROR") + } else { + HMSErrorLogger.logError(#function, "No poll state matched", "ARGUMENTS_ERROR") result(nil) } } - - private static func fetchPollQuestions(_ call: FlutterMethodCall,_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?){ + + private static func fetchPollQuestions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { let arguments = call.arguments as? [AnyHashable: Any] - + guard let state = arguments?["poll_state"] as? String, let pollId = arguments?["poll_id"] as? String - else{ + else { HMSErrorLogger.returnArgumentsError("pollId or state is null") return } - - if let state = getPollState(pollState: state){ - hmsSDK?.interactivityCenter.fetchPollList(state: state){ + + if let state = getPollState(pollState: state) { + hmsSDK?.interactivityCenter.fetchPollList(state: state) { pollList, error in - - if let error = error{ + + if let error = error { result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) - }else{ - + } else { + if let poll = pollList?.first(where: { $0.pollID == pollId - }){ - hmsSDK?.interactivityCenter.fetchPollQuestions(poll: poll){ + }) { + hmsSDK?.interactivityCenter.fetchPollQuestions(poll: poll) { updatedPoll, error in - - if let error = error{ + + if let error = error { result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) - }else{ + } else { var map = [[String: Any?]]() - updatedPoll?.questions?.forEach{ + updatedPoll?.questions?.forEach { map.append(HMSPollQuestionExtension.toDictionary(question: $0)) } result(HMSResultExtension.toDictionary(true, map)) - + } } - }else{ - HMSErrorLogger.logError(#function,"No poll with given pollId found","NULL_ERROR") + } else { + HMSErrorLogger.logError(#function, "No poll with given pollId found", "NULL_ERROR") } - + } } - }else{ - HMSErrorLogger.logError(#function, "No poll state matched","ARGUMENTS_ERROR") + } else { + HMSErrorLogger.logError(#function, "No poll state matched", "ARGUMENTS_ERROR") result(nil) } } - - private static func getPollResults(_ call: FlutterMethodCall,_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?){ + + private static func getPollResults(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { let arguments = call.arguments as? [AnyHashable: Any] - + guard let state = arguments?["poll_state"] as? String, let pollId = arguments?["poll_id"] as? String - else{ + else { HMSErrorLogger.returnArgumentsError("pollId or state is null") return } - - if let state = getPollState(pollState: state){ - hmsSDK?.interactivityCenter.fetchPollList(state: state){ + + if let state = getPollState(pollState: state) { + hmsSDK?.interactivityCenter.fetchPollList(state: state) { pollList, error in - - if let error = error{ + + if let error = error { result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) - }else{ - + } else { + if let poll = pollList?.first(where: { $0.pollID == pollId - }){ - hmsSDK?.interactivityCenter.fetchPollResult(for: poll){ + }) { + hmsSDK?.interactivityCenter.fetchPollResult(for: poll) { updatedPoll, error in - - if let error = error{ + + if let error = error { result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) - }else{ - if let updatedPoll{ + } else { + if let updatedPoll { result(HMSResultExtension.toDictionary(true, HMSPollExtension.toDictionary(poll: updatedPoll))) - }else{ - HMSErrorLogger.logError(#function,"poll is NULL","NULL_ERROR") + } else { + HMSErrorLogger.logError(#function, "poll is NULL", "NULL_ERROR") result(nil) return } - + } } - }else{ - HMSErrorLogger.logError(#function,"No poll with given pollId found","NULL_ERROR") + } else { + HMSErrorLogger.logError(#function, "No poll with given pollId found", "NULL_ERROR") } - + } } - }else{ - HMSErrorLogger.logError(#function, "No poll state matched","ARGUMENTS_ERROR") + } else { + HMSErrorLogger.logError(#function, "No poll state matched", "ARGUMENTS_ERROR") result(nil) } } - + private static func getPollState(pollState: String?) -> HMSPollState? { guard let state = pollState else { return nil } - + switch state { case "created": return .created @@ -377,5 +375,5 @@ class HMSPollAction{ return nil } } - + } diff --git a/packages/hmssdk_flutter/ios/Classes/Actions/HMSSessionStoreAction.swift b/packages/hmssdk_flutter/ios/Classes/Actions/HMSSessionStoreAction.swift index f400ede98..60a3c08a3 100644 --- a/packages/hmssdk_flutter/ios/Classes/Actions/HMSSessionStoreAction.swift +++ b/packages/hmssdk_flutter/ios/Classes/Actions/HMSSessionStoreAction.swift @@ -47,40 +47,37 @@ class HMSSessionStoreAction { return } - do{ + do { let isValid = try JSONSerialization.isValidJSONObject(value) - - if(isValid){ + + if isValid { let jsonData = try JSONSerialization.data(withJSONObject: value, options: []) - if let jsonString = String(data: jsonData, encoding: .utf8){ + if let jsonString = String(data: jsonData, encoding: .utf8) { result(HMSResultExtension.toDictionary(true, jsonString)) - }else{ + } else { HMSErrorLogger.logError(#function, "Session metadata type is not compatible, Please use String? type while setting metadata", "Type Incompatibility Error") result(HMSResultExtension.toDictionary(true, nil)) } - } - else{ - if let intValue = value as? Int{ + } else { + if let intValue = value as? Int { let stringValue = String(intValue) result(HMSResultExtension.toDictionary(true, stringValue)) - } else if let doubleValue = value as? Double{ + } else if let doubleValue = value as? Double { let stringValue = String(doubleValue) result(HMSResultExtension.toDictionary(true, stringValue)) - } else if let stringValue = value as? String{ + } else if let stringValue = value as? String { result(HMSResultExtension.toDictionary(true, stringValue)) - } else if let boolValue = value as? Bool{ + } else if let boolValue = value as? Bool { let stringValue = String(boolValue) result(HMSResultExtension.toDictionary(true, stringValue)) - } else if (value == nil || value is NSNull){ + } else if value == nil || value is NSNull { result(HMSResultExtension.toDictionary(true, nil)) - } - else{ + } else { HMSErrorLogger.logError(#function, "Session metadata type is not compatible, Please use compatible type while setting metadata", "Type Incompatibility Error") result(HMSResultExtension.toDictionary(true, nil)) } } - } - catch{ + } catch { HMSErrorLogger.logError(#function, "Session metadata type is not compatible, JSON parsing failed", "Type Incompatibility Error") result(HMSResultExtension.toDictionary(true, nil)) } diff --git a/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift b/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift index 2d27c990b..a8726877a 100644 --- a/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift +++ b/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSPlayerAction.swift @@ -48,6 +48,15 @@ class HMSHLSPlayerAction { case "remove_hls_stats_listener": removeHLSStatsListener(result) + + case "are_closed_captions_supported": + areClosedCaptionsSupported(result) + + case "enable_closed_captions": + enableClosedCaptions(result) + + case "disable_closed_captions": + disableClosedCaptions(result) default: result(FlutterMethodNotImplemented) @@ -194,4 +203,18 @@ class HMSHLSPlayerAction { NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "remove_hls_stats_listener"]) result(nil) } + + static private func areClosedCaptionsSupported(_ result: @escaping FlutterResult){ + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "are_closed_captions_supported", "result": result]) + } + + static private func enableClosedCaptions(_ result: @escaping FlutterResult){ + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "enable_closed_captions"]) + result(nil) + } + + static private func disableClosedCaptions(_ result: @escaping FlutterResult){ + NotificationCenter.default.post(name: NSNotification.Name(HLS_PLAYER_METHOD), object: nil, userInfo: [METHOD_CALL: "disable_closed_captions"]) + result(nil) + } } diff --git a/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSStreamViewController.swift b/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSStreamViewController.swift index 7385df052..c56d2053b 100644 --- a/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSStreamViewController.swift +++ b/packages/hmssdk_flutter/ios/Classes/HLSPlayer/HMSHLSStreamViewController.swift @@ -91,5 +91,16 @@ class HMSHLSStreamViewController: HMSHLSPlayerDelegate { } else { playerView?.videoGravity = .resizeAspectFill } + + let data = [ + "event_name": "on_video_size_changed", + "data": ["width": videoSize.width, "height": videoSize.height] as [String: Any?] + ] as [String: Any?] + + guard let hmssdkFlutterPlugin else { + HMSErrorLogger.logError(#function, "hmssdkFlutterPlugin is null", "NULL_ERROR") + return + } + hmssdkFlutterPlugin.hlsPlayerSink?(data) } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/HMSPeerExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/HMSPeerExtension.swift index 4f308ce58..c4cc17ef9 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/HMSPeerExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/HMSPeerExtension.swift @@ -73,7 +73,7 @@ class HMSPeerExtension { return "defaultUpdate" } } - + private static func getPeerType(_ peerType: HMSPeerType) -> String { switch peerType { case .sip: diff --git a/packages/hmssdk_flutter/ios/Classes/Models/HMSPermissionExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/HMSPermissionExtension.swift index bd4a755db..a70e374b3 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/HMSPermissionExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/HMSPermissionExtension.swift @@ -22,7 +22,7 @@ class HMSPermissionExtension { "un_mute": permission.unmute ?? false, "poll_read": permission.pollRead ?? false, "poll_write": permission.pollWrite ?? false - + ] } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/HMSRoomExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/HMSRoomExtension.swift index cad828eb2..c99622137 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/HMSRoomExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/HMSRoomExtension.swift @@ -17,7 +17,7 @@ class HMSRoomExtension { if let roomID = room.roomID { dict["id"] = roomID } - + if let name = room.name { dict["name"] = name } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/HMSStreamingStateExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/HMSStreamingStateExtension.swift index e1c1895a1..20b5e9ab4 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/HMSStreamingStateExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/HMSStreamingStateExtension.swift @@ -20,7 +20,7 @@ class HMSStreamingStateExtension { if let error = rtmp.error { dict.merge(HMSErrorExtension.toDictionary(error)) { (_, new) in new } } - + dict["state"] = rtmp.state.displayString().uppercased() return dict } @@ -36,7 +36,7 @@ class HMSStreamingStateExtension { if let error = server.error { dict.merge(HMSErrorExtension.toDictionary(error)) { (_, new) in new } } - + dict["state"] = server.state.displayString().uppercased() return dict } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/HMSTrackSettingsExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/HMSTrackSettingsExtension.swift index 1e64c273f..4063cc75a 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/HMSTrackSettingsExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/HMSTrackSettingsExtension.swift @@ -67,7 +67,7 @@ class HMSTrackSettingsExtension { if let muteState = audioSettingsDict["track_initial_state"] as? String { initialMuteState = getinitialMuteState(from: muteState) } - + if #available(iOS 13.0, *), !audioMixerSourceMap.isEmpty { do { let audioMixerSource = try HMSAudioMixerSource(nodes: audioMixerSourceMap.values.map {$0}) @@ -99,15 +99,15 @@ class HMSTrackSettingsExtension { if let mode = getAudioMode(from: audioSettingsDict["audio_mode"] as? String) { builder.audioMode = mode } - + /* Here we set the noise cancellation controller based on the parameter passed in audio track settings */ - if let enableNoiseCancellation = audioSettingsDict["enable_noise_cancellation"] as? Bool{ - if(enableNoiseCancellation){ - - ///We create the noise cancellation plugin + if let enableNoiseCancellation = audioSettingsDict["enable_noise_cancellation"] as? Bool { + if enableNoiseCancellation { + + /// We create the noise cancellation plugin HMSNoiseCancellationController.createPlugin() builder.noiseCancellationPlugin = HMSNoiseCancellationController.noiseCancellationController } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerExtension.swift index d326e3d45..14a79932b 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerExtension.swift @@ -8,11 +8,11 @@ import Foundation import HMSSDK -class HMSPollAnswerExtension{ - - static func toDictionary(answer: HMSPollQuestionResponse) -> [String:Any]{ - - var map = [String:Any]() +class HMSPollAnswerExtension { + + static func toDictionary(answer: HMSPollQuestionResponse) -> [String: Any] { + + var map = [String: Any]() map["answer"] = answer.text map["duration"] = answer.duration @@ -22,7 +22,7 @@ class HMSPollAnswerExtension{ map["selected_options"] = answer.options map["skipped"] = answer.skipped map["update"] = answer.update - + return map } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerResponseExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerResponseExtension.swift index 7c90a6fb8..0e9305ffe 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerResponseExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerResponseExtension.swift @@ -8,21 +8,20 @@ import Foundation import HMSSDK -class HMSPollAnswerResponseExtension{ - - static func toDictionary(pollAnswerResponse: HMSPollQuestionResponseResult) -> [String: Any?]{ - - var map = [String:Any?]() - +class HMSPollAnswerResponseExtension { + + static func toDictionary(pollAnswerResponse: HMSPollQuestionResponseResult) -> [String: Any?] { + + var map = [String: Any?]() + map["question_index"] = pollAnswerResponse.question - if let error = pollAnswerResponse.error{ + if let error = pollAnswerResponse.error { map["error"] = HMSErrorExtension.toDictionary(error) } map["correct"] = pollAnswerResponse.correct - + return map - + } - - + } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollBuilderExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollBuilderExtension.swift index a74e4e2b8..2030a6e42 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollBuilderExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollBuilderExtension.swift @@ -8,20 +8,20 @@ import Foundation import HMSSDK -class HMSPollBuilderExtension{ - - static func toHMSPollBuilder(_ pollBuilderMap:[String:Any?]?, _ hmssdk:HMSSDK) -> HMSPollBuilder?{ - - guard let pollBuilderMap = pollBuilderMap else{ +class HMSPollBuilderExtension { + + static func toHMSPollBuilder(_ pollBuilderMap: [String: Any?]?, _ hmssdk: HMSSDK) -> HMSPollBuilder? { + + guard let pollBuilderMap = pollBuilderMap else { return nil } - + let pollBuilder = HMSPollBuilder() - - if let anonymous = pollBuilderMap["anonymous"] as? Bool{ + + if let anonymous = pollBuilderMap["anonymous"] as? Bool { pollBuilder.withAnonymous(anonymous) } - + if let duration = pollBuilderMap["duration"] as? Int { pollBuilder.withDuration(duration) } @@ -29,7 +29,7 @@ class HMSPollBuilderExtension{ if let mode = pollBuilderMap["mode"] as? String { pollBuilder.withUserTrackingMode(getPollUserTrackingModeFromString(mode)) } - + if let pollCategory = pollBuilderMap["poll_category"] as? String { pollBuilder.withCategory(getPollCategoryFromString(pollCategory)) } @@ -37,7 +37,7 @@ class HMSPollBuilderExtension{ if let pollId = pollBuilderMap["poll_id"] as? String { pollBuilder.withPollID(pollId) } - + let availableRoles = hmssdk.roles if let rolesThatCanViewResponses = pollBuilderMap["roles_that_can_view_responses"] as? [String] { let roles = rolesThatCanViewResponses.compactMap { roleName in @@ -56,22 +56,22 @@ class HMSPollBuilderExtension{ if let title = pollBuilderMap["title"] as? String { pollBuilder.withTitle(title) } - - if let questions = pollBuilderMap["questions"] as? [[String: Any?]]{ - - questions.forEach{ - if let questionBuilder = getPollQuestionBuilder($0){ + + if let questions = pollBuilderMap["questions"] as? [[String: Any?]] { + + questions.forEach { + if let questionBuilder = getPollQuestionBuilder($0) { pollBuilder.addQuestion(with: questionBuilder) } } - + } return pollBuilder } - - private static func getPollCategoryFromString(_ pollCategory:String) -> HMSPollCategory{ - - switch pollCategory{ + + private static func getPollCategoryFromString(_ pollCategory: String) -> HMSPollCategory { + + switch pollCategory { case "poll": return .poll case "quiz": @@ -80,7 +80,7 @@ class HMSPollBuilderExtension{ return .poll } } - + private static func getPollUserTrackingModeFromString(_ pollUserTrackingMode: String) -> HMSPollUserTrackingMode { switch pollUserTrackingMode { case "user_id": @@ -108,7 +108,7 @@ class HMSPollBuilderExtension{ return .singleChoice } } - + private static func getPollQuestionBuilder(_ pollQuestion: [String: Any?]?) -> HMSPollQuestionBuilder? { guard let pollQuestion = pollQuestion else { return nil @@ -116,7 +116,7 @@ class HMSPollBuilderExtension{ let pollQuestionBuilder = HMSPollQuestionBuilder() - if let typeString = pollQuestion["type"] as? String{ + if let typeString = pollQuestion["type"] as? String { let type = getPollQuestionTypeFromString(typeString) pollQuestionBuilder.withType(type) } else { @@ -124,7 +124,6 @@ class HMSPollBuilderExtension{ return nil } - if let canSkip = pollQuestion["can_skip"] as? Bool { pollQuestionBuilder.withCanBeSkipped(canSkip) } @@ -161,18 +160,17 @@ class HMSPollBuilderExtension{ if let options = pollQuestion["options"] as? [[String: Any?]] { options.forEach { option in - if let key = option["text"] as? String, let value = option["is_correct"] as? Bool{ + if let key = option["text"] as? String, let value = option["is_correct"] as? Bool { pollQuestionBuilder.addQuizOption(with: key, isCorrect: value) } } } - if let canChangeResponse = pollQuestion["can_change_response"] as? Bool{ + if let canChangeResponse = pollQuestion["can_change_response"] as? Bool { pollQuestionBuilder.withCanChangeResponse(canChangeResponse: canChangeResponse) } return pollQuestionBuilder } - } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollExtension.swift index fd52f54c7..44fe94738 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollExtension.swift @@ -15,24 +15,24 @@ class HMSPollExtension { map["anonymous"] = poll.anonymous map["category"] = getPollCategory(pollCategory: poll.category) - - if let createdBy = poll.createdBy{ + + if let createdBy = poll.createdBy { map["created_by"] = HMSPeerExtension.toDictionary(createdBy) } - + map["duration"] = poll.duration - - if let mode = poll.mode{ + + if let mode = poll.mode { map["mode"] = getPollUserTrackingMode(mode: mode) } - + map["poll_id"] = poll.pollID map["question_count"] = poll.questionCount let questions = poll.questions?.map { HMSPollQuestionExtension.toDictionary(question: $0) } map["questions"] = questions - if let result = poll.result{ + if let result = poll.result { map["result"] = HMSPollResultExtension.toDictionary(pollResult: result) } @@ -42,20 +42,20 @@ class HMSPollExtension { let rolesThatCanVote = poll.rolesThatCanVote.map { HMSRoleExtension.toDictionary($0) } map["roles_that_can_vote"] = rolesThatCanVote - if let startedAt = poll.startedAt{ + if let startedAt = poll.startedAt { map["started_at"] = Int(startedAt.timeIntervalSince1970 * 1000) } - - if let startedBy = poll.startedBy{ + + if let startedBy = poll.startedBy { map["started_by"] = HMSPeerExtension.toDictionary(startedBy) } - + map["state"] = getPollState(state: poll.state) - - if let stoppedAt = poll.stoppedAt{ + + if let stoppedAt = poll.stoppedAt { map["stopped_at"] = Int(stoppedAt.timeIntervalSince1970 * 1000) } - + map["title"] = poll.title return map @@ -111,4 +111,3 @@ class HMSPollExtension { } } } - diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardEntryExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardEntryExtension.swift index 7db87b77c..f1f8d628d 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardEntryExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardEntryExtension.swift @@ -8,19 +8,19 @@ import Foundation import HMSSDK -class HMSPollLeaderboardEntryExtension{ - - static func toDictionary(hmsPollLeaderboardEntry: HMSPollLeaderboardEntry) -> [String:Any?] { +class HMSPollLeaderboardEntryExtension { + + static func toDictionary(hmsPollLeaderboardEntry: HMSPollLeaderboardEntry) -> [String: Any?] { + + var map = [String: Any?]() - var map = [String:Any?]() - map["correct_responses"] = hmsPollLeaderboardEntry.correctResponses map["duration"] = hmsPollLeaderboardEntry.duration map["position"] = hmsPollLeaderboardEntry.position map["score"] = hmsPollLeaderboardEntry.score map["total_responses"] = hmsPollLeaderboardEntry.totalResponses map["peer"] = HMSPollResponsePeerInfoExtension.toDictionary(hmsPollResponsePeerInfo: hmsPollLeaderboardEntry.peer) - + return map } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardResponseExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardResponseExtension.swift index 54b03a23c..80b07ac1f 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardResponseExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardResponseExtension.swift @@ -8,15 +8,15 @@ import Foundation import HMSSDK -class HMSPollLeaderboardResponseExtension{ - - static func toDictionary(pollLeaderboardResponse: HMSPollLeaderboardResponse?) -> [String:Any?]?{ - - if let pollLeaderboardResponse{ - +class HMSPollLeaderboardResponseExtension { + + static func toDictionary(pollLeaderboardResponse: HMSPollLeaderboardResponse?) -> [String: Any?]? { + + if let pollLeaderboardResponse { + var map = [String: Any?]() var entryMap = [[String: Any?]]() - + for entry in pollLeaderboardResponse.entries { entryMap.append(HMSPollLeaderboardEntryExtension.toDictionary(hmsPollLeaderboardEntry: entry)) } @@ -24,11 +24,11 @@ class HMSPollLeaderboardResponseExtension{ map["entries"] = entryMap map["has_next"] = pollLeaderboardResponse.hasNext map["summary"] = HMSPollLeaderboardSummaryExtension.toDictionary(hmsPollLeaderboardSummary: pollLeaderboardResponse.summary) - + return map - }else{ + } else { return nil } } - + } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardSummaryExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardSummaryExtension.swift index 515acb436..2163ab2c1 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardSummaryExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollLeaderboardSummaryExtension.swift @@ -8,26 +8,26 @@ import Foundation import HMSSDK -class HMSPollLeaderboardSummaryExtension{ - - static func toDictionary(hmsPollLeaderboardSummary: HMSPollLeaderboardSummary?) -> [String:Any?]?{ - - if let hmsPollLeaderboardSummary{ - - var map = [String:Any?]() - +class HMSPollLeaderboardSummaryExtension { + + static func toDictionary(hmsPollLeaderboardSummary: HMSPollLeaderboardSummary?) -> [String: Any?]? { + + if let hmsPollLeaderboardSummary { + + var map = [String: Any?]() + map["average_score"] = hmsPollLeaderboardSummary.averageScore map["average_time"] = hmsPollLeaderboardSummary.averageTime map["responded_peers_count"] = hmsPollLeaderboardSummary.respondedPeersCount map["responded_correctly_peers_count"] = hmsPollLeaderboardSummary.respondedCorrectlyPeersCount map["total_peers_count"] = hmsPollLeaderboardSummary.totalPeersCount - + return map - - }else{ + + } else { return nil } - + } - + } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionAnswerExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionAnswerExtension.swift index 870c71e0a..578ea6518 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionAnswerExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionAnswerExtension.swift @@ -8,16 +8,16 @@ import Foundation import HMSSDK -class HMSPollQuestionAnswerExtension{ - - static func toDictionary(answer: HMSPollQuestionAnswer) -> [String:Any]{ - - var map = [String:Any]() - +class HMSPollQuestionAnswerExtension { + + static func toDictionary(answer: HMSPollQuestionAnswer) -> [String: Any] { + + var map = [String: Any]() + map["hidden"] = answer.hidden map["option"] = answer.option map["options"] = answer.options - + return map } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionExtension.swift index 79129307c..508e1f77a 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionExtension.swift @@ -8,36 +8,36 @@ import Foundation import HMSSDK -class HMSPollQuestionExtension{ - - static func toDictionary(question: HMSPollQuestion) -> [String:Any?]{ +class HMSPollQuestionExtension { + + static func toDictionary(question: HMSPollQuestion) -> [String: Any?] { var map = [String: Any?]() - + map["question_id"] = question.index map["can_skip"] = question.skippable - - if let answer = question.answer{ + + if let answer = question.answer { map["correct_answer"] = HMSPollQuestionAnswerExtension.toDictionary(answer: answer) } - + map["duration"] = question.duration - - let myResponses = question.myResponses.map{ (HMSPollAnswerExtension.toDictionary(answer:$0)) } + + let myResponses = question.myResponses.map { (HMSPollAnswerExtension.toDictionary(answer: $0)) } map["my_responses"] = myResponses - - let options = question.options?.map{ (HMSPollQuestionOptionExtension.toDictionary(pollOptions: $0)) } + + let options = question.options?.map { (HMSPollQuestionOptionExtension.toDictionary(pollOptions: $0)) } map["options"] = options - + map["text"] = question.text map["type"] = getPollQuestionType(pollQuestionType: question.type) map["voted"] = question.voted map["weight"] = question.weight map["can_change_response"] = question.once return map - + } - - static func getPollQuestionType(pollQuestionType: HMSPollQuestionType) -> String?{ + + static func getPollQuestionType(pollQuestionType: HMSPollQuestionType) -> String? { switch pollQuestionType { case .longAnswer: return "long_answer" diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionOptionExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionOptionExtension.swift index 5bead12e2..0260a4be2 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionOptionExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionOptionExtension.swift @@ -8,17 +8,17 @@ import Foundation import HMSSDK -class HMSPollQuestionOptionExtension{ - +class HMSPollQuestionOptionExtension { + static func toDictionary(pollOptions: HMSPollQuestionOption) -> [String: Any] { - + var map = [String: Any]() - + map["index"] = pollOptions.index map["text"] = pollOptions.text map["vote_count"] = pollOptions.voteCount map["weight"] = pollOptions.weight - + return map } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionResultExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionResultExtension.swift index 2889979d3..30b2817fe 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionResultExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionResultExtension.swift @@ -8,18 +8,18 @@ import Foundation import HMSSDK -class HMSPollQuestionResultExtension{ - - static func toDictionary(pollQuestionResult: HMSPollQuestionResult)-> [String:Any]{ - - var map = [String:Any]() - +class HMSPollQuestionResultExtension { + + static func toDictionary(pollQuestionResult: HMSPollQuestionResult) -> [String: Any] { + + var map = [String: Any]() + map["attempted_times"] = pollQuestionResult.totalVotes map["correct"] = pollQuestionResult.correctVotes map["options"] = pollQuestionResult.optionVoteCounts map["question_type"] = pollQuestionResult.type map["skipped"] = pollQuestionResult.skippedVotes - + return map } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResponsePeerInfoExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResponsePeerInfoExtension.swift index 46dce2d01..80327ff7e 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResponsePeerInfoExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResponsePeerInfoExtension.swift @@ -8,24 +8,24 @@ import Foundation import HMSSDK -class HMSPollResponsePeerInfoExtension{ - - static func toDictionary(hmsPollResponsePeerInfo: HMSPollResponsePeerInfo?) -> [String:Any?]?{ - - if let hmsPollResponsePeerInfo{ - - var map = [String:Any?]() - +class HMSPollResponsePeerInfoExtension { + + static func toDictionary(hmsPollResponsePeerInfo: HMSPollResponsePeerInfo?) -> [String: Any?]? { + + if let hmsPollResponsePeerInfo { + + var map = [String: Any?]() + map["hash"] = hmsPollResponsePeerInfo.userHash map["peer_id"] = hmsPollResponsePeerInfo.peerID map["user_id"] = hmsPollResponsePeerInfo.customerUserID map["username"] = hmsPollResponsePeerInfo.userName - + return map - }else{ + } else { return nil } - + } - + } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResultExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResultExtension.swift index 1b0d78136..070711223 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResultExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResultExtension.swift @@ -8,19 +8,19 @@ import Foundation import HMSSDK -class HMSPollResultExtension{ - - static func toDictionary(pollResult: HMSPollResult) -> [String:Any]{ - - var map = [String:Any]() - - var questions = pollResult.questions.map{( HMSPollQuestionResultExtension.toDictionary(pollQuestionResult: $0))} +class HMSPollResultExtension { + + static func toDictionary(pollResult: HMSPollResult) -> [String: Any] { + + var map = [String: Any]() + + var questions = pollResult.questions.map {( HMSPollQuestionResultExtension.toDictionary(pollQuestionResult: $0))} map["questions"] = questions - + map["total_distinct_users"] = pollResult.maxUserCount map["total_responses"] = pollResult.totalResponse map["voting_users"] = pollResult.userCount - + return map } } diff --git a/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift b/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift index 76c22e92d..5912dde91 100644 --- a/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift +++ b/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift @@ -17,7 +17,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene var sessionEventChannel: FlutterEventChannel? var hlsPlayerChannel: FlutterEventChannel? var pollsEventChannel: FlutterEventChannel? - + var eventSink: FlutterEventSink? var previewSink: FlutterEventSink? var logsSink: FlutterEventSink? @@ -56,8 +56,8 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let rtcChannel = FlutterEventChannel(name: "rtc_event_channel", binaryMessenger: registrar.messenger()) let sessionChannel = FlutterEventChannel(name: "session_event_channel", binaryMessenger: registrar.messenger()) let hlsChannel = FlutterEventChannel(name: "hls_player_channel", binaryMessenger: registrar.messenger()) - let pollsChannel = FlutterEventChannel(name: "polls_event_channel",binaryMessenger: registrar.messenger()) - + let pollsChannel = FlutterEventChannel(name: "polls_event_channel", binaryMessenger: registrar.messenger()) + let instance = SwiftHmssdkFlutterPlugin(channel: channel, meetingEventChannel: eventChannel, previewEventChannel: previewChannel, @@ -176,7 +176,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene if pollsEventChannel != nil { pollsEventChannel!.setStreamHandler(nil) pollsEventSink = nil - } else{ + } else { print(#function, "pollsEventChannel not found") } } @@ -300,7 +300,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene // MARK: - HLS Player - case "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener": + case "start_hls_player", "stop_hls_player", "pause_hls_player", "resume_hls_player", "seek_to_live_position", "seek_forward", "seek_backward", "set_hls_player_volume", "add_hls_stats_listener", "remove_hls_stats_listener", "are_closed_captions_supported", "enable_closed_captions", "disable_closed_captions": HMSHLSPlayerAction.hlsPlayerAction(call, result) case "toggle_always_screen_on": @@ -314,14 +314,14 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene HMSPeerListIteratorAction.peerListIteratorAction(call, result, hmsSDK) // MARK: - Polls - + case "add_poll_update_listener", "remove_poll_update_listener", "quick_start_poll", "add_single_choice_poll_response", "add_multi_choice_poll_response", "stop_poll", "fetch_leaderboard", "fetch_poll_list", "fetch_poll_questions", "get_poll_results": pollsAction(call, result) - + // MARK: - Noise Cancellation case "enable_noise_cancellation", "disable_noise_cancellation", "is_noise_cancellation_enabled", "is_noise_cancellation_available": HMSNoiseCancellationController.noiseCancellationActions(call, result) - + default: result(FlutterMethodNotImplemented) } @@ -426,30 +426,29 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene result(FlutterMethodNotImplemented) } } - + var currentPolls = [HMSPoll]() private func pollsAction(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { switch call.method { case "add_poll_update_listener": - let listener : HMSInteractivityCenter.HMSPollListener = { [weak self] hmsPoll, hmsPollUpdateType in - + let listener: HMSInteractivityCenter.HMSPollListener = { [weak self] hmsPoll, hmsPollUpdateType in + guard let self = self else { return } - + let map = ["event_name": "on_poll_update", "data": [ "poll": HMSPollExtension.toDictionary(poll: hmsPoll), "poll_update_type": HMSPollExtension.getPollUpdateType(updateType: hmsPollUpdateType) ] - ] as [String : Any] - if(hmsPollUpdateType == .started){ + ] as [String: Any] + if hmsPollUpdateType == .started { currentPolls.append(hmsPoll) - }else if(hmsPollUpdateType == .stopped){ + } else if hmsPollUpdateType == .stopped { currentPolls.removeAll(where: { $0.pollID == hmsPoll.pollID }) - } - else if(hmsPollUpdateType == .resultsUpdated){ - if let index = currentPolls.firstIndex(where: {$0.pollID == hmsPoll.pollID}){ + } else if hmsPollUpdateType == .resultsUpdated { + if let index = currentPolls.firstIndex(where: {$0.pollID == hmsPoll.pollID}) { currentPolls[index] = hmsPoll } } @@ -606,38 +605,35 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene var dict: [String: Any] = ["key": key] - do{ + do { let isValid = try JSONSerialization.isValidJSONObject(value) - if(isValid){ + if isValid { let jsonData = try JSONSerialization.data(withJSONObject: value, options: []) - if let jsonString = String(data: jsonData, encoding: .utf8){ + if let jsonString = String(data: jsonData, encoding: .utf8) { dict["value"] = jsonString - }else{ + } else { HMSErrorLogger.logError(#function, "Session metadata type is not compatible, Please use String? type while setting metadata", "Type Incompatibility Error") dict["value"] = nil } - } - else{ - if let intValue = value as? Int{ + } else { + if let intValue = value as? Int { let stringValue = String(intValue) dict["value"] = stringValue - } else if let doubleValue = value as? Double{ + } else if let doubleValue = value as? Double { let doubleValue = String(doubleValue) dict["value"] = doubleValue - } else if let stringValue = value as? String{ + } else if let stringValue = value as? String { dict["value"] = stringValue - } else if let boolValue = value as? Bool{ + } else if let boolValue = value as? Bool { dict["value"] = String(boolValue) - } else if (value == nil || value is NSNull) { + } else if value == nil || value is NSNull { dict["value"] = nil - } - else{ + } else { HMSErrorLogger.logError(#function, "Session metadata type is not compatible, Please use compatible type while setting metadata", "Type Incompatibility Error") dict["value"] = nil } } - } - catch{ + } catch { HMSErrorLogger.logError(#function, "Session metadata type is not compatible, JSON parsing failed", "Type Incompatibility Error") dict["value"] = nil } @@ -800,7 +796,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene if let settings = trackSettings { sdk.trackSettings = settings } - + if setLogger { sdk.logger = self } @@ -1295,7 +1291,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene hlsStreamUrl = room.hlsStreamingState.variants.first?.url?.absoluteString } } - + /** `willTerminateNotification` is not fired in all cases. When app is in background & then app is force closed then this notification is not fired resulting in `leave` method not being invoked. */ diff --git a/packages/hmssdk_flutter/ios/Classes/Views/HMSHLSPlayerView.swift b/packages/hmssdk_flutter/ios/Classes/Views/HMSHLSPlayerView.swift index 8dc41577f..a54b0600d 100644 --- a/packages/hmssdk_flutter/ios/Classes/Views/HMSHLSPlayerView.swift +++ b/packages/hmssdk_flutter/ios/Classes/Views/HMSHLSPlayerView.swift @@ -161,6 +161,40 @@ class HMSHLSPlayerView: NSObject, FlutterPlatformView { case "remove_hls_stats_listener": removeHLSStatsListener() + case "are_closed_captions_supported": + let result = notification.userInfo?["result"] as? FlutterResult + + var isSubtitleToggleShown = false + guard let playerItem = hlsPlayer?._nativePlayer.currentItem else { + isSubtitleToggleShown = false + return + } + + guard let availableSubtitleTracks = playerItem.asset.mediaSelectionGroup(forMediaCharacteristic: .legible) else { + isSubtitleToggleShown = false + return + } + + guard let firstOption = availableSubtitleTracks.options.first else { + isSubtitleToggleShown = false + return + } + + if firstOption.mediaType == .subtitle { + isSubtitleToggleShown = true + } + else { + isSubtitleToggleShown = false + } + + result?(isSubtitleToggleShown) + + case "enable_closed_captions": + break + + case "disable_closed_captions": + break + default: return } diff --git a/packages/hmssdk_flutter/lib/assets/sdk-versions.json b/packages/hmssdk_flutter/lib/assets/sdk-versions.json index 2bae0357f..281e35a6f 100644 --- a/packages/hmssdk_flutter/lib/assets/sdk-versions.json +++ b/packages/hmssdk_flutter/lib/assets/sdk-versions.json @@ -4,5 +4,5 @@ "iOSBroadcastExtension": "0.0.9", "iOSHLSPlayerSDK": "0.0.2", "iOSNoiseCancellationModels": "1.0.0", - "android": "2.9.52" + "android": "2.9.53" } diff --git a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart index 987022f01..b1280cb63 100644 --- a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart +++ b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart @@ -172,6 +172,8 @@ enum PlatformMethod { setSessionMetadataForKey, addKeyChangeListener, removeKeyChangeListener, + + ///HLS Player methods start, stop, pause, @@ -182,6 +184,10 @@ enum PlatformMethod { setHLSPlayerVolume, addHLSStatsListener, removeHLSStatsListener, + areClosedCaptionsSupported, + enableClosedCaptions, + disableClosedCaptions, + switchAudioOutputUsingiOSUI, sendHLSTimedMetadata, toggleAlwaysScreenOn, @@ -472,6 +478,8 @@ extension PlatformMethodValues on PlatformMethod { return "add_key_change_listener"; case PlatformMethod.removeKeyChangeListener: return "remove_key_change_listener"; + + ///HLS Player methods case PlatformMethod.start: return "start_hls_player"; case PlatformMethod.stop: @@ -492,6 +500,13 @@ extension PlatformMethodValues on PlatformMethod { return "add_hls_stats_listener"; case PlatformMethod.removeHLSStatsListener: return "remove_hls_stats_listener"; + case PlatformMethod.areClosedCaptionsSupported: + return "are_closed_captions_supported"; + case PlatformMethod.enableClosedCaptions: + return "enable_closed_captions"; + case PlatformMethod.disableClosedCaptions: + return "disable_closed_captions"; + case PlatformMethod.switchAudioOutputUsingiOSUI: return "switch_audio_output_using_ios_ui"; case PlatformMethod.sendHLSTimedMetadata: @@ -808,6 +823,8 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.addKeyChangeListener; case "remove_key_change_listener": return PlatformMethod.removeKeyChangeListener; + + ///HLS Player methods case "start_hls_player": return PlatformMethod.start; case "stop_hls_player": @@ -828,6 +845,13 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.addHLSStatsListener; case "remove_hls_stats_listener": return PlatformMethod.removeHLSStatsListener; + case "are_closed_captions_supported": + return PlatformMethod.areClosedCaptionsSupported; + case "enable_closed_captions": + return PlatformMethod.enableClosedCaptions; + case "disable_closed_captions": + return PlatformMethod.disableClosedCaptions; + case "switch_audio_output_using_ios_ui": return PlatformMethod.switchAudioOutputUsingiOSUI; case "send_hls_timed_metadata": diff --git a/packages/hmssdk_flutter/lib/src/enum/hms_hls_playback_event_method.dart b/packages/hmssdk_flutter/lib/src/enum/hms_hls_playback_event_method.dart index 073cc64c5..72428d86d 100644 --- a/packages/hmssdk_flutter/lib/src/enum/hms_hls_playback_event_method.dart +++ b/packages/hmssdk_flutter/lib/src/enum/hms_hls_playback_event_method.dart @@ -5,6 +5,7 @@ enum HMSHLSPlaybackEventMethod { onCue, onHLSError, onHLSEventUpdate, + onVideoSizeChanged, unknown } @@ -21,6 +22,8 @@ extension HMSHLSPlaybackEventMethodValues on HMSHLSPlaybackEventMethod { return HMSHLSPlaybackEventMethod.onHLSError; case "on_hls_event_update": return HMSHLSPlaybackEventMethod.onHLSEventUpdate; + case "on_video_size_changed": + return HMSHLSPlaybackEventMethod.onVideoSizeChanged; default: return HMSHLSPlaybackEventMethod.unknown; } diff --git a/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_playback_event_listener.dart b/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_playback_event_listener.dart index 016df7894..856a2b696 100644 --- a/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_playback_event_listener.dart +++ b/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_playback_event_listener.dart @@ -1,3 +1,6 @@ +//Dart imports +import 'dart:ui'; + //Project imports import 'package:hmssdk_flutter/hmssdk_flutter.dart'; @@ -30,4 +33,9 @@ abstract class HMSHLSPlaybackEventsListener { /// /// - Parameter: playerStats: A [HMSHLSPlayerStats] object containing info about HLS Player stats void onHLSEventUpdate({required HMSHLSPlayerStats playerStats}) {} + + /// Callback to get the stream height/width changes + /// + /// - Parameter: size: A [Size] object containing the new height and width of the stream + void onVideoSizeChanged({required Size size}) {} } diff --git a/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_player_controller.dart b/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_player_controller.dart index f7fd6378c..46a92cc09 100644 --- a/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_player_controller.dart +++ b/packages/hmssdk_flutter/lib/src/model/hls_player/hms_hls_player_controller.dart @@ -100,6 +100,27 @@ class HMSHLSPlayerController { arguments: arguments); } + ///[areClosedCaptionsSupported] checks whether closed captions are supported in the current HLS playback. + /// This can be enabled/disabled from 100ms dashboard. + /// + /// If closed captions are supported, you can enable/disable them using [enableClosedCaptions] and [disableClosedCaptions] respectively. + /// If this returns null then we set it to false. + static Future areClosedCaptionsSupported() async { + bool? result = await PlatformService.invokeMethod( + PlatformMethod.areClosedCaptionsSupported); + return result ?? false; + } + + ///[enableClosedCaptions] enables closed captions in the current HLS playback. + static Future enableClosedCaptions() async { + await PlatformService.invokeMethod(PlatformMethod.enableClosedCaptions); + } + + ///[disableClosedCaptions] disables closed captions in the current HLS playback. + static Future disableClosedCaptions() async { + await PlatformService.invokeMethod(PlatformMethod.disableClosedCaptions); + } + /// Adds an HLS stats listener to receive HLS playback statistics. /// Refer [add HLS Stats listener](https://www.100ms.live/docs/flutter/v2/how-to-guides/record-and-live-stream/hls-player#how-to-know-the-stats-related-to-hls-playback) static Future addHLSStatsListener() async { diff --git a/packages/hmssdk_flutter/lib/src/service/platform_service.dart b/packages/hmssdk_flutter/lib/src/service/platform_service.dart index 7b9d1d1fe..3e02fd860 100644 --- a/packages/hmssdk_flutter/lib/src/service/platform_service.dart +++ b/packages/hmssdk_flutter/lib/src/service/platform_service.dart @@ -772,6 +772,13 @@ abstract class PlatformService { hlsPlaybackEventListener.forEach((e) => e.onHLSEventUpdate( playerStats: HMSHLSPlayerStats.fromMap(arguments))); break; + case HMSHLSPlaybackEventMethod.onVideoSizeChanged: + if (arguments["width"] != null && arguments["height"] != null) { + hlsPlaybackEventListener.forEach((e) => e.onVideoSizeChanged( + size: Size(arguments["width"].toDouble(), + arguments["height"].toDouble()))); + } + break; case HMSHLSPlaybackEventMethod.unknown: break; } diff --git a/packages/hmssdk_flutter/lib/src/ui/meeting/hms_hls_player.dart b/packages/hmssdk_flutter/lib/src/ui/meeting/hms_hls_player.dart index 0f7631c1a..36657fdc3 100644 --- a/packages/hmssdk_flutter/lib/src/ui/meeting/hms_hls_player.dart +++ b/packages/hmssdk_flutter/lib/src/ui/meeting/hms_hls_player.dart @@ -2,8 +2,12 @@ import 'dart:io' show Platform; // Flutter imports: +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart' show StandardMessageCodec; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart' + show AndroidViewController, PlatformViewsService, StandardMessageCodec; ///100ms HMSHLSPlayer /// @@ -31,7 +35,7 @@ class HMSHLSPlayer extends StatelessWidget { final bool? showPlayerControls; - HMSHLSPlayer( + const HMSHLSPlayer( {Key? key, this.hlsUrl, this.isHLSStatsRequired, this.showPlayerControls}) : super(key: key); @@ -65,17 +69,33 @@ class _PlatformView extends StatelessWidget { Widget build(BuildContext context) { ///AndroidView for android it uses surfaceRenderer provided internally by webrtc. if (Platform.isAndroid) { - return AndroidView( - key: key, + return PlatformViewLink( viewType: 'HMSHLSPlayer', - onPlatformViewCreated: onPlatformViewCreated, - creationParamsCodec: StandardMessageCodec(), - creationParams: { - 'hls_url': hlsUrl, - 'is_hls_stats_required': isHLSStatsRequired, - 'show_player_controls': showPlayerControls + surfaceFactory: (context, controller) { + return AndroidViewSurface( + controller: controller as AndroidViewController, + gestureRecognizers: const >{}, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, + onCreatePlatformView: (params) { + return PlatformViewsService.initSurfaceAndroidView( + id: params.id, + viewType: 'HMSHLSPlayer', + layoutDirection: TextDirection.ltr, + creationParams: { + 'hls_url': hlsUrl, + 'is_hls_stats_required': isHLSStatsRequired, + 'show_player_controls': showPlayerControls + }, + creationParamsCodec: const StandardMessageCodec(), + onFocus: () { + params.onFocusChanged(true); + }, + ) + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..create(); }, - gestureRecognizers: {}, ); } else if (Platform.isIOS) { ///UIKitView for ios it uses VideoView provided by 100ms ios_sdk internally. diff --git a/packages/hmssdk_flutter/pubspec.lock b/packages/hmssdk_flutter/pubspec.lock index bfac3fa40..2932c9c3b 100644 --- a/packages/hmssdk_flutter/pubspec.lock +++ b/packages/hmssdk_flutter/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.17.2" fake_async: dependency: transitive description: @@ -59,30 +59,6 @@ packages: description: flutter source: sdk version: "0.0.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 - url: "https://pub.dev" - source: hosted - version: "2.0.1" lints: dependency: "direct dev" description: @@ -95,34 +71,34 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.9.1" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.3" sky_engine: dependency: transitive description: flutter @@ -140,18 +116,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -172,10 +148,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.6.0" vector_math: dependency: transitive description: @@ -184,14 +160,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - vm_service: + web: dependency: transitive description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "0.1.4-beta" sdks: - dart: ">=3.2.0-0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=2.10.0" diff --git a/sample apps/flutter-meet/README.md b/sample apps/flutter-meet/README.md index 08ab7d3d1..667478c9a 100644 --- a/sample apps/flutter-meet/README.md +++ b/sample apps/flutter-meet/README.md @@ -25,4 +25,3 @@ Google Meet clone made in Flutter using 100ms. ## Demo https://user-images.githubusercontent.com/53579386/153129731-a5123b2a-c782-4c06-8d49-a6a431d8fa6d.mp4 - diff --git a/sample apps/flutter-meet/analysis_options.yaml b/sample apps/flutter-meet/analysis_options.yaml index 61b6c4de1..fd16f9219 100644 --- a/sample apps/flutter-meet/analysis_options.yaml +++ b/sample apps/flutter-meet/analysis_options.yaml @@ -24,6 +24,5 @@ linter: 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/sample apps/flutter-meet/android/app/src/main/kotlin/com/aditya/google_meet/MainActivity.kt b/sample apps/flutter-meet/android/app/src/main/kotlin/com/aditya/google_meet/MainActivity.kt index 585878b3c..298d6d89e 100644 --- a/sample apps/flutter-meet/android/app/src/main/kotlin/com/aditya/google_meet/MainActivity.kt +++ b/sample apps/flutter-meet/android/app/src/main/kotlin/com/aditya/google_meet/MainActivity.kt @@ -2,5 +2,4 @@ package com.aditya.google_meet import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/sample apps/flutter-meet/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/sample apps/flutter-meet/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..64f764c137889381d815ef988f859422208c2ac9 100644 GIT binary patch delta 433 zcmZ3$@``zaO8vS3pAc6D28NKMKQy;}V+^0o=+^~d14*|7-=TtGvw&QPxJ21fkg~9u zw(A}PMO^lS)X22&k#5^1-MmezZi8gSGV%PGVi^-a%ED(ch5^wG#!w)d&KNv}F=#Sl z&?LsdiOd1r%>EW#jD9_ge%(M@nSI+BecG8k8yG#C7~SicoXeOT3K;G38SV0ztkW4R zQW%UP84ZFNwY{B}3WZN%RI0z>>EaktaqI0FbFo7XJgg2y=MomNs?Ct`Ire?OME@&s zt%&H`A1=rw2%TD%vi0oKuNU3rYrlYqeYQ8bruTk)ay!N}^XtQjUk<9&8hFb4JYpBF z=Mt2&d#GtOSA$>eo5c(tIiEKNTaITW@vGUm30E6DG;Cs@3{#%Bao(K6TR+V3zIx+w z>{Ma1W%q6^p47T>|NP#QhfFMf-D`Pr`NN*M^-3>IcCpVsn&WioWNmEo-1mZ<>gF}~ z_&ys>EGRmBJkv{{d{z83mL|>1DgwSQ+x+%h{atO&?OP$gkyFqY7%>cYeY$Kep*R+Vo@rCV@iHfs)A>3VtQ&&YGO)d;mK4R1_p+80X`wF zK$_97i!pq*=C*GkM}I&_#;{os5w`>1fr3E97&a3qB2l&!qQG|DV+Ny028$F%ZEr?{ zU`D$ari?l(!3| zY$x7Us(XFs{_1`|Ng@fB$>D)U0V>{QITXs2l z`mDY8^V5?F&U}_&;{BYEM*Y@;Pl}sY)m`RnHSn9!Qe46LnniWbGM;lL$HT*D_FKRD zHOw4d%fj-%E@Nw0kdoE=PUc2IP^D>8f`(=o%LL`4-YS1reOYhI(5s@%Vx71105GN) NJYD@<);T3K0RTlK%8md4 diff --git a/sample apps/flutter-meet/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/sample apps/flutter-meet/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..5aa38115aabb027991ced508d4f766579abf49e8 100644 GIT binary patch delta 337 zcmdnR+`~LUrM@e`C&ZP3fg$AR561A>KvHwtHwX#g!bN}#AYu>ihlmFr0jUJCS)7Z2 z;y|w9&Tl{wr8!rXW?fR8dRo5sfL!M;paRD5S&U&oG?OuOI%CK*#*nFO!JUkL-K@S1 zjJ_gWtWJ51PNfVs+02H4%(~ta-yQxj@s3LU98VX=kcwMx&+Zgzau8@q4ESjG!Qo?q zh^tM*_j=o`NpE{*@^UQL_W1q!Z&mvgdNxdbzvuJ0?XQ=6=R7=JCzhCap+KQRq)e{$ zP2hp^{>Rz+5B;!s=JX*(;(Y6aO6LkamGjNdonHHH4E=GD?_j0vhbO(hg_X;tXFvLN z@?A>cojRkRuN{9WY5u>jK5>`FDkcBUFGm%Vvjcs;UFlw=yqZy6+n8^Gu1q^H;21nz L{an^LB{Ts5Q`w;s delta 383 zcmeBS-o-pYrCzecHKHUqKdq!Zu_%?nF(p4KRlzeiF+DXXH8G{K@MNkD0|P@>fKP}k zkY)^@9dh)C=C*G@63B)y;37ZGp{-5j2|t=z$t zr?2e1x|<{M`=1Yfvn$S7zyFh8TkauU@pWH(C1>qj#=iApHvX*F>$zT5Z{Kw7wA0T; z2OjON7k#nuLZxEVlhB3Vi%#w5R{pQ^jOYHGzyeuwiTP#h-KPy@t!Iin*SXo`SZH-* zHSdS+n*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_Adf()LnelF{r5}E)+ CtE}Sy diff --git a/sample apps/flutter-meet/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample apps/flutter-meet/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d4391482be68e9e4a07fab769b5de337d16eb1..c7142758ebaa430bb196a87eaf499d463fe4aea7 100644 GIT binary patch delta 586 zcmcb}I+b;Tiqy9NpAc6D28NKMKQy;}gOH5jvl%@aCMJm1yC3`x5rs&IjA1hvL#H!_ zOa*FZ44%vw1Vj@V1Ns^L`xyPY7=1e#eLEO^fT*3>yNTJWkq$MX&w2(%p%VE&nE4xjLcUU^sJvB{hrLdYg_F7GmFX(zgoS1-!Ek1e(t{)JIb9ae+d8E z{c_&9uYw9cGzw}&BJ8+y)P$Rqm%`v z^DN)+_iIh*l(3kFppJj`a_jp2zpqZs=2UpCiE)8*#* z(+hYeY$Kep*R+Vo@rCV@iHfs)A>3VtQ&&YGO)d;mK4R1_p+20X`wF zKsw~;56x}g7(E&o!)F5-KrRpg*+7yvvLC_)in|~D4wVX?0+bcYnFdkJpzF_|7sQ|+ z!e9`>V3)_J>Bd;E<;7?g$7r6!XpzEboylll!01rK=vcz&TFvNI%jjOm=w8q0QP1ev z%;?p^=+n;V)4}KqM4gO&U5x&Hi~;?OK@%B+CNl;D(GCPn7taAy_>```o*)=rbracldM=SRl82AakF&uHtE(K z(rvqByAA-I?6M!^kHQUW7fe=WRIT^-ba4#HxcBzvb-%+70<8}nG!}l(s>)~dNosn$ zG+8L?!QXhc(=OAl-|I}tX5+MYUiGzp)@xmn4fl8Lo9SP~{9Z zg_&vdd80=)V?B$fi(^Q|t+yAB2Sq!Iv?N+K+MZ)nFEC@{p54R1C?w=rFtu$? zF`w0*|6HqnF}};0VJY(W{q9$*{S1~F7QdXeuwAa|1yRJj=x00sAF#BaCw}g8`pt1`PQW_`FUY<~V{*ebutFLE}QFA7=jUb5|?b=ayX(?yrRzN^`o zwN~un$*AWUAuCr2%C6s*zU(wZrhx2Pm3FnoMN5OWYPqzZUdz1GDTGO5&x?eWmQy9! zPiP6e)&G|zf5wj?<#6{mv3*;(b0SS4vvio4U58$JhS5cj?+?`yyVgFdZ)Crsq$0FO#pIbocM!pOOWKIv-Ttz3^qN z<;}^uQ6d2>=V!Man;&c0eW>d}F<2L}g=v_90};s_IQb=n-jBQ-_Hsino`zyV+DjSaRpGK7BopYLHfsipY+ zUE`QB1@;PS5u}2rXw#-|7lv%9b zE#CyHv+7i(guNO5)z6>q&ZN{$H@Q6GzEmgc$oNOSl=6tG z|B_v6AriZa{bX0&zIu^4-MZ!tDz5hTzV@GWN%B6qwd9$Eiu$jA*Zx=7h;*v0QYgIm zZ1!*F#s29^7k|7CUHwl{F;KC7b9qKdO;*`L7O&J$XQo9?|BlVw$FAIX^5)MYQNr#{ zA)oHNdM7Qz=W{g3OMLT+H!oj(v#${hFWcCqHP7R=ZQ@gr#Vg+IG*8r=e#52p&s}v- z*ADM1|8i6pPw`w7wy53z$})u}4y6ttvFNjk)ANqGd0!k-%0)b ze?FZ?Bj(p*2!T}8KcA1O7AWyk9mS0QQ}<`gdf2t*!|eYrzIcL7(y99|wq5@Ej=wvO zzdi1nXP;N}yKw65{@r)#VxE0_YzCCt`FF?px9ttL`I)!NbLH8~r2W$@R$udB@qrWe z>VK0DaoKbA{Hf0rx$(fgaiTnH*pI-3C+>}+bs`c!0~1>1S>5VHA|8?^)hy4N2GsT7 zIL}{=2j3Pq-0o+}v*+4@hf}Z7aKG)W$LzPiLzaKg`NzJ`c5dVwky%Gs=kUs|pyd~Ibbox>vDtp&7n^HM=llJB{WI*}`rz{6Zv~ICvd$>${`j##x1>_# zDSLrf&5rxGq>CrlF{B^-U43xg!6O-F4I3WZz58q(_s56#8EaVc-^WKLCZ!CRNe&urp*RkumH$pb^GsZqY%v}GYZ<)g~O^NmMwwHLn{dd?s>XyIq z0pDLSig;h_~#nBI_sNE7RR?EAKny*ru^w%9ro_VT_Hav z&#(z!^&__}t7UQ6)wHI*v{=OA5Yvxv7Ne?DO&?#iW}kn!RaJM!-i?_TzW&|9IypV{ z&k@$6PxsB=wZLr2A!U7yP{(;I9ayyrWJFguu=Xux5?yhiFk}IfXvVGoJL5kuIw5e& SUxbMP2s~Z=T-G@yGywpd;J04@ delta 904 zcmcc3wU~Q?vND5YiEBhjaDG}zd16s2gJVj5QmTSyZen_BP-E_<==W?FSnfd-VWA(i9vNCQi1D?~by|n+mTDwT3?#IJ+d7_AVA{1u+=1$LC zH=!;5_rlG~5Axgp`B3~Z`~IRw*Y~;|UtiuoDgL>V?*3oCS#?)efBBPDuK6oe@A8+^ z_m{nzzSr-%e);@O@oo;c-{&oOR~~%fyS4S4KgMEx6F*psTKp1MssG*DCn>TBPF9cKrz-R*P3W*{(g|(`vDbXXo`Md|548@nU|I!rO4o4_EAWIvmuG zf3VShv%^XK=m$IN7dc$i51+vPzoKzd{4xdoeEZ>S{dFHapC5DJ;{3=1m+b=&T(yro zaDBesgE!l`I(~3JQ~CO>T8(%AJLw-^i}D1YH_g|-_4`!UPqB!9j+^(iWFPE*{&w%j z7J(SsQk-ao4Odi$9YhQm$z{n5J?yD5nt*!~) z75`)W-(OwBpvLF+w?P9`ZsT?{lcb~pSS#J;p#dCCQq|| tZW0n%z`&~Ez{nNQzyxK^Wa3%8wf^&>69N(Y8P+lYfv2mV%Q~loCIHXN(%Jw3 diff --git a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fab2..e882ab988 100644 --- a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,122 @@ { - "images" : [ + "images": [ { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "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": "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@1x.png", + "scale": "1x" }, { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "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": "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@2x.png", + "scale": "2x" }, { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" + "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@2x.png", + "scale": "2x" }, { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" + "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@1x.png", + "scale": "1x" }, { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "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@1x.png", + "scale": "1x" }, { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "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@1x.png", + "scale": "1x" }, { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "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@1x.png", + "scale": "1x" }, { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" + "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": "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" + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "Icon-App-1024x1024@1x.png", + "scale": "1x" } ], - "info" : { - "version" : 1, - "author" : "xcode" + "info": { + "version": 1, + "author": "xcode" } } diff --git a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4725e9b0ddb1deab583e5b5102493aa332..6b3f08fb311f4e5f2443ec0bf861717c6e9f2d8a 100644 GIT binary patch delta 20 bcmdlI+7dEBd1K)XO-=?+S3j3^P61e delta 195 zcmZn(*%CTI*@uB8-O<;Pfnj4m_n$;o1_lO&WRD45bDP z46hOx7_4S6Fo+k-*%fF5lweEpc6VX;4}uH!E}y(n@s?(`YKdz^NlIc#s#S7PDv)9@ zGBC8%H89gPG72%Yure{TGBwpUFt9Q(sB+xA6le%SLvDUbW?Cg~4cs!^l0Xd%p00i_ I>zopr0BnCX`2YX_ diff --git a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf03016f6c994b70f38d1b7346e5831b531f..b8e7dda87eadae57961813ae6936ad859838dd18 100644 GIT binary patch delta 407 zcmV;I0cifT1ik~1F@JbTL_t(|+RfCxN&`U<0N{H7!P-V_v@>Ys8(8=kjW)(QMJfvo z<^^oDvXPW3Y9lC^B8m_tCZ>>>9}%sLKg5W{1QN{c-R#V`n&N~MM{vm^&#wt^VQnzWcPc>c=Z)jPl{5VZ(e*neD7(8Rgm(U@T0rGfbm z!Zt#-u&KCUw*vhs1E1>_W&=9GEYLaD4BBHax`nj_rYk6Co3ZbV?mU38nFq0VHHE!JcrPT}L{5k|xF9Nw!^j=?Z}>QpdiC;NEbJ`8yo%^GE+J9o~_IZFoiamQVQXP7%L zzTbT3F@uf+9x&7cvWlpC0VNgUMUIx2NKPukR9q)sZsv$S9)Cp06~d4Jx-*KV`ZQ(@ zVyUPupum=XhInNG#Z_k-X|hK{B}~9IfiWx}LD5QY6Vm)p0NrWymdkrHPN5Vgwd>5> z4HI1=@PA+e^rq~CEj|n2X`??)0mUI*D{KBnjv{V=y5X9|X$tI-0sMRvg*+}rL4e>U zx{S%1a#K|fd4C3+?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso91ljw83cCo9B!2=)L_t(|+RfPCZxmG&2k>c2V+@Ip#utrG;x6K^h&~rniTr43 zp|T}nq9#Uz$^#}m7>w}`Fu_=SD8(3~LZU)UTiue-0`0acD51tkc_7hP=`XjG-PxJh zne*k`+*wY#Ff4Fq(w^j-oz2eXe&*bnotxlSVpAi_!M#EGIoKJ zR>%r)Fg~1NiD8&iuP+E)J`5`eb;U9;JQHAac5!l?U*-awnDTM)Dn%cVSlSfuD2L2| zg1cHK>^bM6d4I^k##0V9RC3b6=F<+E21|JLXBU4L33O{9ae5VS61vD72PoW&jLGC2 zyd**As0O}s)5JbiRXn$})2(D(C0p$n_3itb+PwSLJ zn7&D7IscUiGY;K7kiyKmgx$ZnjJQ2qS-;@G#WLE4_5C-KQ_=hH7**fZ@5kqZ@PG_} z>WJSw+N%Sy)#h^Q#|hx-aac`jOv`=^gbQZZS`-4 zJF2(Ohku>(DaMQBe^(>9lboC5KK#SOy1^3Ev~D%WMFXRedb;2E)fJo`5AUCMasSaG zo*A&w&X|rIBO`GM{!q`la`pLC!FhP#SP_r^Xk%5MjVFInNIlK1r%TZ|b71ttkWU)D zZ(QTnFfP?^BhAssBHBh=WX3#wK?p7xpdjU(+kZdd;lA&Rj8QmAy03s#*25#m@;LQR z0jfOFB?lCw&ZPqJouz~ae~=EQdJ9NrU99depyBl|@$8->m|7_54h9rEu=W#;Ls&ng zkT%xzD_j|=z9Lq>){VL?`(d?whNs^97BdTuAd?5mc_>I55A~PvSZ^LH+dhEx{65rW zx_{BQqX*XJE(4?h&5)%4%>#Kfw0?y8roE`o97NsrZcY{QE~eY?u~P<{oX+FXU0=a! zKd4X|XpSTWoHBiI_bEiq+ep8A7*u1*D9!5NEf`HcuhCAc%3#>yJu z6C5W_WZ4gHm0F$iI8RqE0k;TpiI#u`*B-S_y_S>1($yH}xmqjz4V;~vEN3p^E+0}j g#sKFP&WdW>HIjb;L)JYCJOBUy07*qoM6N<$f}FqxNB{r; delta 1263 zcmV@pi1MCNO0)IV#%ludY7y^C(P8nmq zn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^ z0;J00)_^G=au=8Yo;1b`CV&@#=jIBojN^JNVfYSs)+kDdGe7`1&8!?MQYKS?D#s$vI-^)!+G(}fY0@GZE%Iy@h8^{To?*ybtdaV z#dGpubbrm88$G&G)t}RfNfHWj;miff!TI4N-053E5yn?^gn~>Efgal6v3aRQH6&5h z5RdxeaKF#Z3DF+wT}ipqw*r>6L_o>H)_@;IH+E!`$CjnG=I69xv5?4rFE5T<_4z9k zUE13=6Yl7M&)0VYdRh(na56)ifvXBQ9rBHSpMOyB?&C3B2(E?0{drE@DA2pu(A#El zY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEUghdWK)^71EWCP(@(=c4kfH1Y(4iugD z4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ!)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup z8Gny~yBS`4IM3Ejiyx@r7k6FNj3I&(u=gRCXSyuIdEq9`!w_5#8|1lh<}9zje~vrj zSvOi;kBKp1x_^6dt;HYDvVDC|!;npFnu?Iwkp16`aq;61 z(bxp^tR-32bxI*$q^qN~E=WtXi*Lvr13@UdTP>e%gS9JH6E0gj#d#qJ@j6<$^6L;U z96QH9<5?VA^D`+63@3jYg0zM}Vnuf+7mvTh#+B{(=`1atgKG`0V?kn3XB$_3zkkE- z<6kn8$>UfOK*JmOBy6`wGVJO*%iu3V&@!!@aV?BzS+#yS=il1R z>g7u?fH5<|9S}wLf|2wx@ELQ^JW3#kM*rj72*DX61F^` z1H57+xxlx;4(6i$CY4j99!ubU1ZbWz@uFLa`oyWZf~=MV)|yJn`M^$N%ul5);JuQv zaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$NIIaX+dr)NRWXcxoR{>fqI{SF Z_dm1Ylv~=3YHI)h002ovPDHLkV1kK~Wt0E_ diff --git a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0bca859a3f474b03065bef75ba58a9e4c..2f00e1e02a9c27e8acd9203edd30e750efd57bdd 100644 GIT binary patch delta 1496 zcmV;}1tDlA;MFcLM!kQ8bJ+Qzn8sX;7pfuI-~v6sHk(8L0^q6w5zC;JNPEjE03)5!DrbdpRTA&R6EoOo5G12zUZh=u_KI>|%;=0ACZR1t|0m`G4 zUfaFk+P6}9a(`izDV6XHP#)K;W|#Mgq3m99xKP4$P{)eyx;X=jE_H@E4tJ7ONGLcp2_sH*%IZ2fa-EE( z+ssfTGqjD`sezA~x037Fv!lp7BO|*>#^%OYF@B!aAb(>Eowp6JuUA>G=bbhg)iO)%*kB*V9tYd74rgPJgT0Iu;2FWn4rgPJgC|Z) z9BjL19e)mEkAwZx*>SKB9|W9%-N(ip#@+-^cW0Spok~p*aqYB#-G9VmUhXi5QCFQm z;#rUSFJbS$3J(0IV1I&?{I8&>eGJuC6pS%|&lH$Pa%q4gw^_y;gbh0Ata&`&$MkDg z7;9BAvy!qVB;)1NBl`9K^fPeGAeh;{;D&kuc7KXpUvI2c1!Jsd1@$#8z;AdTn-(wmT;FF?xreFy~0Oso|>1DzIN?hcw0TEYCBVo0xkj@0}^2yCek z&}irl)7fbOmf=DvjO*x7TQ}zCRe$N2Ioag`=?XJ&W{ zTaN}l>E5;_@nz4ncaNj*&#A~Zn$(4 zg=HbU_e~=TD;uKb-N^C%swTWuQit6&=O8O8rsNf_FlCXs-?H5O&80?%Tqx6Y$ixOH z3n^i-MbEurjBDLaDk|ei?M|kQQWk4g>jv?6VAL39Pnniov3A9m&XQg6+=8vN8pW^{ z=(1V#l1HUs6K}TI(J7Jhol%sqZCYm@#q;skj6}(l4fcFo$?)J5dNtl%J-Cv y*x1Cc;w_d{1m-Rc z210OTK!#(3GBnSlA5cdIBse}KgGA!GuT*2G$0_ieD*GKNGJ_qM3_-=2X?W?kDOlGw z1-Uv}i(20%vhzgfx<$ei@ilE)c2eNuyel~ldv+S~+J8mZ+$F-MPJPkWi~J&#;&I zwjDS!K!1rz=d7i`W}>mjL0C(G&5odY9fUOvoJ!7`24294fb+m+VS5({HX&fEoh7O~ zRV=xzICEO&fUTb5kz{t5U)vRnrg*%jH(~GJ5*+wPg8lQT_FoCA`-9MQS%M%*Y+ftG z3QKiyJOpJpsCcbC43ns{ygq-IDCe$axhg&7qJK8{M0oM^lyd%GV

V@$v#>H-Tp zjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4 zpY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5>ao-}771+xE3um9lWAY1FeQFxowa1( z!GEEtTZKB&n8Thy*i}CTVC9LxOwcQG7}k=ZC2ii+ts()n&iOe28+~E0 zA_rkDn>_Sj5c>0uv9lDGg3{xx6WY*r!&)*u3~4;RWDRFmGgzwZC}<@H_*Jk0dtfa~ z01(yqme*%*&pNAy)q>9RO)34l90xUaIe%+QPK(2~jmS!HTB&NrS_(r?Le@zUD*ljQ zBp7ZpBo%U6YT%BWL;=m52RN%`0#>)m@Z`~9c&e%idM@7z_kWqB2Cy2sdz8R42)m+1 zyGc{n>{Bvi9vgxC%W6SbUjq+rZ-nkk_Cnj5h(D(Toz-#=a#|_u4M_Lw1tG5n(tox# zfKX5i4{iGzx@~mYwx3!g9q2GDfz0M%NYC31LhdKv_SAu^#0zO#>Jhpw3~iIsw*HtF z4ZZxw30U(hK<3e_kpBD*5T2_7cYZaviVuQo)0c|Uwj7#;w!vxJe=ePcmSN4QX;{{D z36{L@Itb5Jg1cZJxIEq&SWRe)oqu-Z2k|g;2%#m&*jfX^%b!A8#bI!j9-0Fi0bOXl z(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twVXd^Tr^mWLfFh$V6P57%1-m2(NsufVZ9>Qc_NFKy$YDDex1hd*JZWLk{h~ zcmt}uK6vM=PN-_|YwB%QzuVXaZ`8KI?&fnKN^-0#@i=An>-pg8$|DHVK2DZ@lMvK)nT0^DLmiCqkgQ?(c^oJ z#SL29cr>81P~WgeR;rW<)VHXdd0`E<;;lk`K*tHF+mL)4wdl~R103}s2Q}K2_8)U< Vj0(=c-2wmr002ovPDHLkV1m2^{pkPz diff --git a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118dda48d71e01fcb589a74d069c5d7cb5..dd514f6ded86ab47844c9f8e23296b928a3fe679 100644 GIT binary patch delta 826 zcmV-A1I7G-2+;u< z0wE$w>meVCdI^H?!Lp1ri|C>$DH&#B-bz#Ovi1-ZMNo(jf^D1Y%+CGanVD}H8Z>tp z^3VSHnBDo#@0`QT$5{p-$uuJY$RxLuXUGa!^;bSnSH6$b_VB3_2 zlVdU}LNd-y$T&ACqij^h^-l^!EuIXQ)4?|;Gy;-W;Bx}zyp?cpP(lH@Xh=flKn$nG zWz0whuz1^|%zqECbXY>kdkMi22?yRuR>i*+2e_D+AK+mE9-(zV0m}*ai-3tdU~GAT z9J;(f4)qI+cm#5&{|Z%FbeBCt4I}`R-I0A%8qc&f0pPp}jQ!LR3hS64hq#Alb%)BRVqrWA=OK0){y-O2EnwGO`9FGz|gRB9 zHg-&Fn29;S;voqsF9huBj#=kz$xzHXb02e{=I3y5w`fW3=*?zQ^y0r08~_Mr!ryRsq`xxVclrmQDew-5SzsLx{`?c9ka8FVx-YnlFmg z+O*d(r1b+SS9`&-s<6GX4YN_n0sDoz!PTE-tnHAHQrQHy?;?_qKLE?G#f}TDF0gMX zpEr20Ydv4Vc3;8D;C%#w4OnG@3+`YiRUDp|d4H0|KcREp!3Hk1OoHXz1}m-uE4m9- zS`U^}gS7K4m<|i}{R@NVO=ypo8OeatzRS(ySYP%C8_zUh!|6tBDsRS`6Ay8ysvC2n zVpU5Y_lz`PLUX-OgHHP-Mf=N&rXwQAP*i=fX)r%NhStH!M)v|V z7&BzJy`UP%$>bHx^;yoHXvH$=807*qoM6N<$ Ef;$X}@Bjb+ delta 1003 zcmV0sYywyRT@!HH?E|j zMhb#%s#4upT=)UH*V?AKa$!XfDp{yYMM4EjBZ*)|sUM-0QcIzVVEUzzCX>V@GwETqrXGsdu zA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnb zRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yIC*%Wgg22K|8E#!~c zTNYR~@Y9Kep?_ava@M;M8kmViU%_&qKVn19EJb6L%o<5ryPpl%8u^fhsfT?9!(e|z zYg7{msk{Fg=+Yccm!Y>{*q;KO>K8^PXf0r1U?zrb1;c^{Splu&<;=9>z4K+dat5se?oWZUF&sQo;?$JL1+gmMrdqaw4-)Uy6fE`VS`NRWHPm9hb$Oljeq3qzfzL34WA)&3E*EQj_ z#)s*NZh!yi@GmiUwp(6LfzxI2{CJ7UMd-?TP+;I{8J_M5c&a;WSjjcpVCBWnFDBgE zY1z>1fzN(l;@ioDEiIOtTMPkWDu!pahV1DHm#V&=)a||1#W682UO8Ff#AHHe&ajZc zxl(1kEaPYZ6hwubN6i<8QHgsu0oE)iJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+& zK7ZsKxz;@6=dLht@F-X3N(9#6>iA+L6@m3xzycB{LJO~c@)K`<_zg5y-Q>ju`fuOJ z$ZNaU+|lB5Y}R-@XC#dRJIOdB32iJUA<4bXt Z%s>0@A=V4gM}z;`{{H;_|NH*` z{r&#@{r>y?{`>s>`2GL-{Qmm<{rLR<_xk+Y^ZwiO{?hRM&+q-s?)}2*{lV(|zv=wG z>HNLv{JrS>wC4QG-RraE{IBHvsN(yh;QONB`=HqVo80=B-20N+ z`+tqx{E^!Gugl!T3kP_(#F_NWk|;!1zbN_eQ|?Lw~>bd#KAV!v8zG_bkExEWrOQzyEfm$#$g3 zEWiJCp1&-&_A9scE4KD3w)QEu_9(XYDYf+|we}~q_9nIVD75q?we}#j_#(6R8nN{m zvGp0S^%$`A7KFeTgT5Dlw=Q+9Ep@FdbF3?KtS)h?EODzBd8{jJs26jf1#zndTz{Yi zTAc-2oCR5&16iB{Seye`n*>*z0a%;?SDOJ=ngvvu0alp~QIG;vngCUr15=p;Q|ODQ~v<~0s{mE1_}!d4U-K6L4WQpjS2t&0`o~kK~#9!<=0nJ6Hx$$;crjY z-g~dud+)txv#!PnV#A7JZ`eh}ZembDi3o~fFNj#chP_u*K*fe#Y`?*iO<*H>vI#oe z_+Df(J9*~I*~w%Q5E{o5QnYf@&fR;e)T48=D#Z!mIYNU~a-?MaUU0GA4N6mDqkkI0 zR|6pEM5rd}1OOhnDI%bo`5J;=aW%SuCIvZ;fa7yDMjyCUA2iLwbKpsJwq)BHga;`B ztq5j%$suH>MQH-k2m+W%a7?e97tIKY2~IW*n6xr*nXSO(t9&3Sx4HzXLU1A{hlhq* zQ?HfQ1QkH-6uxuTLMPx^2%zAK!cP+lN;viEi6~ z^Sf{V`0_d>IXN-)#ZtFy-+6bwze`F=rZIK}mi6j;_~Tvb*tRP~mc_yJ2W;CNZJ`6h zdgDO){ltfI_Z~l4054=jQ3rTH0Hc0`s7-q#_crbQrxoyYIA=|i5`RU2d0y^$+OD5*felRDY?QCpAj;P||4Se7l@ z7tnAZ8dA751RY#Xr$xIWl!4HP{B;3Q5TTm=W%UYCM5)!1iY?%BEi1`Hri7!<%GK)F mQ+>Brr`qK?qLKe1{S^e5)8|=g<4&{y0000+F473f9DUmPZfByg{N&q-%07ZWQN|OLgl>kkd08f?xP?rEvmjF_j09Bd+ zM}GlKiUCiJ0aKF!RF(l&nE_Us0au#=SeybkWdcx+0#%v=Qu8xugl=CHNOw{J-h^!Rq|N>ix{!>&@={&+q-x@crBK{@nBa_xk+!{Qvm<|N8v> z`uzU;{Qdj={`>y_{Qds?{{Q{`{{8;`{r>;{{{H^{|Nj5~|Ns8~|NpVjZyNvr08DgJ zPE!B@0s{mElL-Sxf6>(rKA)81xNB!S9WCy_czeGXN+Q1>T+MrE7?3YEFH1WJ&s z*)ANF5@yTFt6Jo`LuUAV=YE!$dk=2sIozv|-M#PWe^H~e{Fu5wIZ2Fu->q*fP-s@7 z$yuhQoiT|yoBWVIVGf=o8tvq7UK+)V8BB>;_~O)UVnth0^HZvnbpCR%HCk)tCRjJk zU^?h3osI;qn`D*mVpbn9ouo_WgUG_8LvJlICT`6E!IGuR@#o+R_JZ83)2-tzX`Pfm zbr!!we<|2giuetJjNP?x z!3)TjO>r`#g|e~RmW~@U^*Q{IO*u2%h{knew=WqsX?i5ZlB@D|dHnhvi-(S%7#>Bj zlnG;-xoq#EA!8?mYqe34)1DD5?LQr7F1|T`fAW-w2(6a4q`HXKNpJ(%ZH1hbE0MlnLBgVODM2!L;4^udh=8C3)`^KLPmv+9R3&z z?V6ufs5V1yx$vB?Y}~U|$fliJc7K3BB`p`!MT)MBM5FiD;kU20qKqHzi0gq$;yztS zY#vrYL)^!sw*S-AdSj?imhV~mR=Rg34+Y=)SM}qYa@ogu1qXYh_e(SzL?&}8kYtul1JcP%bf7!5yLd=GP_5c6?07*qo IM6N<$g0>X#u>b%7 diff --git a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c28505ebc0b6c3a359c4d252bf626b9f..740b6ac9cff7753f94b9f544fbceb13c1a67b148 100644 GIT binary patch delta 1833 zcmV+^2iEw254R4GBYy_|NklTq-Eln^CM z(ZS-5(v5^Ep(y*chcZFzWV5gt{E@Phq6tqNbP=Q(a1lC0siX|#U2JL$02o5q4{_sO zAp*IDlEvK;?SI(&DIMr8Ee$(0ZVB(^Gn5CpUrbnI&fY~i1-=2VdaI!Ec%G6K71%%> zV5AxR2fZ=_+(sE>7^>0c?E7BFCN&~fh@K+sIDYuy%~;c=3mrU>yNVxT2Pu&0s35G{ zm882#bZe!k+?By*^M5ZSDTltbIBW5Sc!4n2AdGB2P-kITVZ>}^hjGJz- ztP~GcV%&6vWu@Q!4-FY^Lf7W_Q8fEg%xn^1diXq#V#HNmT|3juyxqV4h;;pSTa~sxYj$^ zIxPEpckjH8>_&DpA{VTHn^Itcu?9PoV1GyNxq;ygH}J#(1}qP(fSW?#&LH@2&s11| zePs6yjHH+aX0VfQf_2e_cp?@%P%r!05-i08u-LIJBCHE9#M6y{<5_@>4eZQOU>VnX zS%Zxa>|EHs#We+M9=NavY?@w(ryGe5YZ{n3)`!W>KGdAyuxWfD9^3gKW$}pshJSq{ z!kPzuxZyg+HEP&=Aq-X_J&iBKen_JbxUQDzyy8vtxUo0f zexJe4T5mxF4(ni5zV{TA38ly^*?^2WTcDQIMglq(SfK>qv=V{CA(U=C3T0X~1Lj8s zHUaA?$Gz;-iw4CJQM#oK41Xt+kO)f#;4X%H%*m@W>AKOuB5;X~z+ncQk~8~X8SWOR zFOX>fr?`V{U(fMthCAQMo!c8QLmF7J(Nb=mTMK1+HRe?}3D03G?i&pEK1!EU=WegS zxvQZR)IiB!V=XVQh4OqA3O?S4E}xcc2aA3_%0mh(0Ch4fIT#82&VRO}cyjR$Osc3y zc6ptpJhP-8W8d74>b>oJMA)&^-~n-o*J)0lyHSvBEqcN190@ZyY=)7aWCJD;LbfG# zE|A$l$)u>_n&mm(D*Kj8bjypiD15C4oD}SrqG#lNB5tR2$TlAOu|%hHl)-$=#=3RP zaLQ#5*c9wF%4qHw?|-I8;~B6wA`%(}e^Ig{$3Z;cN5v80^fS0Vtgz6rXDMS1uYqQ7 z+clW7ReT$y=^apXVc&UuMHwP^C0-HtoSZ>95oO+lT@0-YT`E>Pr7)rq&6ju^6yv~n zHl@+YOL3>D{FEljJjyNNEzaz^$*1`JXv#uLJ*CsB|C^CSX(E&hlm^OMlrajG*>3$8 Xov04NhO^aO00000NkvXXu0mjfBGzzq delta 1906 zcmV-&2aWi*4uB7kBYy`+Nkl^fDmF(5(5$|?Cx}D zKEJa&XZP%OyE`*GvvYQ4PV&!g2|L^Q?YG}tx;sY@GzV#^0C$Z`m}blr)&(OPMHT6T@%AxHOWYGG zm>@=0?<)?0r0XK9>leRXyD^j4O4bXT!@LESVI<~>`yB+S2b_f__vvyMHkwGyyXedq3usx&X*}E{O;}+nJ#G_xm zJbQX5 zuFrVK*|x9RQeZ1J@1v9MX;>n34(i>=YE@Iur`0Vah(inE3VUFZNqf~ ztSz{1fz3Fsn_x4F>o(Yo;kpqvBe-sbwH(*Yuz!}~S^-;|MX;9Py1im8!}VY-!u4P+ z!nLEvx?+Rogo+Kw9OBXy&lGIvJ51_7Hl6Q>*MAJa!lMDGGML530`Te&0ciU({DC7` z;RKg{uC>($*mdXnVQQBjiVyqYC7mf!Z%e-Q!?Oo{ufu`G3CAC=UjiTpU<94UTFz2KEDuNS@KKkg)K8!v1L8RUP$_! z({LG$Dy&JLEVpiNVe^G(HCbA*SpsX?292)#=JVQDUMp-cOJPl`+yTHTD!AOit-zAD zptXYa!q%OVLa^mo25b7^Z>kE`3rqgr-G4i;Lw<+P(8khESW8aaBmxfY`WS#6j<92Q z-++&Y z$gbH4#2sT0cU0SdFs=*W*4hKGpuR1QGacHFy;`# zR&NJkbTNp}Ee5f08HmLzK%D(q3@lH;Tzx$?VT;&xJq-KyHV__o3dD)?KrE<6*iXV0 z8;6<{IKw@dyDwJ90H5^3h;|9?twz{UGr|>1Pzhdn6W(r0ifeZd=JI+AAz<~yL(QJk zAWW))+=`8mGj|J!70oF@d4B;WxB=jBM1bWWRBb&1!ie3BUzOdm}yC-xA%SY``k$rbfk;CHqifhU*jfGM@DkYCec zD9&E@F9*za0?~OQZiDE|Cf;;0 z`uZ%H=IeGyG^H0ruev{yIVARgwZUG)j7>F#3~dh+8DOtPByR=(VDhz2enf#EjcXCI z=NUPYr4@S)GhX!?s(0Eh(nGe!y$#$r1B$||I~`wPhUuCQEo;#Xb$EXk=6HgBBRd#c ziK|jN$e~{zV~|;*E@Ij-Z(+tI^B8RW51ptE!nMnPyZ`_I07*qoM6N<$g0N+b9RL6T diff --git a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967d9697cd4b83225558af2911e9571c9b..90f6959d924398327f702b936a3c9f11a28a159b 100644 GIT binary patch delta 1062 zcmV+>1ljw83cCo9B!2=)L_t(|+RfPCZxmG&2k>c2V+@Ip#utrG;x6K^h&~rniTr43 zp|T}nq9#Uz$^#}m7>w}`Fu_=SD8(3~LZU)UTiue-0`0acD51tkc_7hP=`XjG-PxJh zne*k`+*wY#Ff4Fq(w^j-oz2eXe&*bnotxlSVpAi_!M#EGIoKJ zR>%r)Fg~1NiD8&iuP+E)J`5`eb;U9;JQHAac5!l?U*-awnDTM)Dn%cVSlSfuD2L2| zg1cHK>^bM6d4I^k##0V9RC3b6=F<+E21|JLXBU4L33O{9ae5VS61vD72PoW&jLGC2 zyd**As0O}s)5JbiRXn$})2(D(C0p$n_3itb+PwSLJ zn7&D7IscUiGY;K7kiyKmgx$ZnjJQ2qS-;@G#WLE4_5C-KQ_=hH7**fZ@5kqZ@PG_} z>WJSw+N%Sy)#h^Q#|hx-aac`jOv`=^gbQZZS`-4 zJF2(Ohku>(DaMQBe^(>9lboC5KK#SOy1^3Ev~D%WMFXRedb;2E)fJo`5AUCMasSaG zo*A&w&X|rIBO`GM{!q`la`pLC!FhP#SP_r^Xk%5MjVFInNIlK1r%TZ|b71ttkWU)D zZ(QTnFfP?^BhAssBHBh=WX3#wK?p7xpdjU(+kZdd;lA&Rj8QmAy03s#*25#m@;LQR z0jfOFB?lCw&ZPqJouz~ae~=EQdJ9NrU99depyBl|@$8->m|7_54h9rEu=W#;Ls&ng zkT%xzD_j|=z9Lq>){VL?`(d?whNs^97BdTuAd?5mc_>I55A~PvSZ^LH+dhEx{65rW zx_{BQqX*XJE(4?h&5)%4%>#Kfw0?y8roE`o97NsrZcY{QE~eY?u~P<{oX+FXU0=a! zKd4X|XpSTWoHBiI_bEiq+ep8A7*u1*D9!5NEf`HcuhCAc%3#>yJu z6C5W_WZ4gHm0F$iI8RqE0k;TpiI#u`*B-S_y_S>1($yH}xmqjz4V;~vEN3p^E+0}j g#sKFP&WdW>HIjb;L)JYCJOBUy07*qoM6N<$f}FqxNB{r; delta 1263 zcmV@pi1MCNO0)IV#%ludY7y^C(P8nmq zn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^ z0;J00)_^G=au=8Yo;1b`CV&@#=jIBojN^JNVfYSs)+kDdGe7`1&8!?MQYKS?D#s$vI-^)!+G(}fY0@GZE%Iy@h8^{To?*ybtdaV z#dGpubbrm88$G&G)t}RfNfHWj;miff!TI4N-053E5yn?^gn~>Efgal6v3aRQH6&5h z5RdxeaKF#Z3DF+wT}ipqw*r>6L_o>H)_@;IH+E!`$CjnG=I69xv5?4rFE5T<_4z9k zUE13=6Yl7M&)0VYdRh(na56)ifvXBQ9rBHSpMOyB?&C3B2(E?0{drE@DA2pu(A#El zY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEUghdWK)^71EWCP(@(=c4kfH1Y(4iugD z4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ!)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup z8Gny~yBS`4IM3Ejiyx@r7k6FNj3I&(u=gRCXSyuIdEq9`!w_5#8|1lh<}9zje~vrj zSvOi;kBKp1x_^6dt;HYDvVDC|!;npFnu?Iwkp16`aq;61 z(bxp^tR-32bxI*$q^qN~E=WtXi*Lvr13@UdTP>e%gS9JH6E0gj#d#qJ@j6<$^6L;U z96QH9<5?VA^D`+63@3jYg0zM}Vnuf+7mvTh#+B{(=`1atgKG`0V?kn3XB$_3zkkE- z<6kn8$>UfOK*JmOBy6`wGVJO*%iu3V&@!!@aV?BzS+#yS=il1R z>g7u?fH5<|9S}wLf|2wx@ELQ^JW3#kM*rj72*DX61F^` z1H57+xxlx;4(6i$CY4j99!ubU1ZbWz@uFLa`oyWZf~=MV)|yJn`M^$N%ul5);JuQv zaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$NIIaX+dr)NRWXcxoR{>fqI{SF Z_dm1Ylv~=3YHI)h002ovPDHLkV1kK~Wt0E_ diff --git a/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/sample apps/flutter-meet/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5cee1c98386d13b17e89f719e83555b2..967a62da75f587c180d32e41dc5baffc10bd59c7 100644 GIT binary patch delta 1807 zcmV+q2k`jk4xJ8=BYy_uNkl3{Fhlm(QHl;f0ZlrBmS zrQ0Rx5udq(a)R<5dW(^!mkjFM|IagGNdKv@pGB0Me!a{?L>(P|atF3rX%-Y`l-Ot%faGLHKo zyddav3Hc*(XfDwUr ztW(9ZmJq%=7s86xM515wDP7;*!@!FT9FS3(DGJ9MOMeMM($esb+)z<)pclDKy_iYK zOCU4%M>DzMHny}!#8ghndiVqfd`s5p8-sVS7xO6P&Alj1AY~%62=J$C(IK+bP2jp*;2e5vAbHwQciIR0V=yYrIA+n-uIlT|whyy+Cs zb)9FzOMgQJeNLxtrt|tPAMDG0c$PKK( ze3{Nm5>Q$^N!L6JU}nQe63}haR%OYDr4rCB8-IqZjp%bj=P^U~UFc%C*qwOVD3^K| zT5hW73^MV)cMwob>zZf93o~Hp=@9Y>y66!9f6h}#DLE9qt6|~sAg=c^u=E4knQr;Zx@HYtW-;kmE?ld4Dc%B$kNz&8S9`d$NsOlhrW!HHHBV)PcS`Qg zGXbUx(K((f9)l|I&dDGF5QOA-I$#o^OdbAghZ0{lL+u#^Q665KBsYdMJ zh*7xG=EU(P?7D?9J8xlnqd=4t&jgsBPJhg6a*p?~z#ID`j}4+7o||v&(4gx6ieTH7 z#PN*Du_D^zdEwR$#thG4!h;bnXu`8*3`Iw@!Sm9s9V(*?sPk+(F&>`RZ|z{P!E=aM z(R>>-8gIqodHvQ7CIqZFRgv5W6w;Kp2Z#fX-euiZ_&Q?P&Nrf)QJLFn^x# z#t9V1sqe<)`+yPaTCt(=40r{@D&m!=0F9lpr9F&Cck~dTbHtqbEKZu?L9!g<+M9%`+AX@RhZbjDgMexn~3`$8A zl+txj%GMKN4M&XIiJk^DA)3}ifwzzFYR@1r>2>&~ErwFCoUkeha4iRU+$B)vd=8~}4KzG0SrvVD;?MxJAa3u*_(ot<^?ylZjn9W~ zLJ^ed)P033sPi~njgF@$-gF)sfI1?><|`3APd$dEFliq zCfn^N;fN{_&%iflwNu2w+kaSetE(JvmyAF84{3;dEQm5pXYEZIIpBM;9y$?<>BKb% zlvgA0LM;LnHI9=_ug~V!6|KYfM=nN3ud-AfI`Ihyd`i~yVt9p(oj7|nIHYebWsYt1 z*A-Mnug$AOO~ct#ATp7O+R;x(Wj=UELtMZ2EG8}5iswG6$1@++Ie*WRIy_6)O#Wa8 zCcnQOr!IF!5gAM7_@|SUfOc}xd|9V4l|I-MRYYZ}IDl-S@$A5RD1QoB6_OSWoFJ16 zOU>b9S1I>W6yx!CrJ3XJ9CzQ#!Yp7uLvTw5(H5`HXl9(oe#(86mJDKCyz`Xd!pa}- z1mBUA%bBAngo^2jmp*5lw~>k4IoU~r6Q*;vH$`zdcyeuMYx56at=VFm}qmv~v_0R$*h xC|^*TDK}i+g-nd$Fl9OAc}li%ymmhS1CU^I(mN-!&j0`b00>D%PDHLkV1iBgYGD8X delta 1881 zcmV-f2d4O)4(AS#BYy`jNklFQtfgmaf zE#=YDCq`qUBt#QpG%*H6QHY765~R=qZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1 zK4#~5?#|rh?sS)(-JQqX*}ciXJ56_Hdw=^s_srbAdqxlvG=BhaYkq$ewHUPpbsRN- z8b*0hBN-yE`ach%PN2R+y^b1xyS^vYaA%P{0B)C}TGSVIY9nx4Q57f`g-BQ=3(v)D zPe=Wtd7%(WQXl0E1}1Cz!nR|m!dOgn!NW%nYBh5}NtNti2g*!8Xk4x|Mj*C>NiaUs%#fyzU5=HSJJNOHlQqY?|vw)nU< z2QMMS`lwSJ2Hx}P6M{KbaT{e*9AT%9eI(=l&SYCYM|Ig4rI}dB5qd~wj*Nx4QJx9# zIBAs8%j_7~&YS~r*CZ9*v7r#G>JGqH=K`>%Cppo5K7Ymi`@M4Hq%TE)WY?~6BEcUM zP<~#WN8ug07J`z4J}B(;!E97f5}JKL(X{rPu)SY0B9!TP_yhs?Hm~6u1@Dj#7NDxT zd{CK$s#MKEfIkjM={PS|7%w9Lck&33!aK^qBS6Xz>sN&;;@I6+q^RQ>aFs$@8v%HZ zxenykd4DOxJ773GmQ_Cic%3JJhC0t2cx>|vJcVusIB!%F90{+}8hG3QU4KNeK zmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u7JyDj7`6d)qVp^R=%j>UIY6f+3`+qz zIc!Y_=+uN^3BYV|o+$vGo-j-Wm<R zS$}xdX~O$Yg=geSue3vW@1PX^2Pqgc;1#9_uPTP~j9w;ZND^rqkiuhAB8Yn75d}l( zInN4shMvYGf|$&hM>@LV6yMG>^v=H@#E$v_c z;k|a$2M;%S5%$e6j53blnKqfwHmE=(;D0K+l7~0B^#+XJeFJ88=!a+rJX5E$s1u7i z^}~Beg*Rc3BE1mpf@k`KYV%(b?7W;BJgR~O5bcC#>Xg1L$DatG2}6PBj|(s2#67Vg z+6~Y250zrAhS%%7L{8)Zp6LKbxe`)?r-it%>n6kA|=8iMUiFYvM zoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS*ZdmT`g5-`BuJs`0LVhz+D9NN za3<=6m;cQLaF?tCv8)zcRSh66*na@qY=&sd0?S|&MDJ}O7BnNgc7XiN=fOQ^B?u*J zL9DI^H_BDH3B*!+A=rxG>mqY4 zM0QLJ!^4D;-*^T*Q{Mn##xf8~RwJw$1h^p#P^_xgK)hf%MBBgS5EEjH3V*NRG`OEE z2jS^OAQmnMao*=3me)q$QMnh|=R_{PXcGmoX#^&90F0|Y3GPY7AWSX;aVEO2bPYO> zfNO|?7lCL~ICJw=F9o6u+b&CRf6WQVn}V*J@g73MF|WE&Q4DY~AzGMNxcahTP(8<~ zYxxy~;u`S0v;|xX8%Kq#E`Nv?IgtPy0ghS(;=XPW=B@(|LUC0$=muzUVhIp{y>Vup z?1QLwwr<}U5az8<4WhY;g|fOr0JhT0Ri2zkJ6pFm4T$E2Gd)NL0r)J0wVqVe+n)AbQCx)yh9;w+J6?NF5LmoecS@ieAKL8%bVd@+-KT{ zyI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^EkL$}tsQt5>QA^;QgjgMV83iI%^s)DX_K`0jg!)|u7T5Zs=Fm=Ip`s2bC17?Wgj52`x{ zF)qCGsQXk_u6PxE^HG=RsaTF(1Rkk+UXZAgubV0N&N?xah2Uh3^Cs#l56?g(R{cTq zPGkefkY@O5Mm?BRZGDI?1^+6lFUF-zrk8j!wrbtr0pm()i5ms1!m^!%y4VF z_2qfJ=gaTj=iJ`=+^6(ud+&XE&$;KDeDXBw_N=*|e$Vfmdw>#BSma;%Z_#^TfTz70f7K#C61vh@(UrI+IO?=pnWd z*ZZ$|CB#m%5FgJZzD{(ZG$xTJOaa%7j_+Fe(@m@<&i7vb$Xg3wnP{sMPUJ>p>P#-K z@SN{DzD_hn)_-OIGsNqq#ItbXeCO#z6wz)s2Z&F*PJ)Xyi0R_vrNk*M)}M-t?j@R> zCKE;&a@WC44mwj3Uz;EB%w;2p+#R~Lu?qKy-s^4hWM=M86q41Or`RPcUVq{}FHc17 zhLJbYiEA7;507i(;j@8~5 zP)Hs+vcz$9*xewHF1Jn}g`|z+Y_;oD9Ek+y6UPaj#HE>6gb3r_CjKvRq2txNYhIny zrCFg}q|@RxBfDy6%_fc!JV}DRVh3};a2^Crt z8sGhM21BAcymFK0B=LR*U~Uq``sMDh3ocrg)o!j|@O}p1Pb$yfgf-W{M8C-ZoK)|;pV;R5 z1)pUA2BBblax2(e?#PWpI8*WL%Ppc`mfhxdmNYjuVNfi(xe5y|lAG}6DkoSsS7E_L za(|O^6iS9*ZlUF>DLA1wF_Iy;L~e(kxylIEGgo2333}w0Ji&VADl9m`4-V89tedN_ zVBK7W1?$_73Jcbc{1g_fo2#&3{m4&Y!MeE$3)Z(E6&9=?`6(<|-^5T@uzuvHuwXrN z0c8d2M}CDUDOk^3H_8dt4-Sf>lwjT5(tju;IPT3AUtcBgMh(Gnzx}9sv00KI`8l5- z$^%FHOuW`_q9-Dboi)*Y#Y4+ILJMhtx$8@%gdEkuvc;_@0C2&%}vI#T(M)4>5;s)hjyxBNeo9W*Ug<`!R`eP z-eiAs^@b{d;E0=?#Z42G+$e+G$$vCg6~X={yON`AZeNz;?pHHdoKU$w!EtJG@aFbr zIBGjnnDfFgnvO&(_tBWj4JtTzlgF6J4FTmYq1JxvxgmV^`C-g$OQ9}d%?%(p+$OIZ zrOAyU$o(kEy^7jv3BLS*keo2N0R)G>yB0^NKt~}VH#H*n%I7V7l3xifcYmQIasvpC zMlx-Nh+xh2%N<>^T1TPS3ie61#oqO5Sj{aXS>a_lmOr{ev0FRfG#7!JTVArl&5h*` zpSd50*4*-v6>V;8e|(gO-`w(&6>M&7e*k>kau55P8&I-h&6Q4YndXKnxn6|gf z$xAL*D#52S9Lry}?=MFp_%r(ladii9;l_hl_0Y30?e=nTF1O;?3-&J2!+F^EsV~_l zB!?(>&P%{W54K}k_5EPg+fldepe0y($x;X|`YD85xRPDDFM)k!BY%v#$5FNNNwAt- zs27qsVM;1l8o^%4gT5vQmixdVurIGCxqDzVJVCT#=GVNE0aH@RQV9-GvMcw8ZD8}i z2ji9&81;`4t(IV0bd``?C$VJd1P5QPh1_${k3xDU_rk`2}QLW>VDfLWdL>`KzAemzzmBAP8dID^53#xAYnY9f2cE~;g zH<`yyG9!keV4RD5E^Xra_4`QjE~{~2i7j)4ijQPQyrb%`j2e-g$`&NowuC8JhMOUS z3_uSGF`-~ga}>!dBbGeXj~d;wo?rleML@+_JT*;@!GEJhx2y*lfDNh(7EKN+S*CNc z<8mtlut3!rJVlbH{!ydrav1~g0pg^V9D(GOMH?>xuaw+g;={}efM1AD8EZ`|-4*wQ_=5LVwBE_JQ;HxFC16Sq9lhQU;> zu=$F9{(s))$Tf&r#D55`>TzkTCZ}^I{&M0pdj53=J^ze`WB=~Ok+)7^AeD`B7IMKE zVzwiAhIrjTASuhVlbAjPnp|9WTOGG{g9RTXP7`L5S}Y{nOSZ`)#^i`FTzDHGE)h(SP8-6Bh`V1{3kOc&_*dxg?y66^QqE z`z6<BgZVJ=XA4=xWF%WQj*2T_d!JO zuzxEa|AU|{MUH~Rdq+e(JAOlp7OA}0<3z1@jIzKfJtx19*hKWhP2NJw4ozy_Cw7o{ zn3(Ol2s!>Y2Ji`D8}a}5m=mTJKCW=zHS?yIXdy25IfyO*44>!^5qF9sRX=os<{WW| z_!e=IS1e($N=3|o8NNTej95uLM*M+zM=9^R%eMD4(M_}xw-KN6ubM$NQttw&^cJ>( ShAid)0000>?~M zD4_Ea)F{T7n5f_r0DEV6q zME!>9MD13x)k(+XE7W?_O4LoLb5ND8aV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u z9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBatlZI#nu$8lJ^q2Z79UTgZe>Bv8Gd@-GAI7g$glhau4cs#&RI{zEz}S zQz1roy^(K_i@~i{Qz2o4F`HSCswr`z15w)`zg(8MJQs!nUJN0p_yu(cMznnWiGY&Q-~FaG5{0V$3C+HF{1n4!3MJL-Vkyci#h=Ke#XQZk8# zty!3{DSrbuwP)bQ0Ce+58L0gr4GX_8Y}f&(Yi7_YzR6kz`7OUhz%}UzFZ(tN<66=% z?fo=N@ljLh@#?m;{ra;uXJC(Eg;5gN+Qo|uE^Dq5;b1r1@i_RzUDa*=s}5gxVrv@i z{xkzU+0g1eJH@Z4zY@Tsl9HNvxJwD#4Gvu5GJn5C;7-8TzYF1ZQ@ExT>YqwtECD<_ z3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w569N3O%=I^6&UL5gZ!}trC7bUj*12xL zdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl z=zristl>%lm~W^QT*&|@bP*#NfWzE45UJ)$0a(>si2)~Q%P)C=Rn3(caDq1ubeV3VgZ^8qPyK~v^_Zk+=-kgDd;`6WAn1;y#h+Ix-^t&lo@{M6% z5Y4g#2h6|!WifW{KKw9{>sOIW9n}SJDWw`z{id}2;aRmUD^H3h<3~QJkAH30&VP!d zb9uNyzCY4XuH2f`eSU+;ml-t2`@%_)0jDM`+a z46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs6w1p~$bp;6!9DX$M}Gy7KkyEB zoFH%`1zZd=X@(}X`r}*%*H6fCoeISkup2S~d;2H;Id?$F5~t-@0p^!AiEF#Eg>~Ai zbNzrUqH|*fn1}3s?1yu`Ko-@xu>;J&S^+t?9LOR&H^*H^ea zg}b2b1RvS1y|c;r=knr2!S zSeB&#hn1qq4V=k3^|5RGg-qa%d=KF0hg+ax{DYv4Ujo&O*V%wYhkq z&dmz2Adqu#|J@AQn0rCLssZ$xr%;VHU;=ssgWMOa0k#jN+`RyUVUC;#Dg|~oS^$6%wpF{^Qr+}X> z0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx z?_|q{d`t<*XEb#=aOb=N+L@C{TZS2Y9%Ii`yt-G2md2OAG5oop6?JUw{G5pOlJ zlWr$~|40AP2a-lqPL6P^k(qTJ0X$!Jz$Kj=cukJ1yJjpOCxFMwT7#p69Pw5oJJD1E z_)yfZ3UUCDrx!L}j;yfYcA}2dNZ^D2(qCjD0nx7}<2<>tesu`lwR7Hp8TV0^O&SYg zw@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSAf3Ih7b<{zqpDB4W zF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c52HucO=lrNVae5X zWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?0?&ZI+il6U@oqt$2*hgV#(b50MC@K z!xg9zsLzwuaLLe4PzQg9I+(K5H9j~OkD^b=CShJILxtho`}cnCbwOqg*FE$Jh8n-v z2V=C5XIL^g$S*0#!oXd_*!4@zMF5j}T!|WvdPhNyJ=`|Bj#05+xE$mP3Yrw`X6^-5 zO@EFb+`||%Z79K>fX4$+FLT|Rsm3?bZkf$))(``h1=kIkKEB^I=TdHtrc)-ds2HPi zA?uu*$=>mo3Up*(x1p-|GOGC%+&uX&WVHmfhdXf}j!wrbtr0pm()i5ms1!m^!%y4VF z_2qfJ=gaTj=iJ`=+^6(ud+&XE&$;KDeDXBw_N=*|e$Vfmdw>#BSma;%Z_#^TfTz70f7K#C61vh@(UrI+IO?=pnWd z*ZZ$|CB#m%5FgJZzD{(ZG$xTJOaa%7j_+Fe(@m@<&i7vb$Xg3wnP{sMPUJ>p>P#-K z@SN{DzD_hn)_-OIGsNqq#ItbXeCO#z6wz)s2Z&F*PJ)Xyi0R_vrNk*M)}M-t?j@R> zCKE;&a@WC44mwj3Uz;EB%w;2p+#R~Lu?qKy-s^4hWM=M86q41Or`RPcUVq{}FHc17 zhLJbYiEA7;507i(;j@8~5 zP)Hs+vcz$9*xewHF1Jn}g`|z+Y_;oD9Ek+y6UPaj#HE>6gb3r_CjKvRq2txNYhIny zrCFg}q|@RxBfDy6%_fc!JV}DRVh3};a2^Crt z8sGhM21BAcymFK0B=LR*U~Uq``sMDh3ocrg)o!j|@O}p1Pb$yfgf-W{M8C-ZoK)|;pV;R5 z1)pUA2BBblax2(e?#PWpI8*WL%Ppc`mfhxdmNYjuVNfi(xe5y|lAG}6DkoSsS7E_L za(|O^6iS9*ZlUF>DLA1wF_Iy;L~e(kxylIEGgo2333}w0Ji&VADl9m`4-V89tedN_ zVBK7W1?$_73Jcbc{1g_fo2#&3{m4&Y!MeE$3)Z(E6&9=?`6(<|-^5T@uzuvHuwXrN z0c8d2M}CDUDOk^3H_8dt4-Sf>lwjT5(tju;IPT3AUtcBgMh(Gnzx}9sv00KI`8l5- z$^%FHOuW`_q9-Dboi)*Y#Y4+ILJMhtx$8@%gdEkuvc;_@0C2&%}vI#T(M)4>5;s)hjyxBNeo9W*Ug<`!R`eP z-eiAs^@b{d;E0=?#Z42G+$e+G$$vCg6~X={yON`AZeNz;?pHHdoKU$w!EtJG@aFbr zIBGjnnDfFgnvO&(_tBWj4JtTzlgF6J4FTmYq1JxvxgmV^`C-g$OQ9}d%?%(p+$OIZ zrOAyU$o(kEy^7jv3BLS*keo2N0R)G>yB0^NKt~}VH#H*n%I7V7l3xifcYmQIasvpC zMlx-Nh+xh2%N<>^T1TPS3ie61#oqO5Sj{aXS>a_lmOr{ev0FRfG#7!JTVArl&5h*` zpSd50*4*-v6>V;8e|(gO-`w(&6>M&7e*k>kau55P8&I-h&6Q4YndXKnxn6|gf z$xAL*D#52S9Lry}?=MFp_%r(ladii9;l_hl_0Y30?e=nTF1O;?3-&J2!+F^EsV~_l zB!?(>&P%{W54K}k_5EPg+fldepe0y($x;X|`YD85xRPDDFM)k!BY%v#$5FNNNwAt- zs27qsVM;1l8o^%4gT5vQmixdVurIGCxqDzVJVCT#=GVNE0aH@RQV9-GvMcw8ZD8}i z2ji9&81;`4t(IV0bd``?C$VJd1P5QPh1_${k3xDU_rk`2}QLW>VDfLWdL>`KzAemzzmBAP8dID^53#xAYnY9f2cE~;g zH<`yyG9!keV4RD5E^Xra_4`QjE~{~2i7j)4ijQPQyrb%`j2e-g$`&NowuC8JhMOUS z3_uSGF`-~ga}>!dBbGeXj~d;wo?rleML@+_JT*;@!GEJhx2y*lfDNh(7EKN+S*CNc z<8mtlut3!rJVlbH{!ydrav1~g0pg^V9D(GOMH?>xuaw+g;={}efM1AD8EZ`|-4*wQ_=5LVwBE_JQ;HxFC16Sq9lhQU;> zu=$F9{(s))$Tf&r#D55`>TzkTCZ}^I{&M0pdj53=J^ze`WB=~Ok+)7^AeD`B7IMKE zVzwiAhIrjTASuhVlbAjPnp|9WTOGG{g9RTXP7`L5S}Y{nOSZ`)#^i`FTzDHGE)h(SP8-6Bh`V1{3kOc&_*dxg?y66^QqE z`z6<BgZVJ=XA4=xWF%WQj*2T_d!JO zuzxEa|AU|{MUH~Rdq+e(JAOlp7OA}0<3z1@jIzKfJtx19*hKWhP2NJw4ozy_Cw7o{ zn3(Ol2s!>Y2Ji`D8}a}5m=mTJKCW=zHS?yIXdy25IfyO*44>!^5qF9sRX=os<{WW| z_!e=IS1e($N=3|o8NNTej95uLM*M+zM=9^R%eMD4(M_}xw-KN6ubM$NQttw&^cJ>( ShAid)0000>?~M zD4_Ea)F{T7n5f_r0DEV6q zME!>9MD13x)k(+XE7W?_O4LoLb5ND8aV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u z9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBatlZI#nu$8lJ^q2Z79UTgZe>Bv8Gd@-GAI7g$glhau4cs#&RI{zEz}S zQz1roy^(K_i@~i{Qz2o4F`HSCswr`z15w)`zg(8MJQs!nUJN0p_yu(cMznnWiGY&Q-~FaG5{0V$3C+HF{1n4!3MJL-Vkyci#h=Ke#XQZk8# zty!3{DSrbuwP)bQ0Ce+58L0gr4GX_8Y}f&(Yi7_YzR6kz`7OUhz%}UzFZ(tN<66=% z?fo=N@ljLh@#?m;{ra;uXJC(Eg;5gN+Qo|uE^Dq5;b1r1@i_RzUDa*=s}5gxVrv@i z{xkzU+0g1eJH@Z4zY@Tsl9HNvxJwD#4Gvu5GJn5C;7-8TzYF1ZQ@ExT>YqwtECD<_ z3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w569N3O%=I^6&UL5gZ!}trC7bUj*12xL zdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl z=zristl>%lm~W^QT*&|@bP*#NfWzE45UJ)$0a(>si2)~Q%P)C=Rn3(caDq1ubeV3VgZ^8qPyK~v^_Zk+=-kgDd;`6WAn1;y#h+Ix-^t&lo@{M6% z5Y4g#2h6|!WifW{KKw9{>sOIW9n}SJDWw`z{id}2;aRmUD^H3h<3~QJkAH30&VP!d zb9uNyzCY4XuH2f`eSU+;ml-t2`@%_)0jDM`+a z46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs6w1p~$bp;6!9DX$M}Gy7KkyEB zoFH%`1zZd=X@(}X`r}*%*H6fCoeISkup2S~d;2H;Id?$F5~t-@0p^!AiEF#Eg>~Ai zbNzrUqH|*fn1}3s?1yu`Ko-@xu>;J&S^+t?9LOR&H^*H^ea zg}b2b1RvS1y|c;r=knr2!S zSeB&#hn1qq4V=k3^|5RGg-qa%d=KF0hg+ax{DYv4Ujo&O*V%wYhkq z&dmz2Adqu#|J@AQn0rCLssZ$xr%;VHU;=ssgWMOa0k#jN+`RyUVUC;#Dg|~oS^$6%wpF{^Qr+}X> z0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx z?_|q{d`t<*XEb#=aOb=N+L@C{TZS2Y9%Ii`yt-G2md2OAG5oop6?JUw{G5pOlJ zlWr$~|40AP2a-lqPL6P^k(qTJ0X$!Jz$Kj=cukJ1yJjpOCxFMwT7#p69Pw5oJJD1E z_)yfZ3UUCDrx!L}j;yfYcA}2dNZ^D2(qCjD0nx7}<2<>tesu`lwR7Hp8TV0^O&SYg zw@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSAf3Ih7b<{zqpDB4W zF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c52HucO=lrNVae5X zWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?0?&ZI+il6U@oqt$2*hgV#(b50MC@K z!xg9zsLzwuaLLe4PzQg9I+(K5H9j~OkD^b=CShJILxtho`}cnCbwOqg*FE$Jh8n-v z2V=C5XIL^g$S*0#!oXd_*!4@zMF5j}T!|WvdPhNyJ=`|Bj#05+xE$mP3Yrw`X6^-5 zO@EFb+`||%Z79K>fX4$+FLT|Rsm3?bZkf$))(``h1=kIkKEB^I=TdHtrc)-ds2HPi zA?uu*$=>mo3Up*(x1p-|GOGC%+&uX&WVHmfhdXf}%=BvlK$AO%L*vl`<0zB@RA5o*)q7V zShr))))rzUcaWN_sYJ8pNfIsPVI4R~Sy*AlLv+mM&=uZNx~zNBFhr`o^g}ixCpD z2ea4HwslY0MBcZYRSa1!?&K+L5BtiA>5>#%ZE=RCHVNn3C7erzI}$F^_Ef7R?<1`j zfcNvQs;m*pIE9lMy~rTfwJ^hLCu5lUV-&?lqnJ@|oqr%b{<%XDEIJcMdxC)hMA{+m zBpwt1yh^}Hjb75onoF{#k*xBDC`yl6r?N4Mf+JCU{Ck|_+e`y@Ghc1?%pv`Y%Zemr zO|MVNnsv-N74-NQNY)aPm9Q@h{7QhDKY%8uB#bA^o~-;XSxp&}rQO9J0)SSt{^h1* z<(nkSW`BhRlQW~MWSI{``z4^~a+NH@WO+@Po08S5FgG=LLs?!E=Eh_h3L8vTD#Bcp ztRCiB-V&D4VQ4JNJHj&Fvm_%~9uejxS+X!U$jY}Q%jLqnmMpb|!Kp)v!Ukn!Q5e6i z?2ZfRw1f?Sn}3-VHV|*o*b?TJWHCErQkc_|<$swW*&nxPnC7`|L>X?yQsXAk8fR#U zFmxueCX63QOP?i+zaSwIc>lM!{9a?#@hE0DTIVHte96%$7M_a90vSN`voNM`xszee zi5OH-C!BNz2sm7SbJxXYWYzt6n1ibA2T%E+2G- zvVZ?lGx&4JA1zq$a}2i?SrT)=ki68vj zEt9e;(v&q{Co9Xs^wO9tt(Z$QnP(Zxib{G}_LJopVdhEW$ugJKp6EJ>a9J~L%W~*U zU!OECtz36M%UD)N0&>G1zCDCz>LMs@aDS#O6JfTJCZ9bEEXiWBtOs_4Fs?R?(YwNU za({$`r`xj}I@9k}S_hMd)Q2<-<0i>cWj(ehj7N5d@hFkYrKOlG2VWYQ&C{-i7($|X zvbZdcPMRfI4!@Q(pQojgERIgv0WNK(nJl*olSS#onJ0^5oV2oI5fmPY^d!sO!hek9 zwZ$1FL?_FVw8{HJC}~V!!7p*#GM42XVd^(A=AMk=-fbZ~UK>Upmtrn$S0{4!iI}|o zAGAgi0F|h>LiT*`fvD)mf@8Z2b33qfq6Ay z+TrtStAz2jmZXua4wA(%YRg{;1b>&oKWi<7sjKkv%0tP0N({`4Jxr}c3evc&D+m-V zg;25r{)%c0nOzN`aOMA{dEqQ+M_S$wBdQY7C|Cqx+Gp^Wt%I-Z3;4=6VCd|P zNolJz(%d``vmuSk+I$g#DQ`n~elh$dpTjqEl_G4tENzI9G&j!l?MNH5i+^MtB3YX* zATY5U!lc*XFI)m&@pAY|*UF+eJtWOd*OIB0sFk)Sgxoq{bWIa-#uq^tR|yG#bB=~HS8#XN zS+IN)0wlp#rb||@NztH8Z&9udzxKq+|5-1z*dKRL0&Jd%C@x3Tt;y>y9EFXvi}8B^P~-}+9->& z%3WQ5H*GcMtU1hM!fXqBR{-!35z5}MG?#V{9Xwq_LYT1z!t~YFDSuuIVahTTeYzKS z!trF%Z1gW8PhDAH2VXwLDaCK$oNaE!xAm9s{qf8At}!*Xp16X|2hXGVb|~GHiHe(j zf?5Ecb?QPN63cSvxsw7_mS5TKb+WXCNJ#0!m@QdHR9Es3!bjv1VM281$4mRWnBpQv zjP1VmPL8sjgJi`ArGFu=Z0B)D#tW`}GQigmV;Wh@xf^|HleUzAQ+rw;7H>yfRu174 zsLZ6jL4?&EdAzrY3Q&61TgI}?rHv;JKvOPXlGJEvQf(b3o=_dwS@^bys3oHCUd5D9 z1dWb8RcU@TlA6SQK

3xwOBDL<>{lx00000NkvXX Hu0mjfkXLLo delta 1874 zcmV-Y2d((04&V-uBYy`cNklx~L`~4d z)Rspd&<9kFh{hn*KP1LP0~$;u(LfAup%fx&qLBcRHx$G|3q(bv@+b;o0*D|jwD-Q9 zuQR(l*ST}s+uPgQ-MeFwZ#GS?b332?&Tk$&_miXn3IGq)Ab;WSyQ!vAt)@Ccb&;xv z%AX@*_j-xyC{+_x1Jy90eS@gHl9`eOYAn?ns>{f~a)oLg)i}Mj8-?Z5uJ>q_5#bAD zs+e{qIS7a(wLvT!sO}RPqyuZB*5&w@L~|675r8qFZOn01hM)stdt z(}AUD*+MA1yMMAWR~H%&a`aj*kR~YHLIk%}CkzB^7}YeC>Z%YF3%$siy4gUWO|iV9 zyS{R3%u=2p$xI}(n6@d|Wn+c7k}2 z((AD!&VxpxR^A?&MN{;FY6cOn>|#gvx^pnz;el1jsvqQz}pGSF<`mq zEXRQ5sC4#BbwnB_4`c5bFE-Gb#JV3tox9fp-vVEN{(tOCpR zse`S+@)?%pz+zVJXSooTrNCUg`R6`hxwb{)D1R`!oJBH}D}dSIti0)xc5KlQ;k&Me z2>uPHr)yMQ5rWI~9($t>0^<$2oJD8x2jY(b@BSK5-ZeHH4q-;SalJr4Uv)5qxyQl^ zKr6bZWCj+~xn6>qN5aY*W~-eg!g}X;SotjbKlUcusA}8P7rC$h7*?ESRri+6cEyu7 zWPjzUS7|qw9as^i(e+yWM&5Yy?_fs@8h;Aox&})oF6feHdx&y#6|KggrRyvPD_!^2 zWf4|JL7&g++36T|jv*B^GqCDS!kUvMSQ$?%`k)a7T4g`bQq8k?qe61#uo|-n%aVy} zDyOB|R_2Xj$C20|!eLEyAuP+5M#)a&(0}Su>}T;t_0TyI3)n+9`|(su5H)S?gk|aC zJK1S$^ei(3izu+}-QvfnodFEr7Qo|sf`$eWJ7ShC+dG|f^kiM?ecBBp2AsuUJ+dQ! zhqedsFjcVuXm&d*>l=s{{6rb?j5v$K;z|Y0DOg$CqV8|;Jdm@J6^FLB5870RVSi=y zB?##U+(6Fi7nno`C<|#PVA%$Y z{}N-?(Gc$1%tr z4Pc}}hm~yY#fTOe!@v9s-ik$dX@ALH?I94Q-n;}RcAsCmWQ^Wv3|h(79tw+~Xv3fI zRWE{f+Db?hm*K^w`_(#S`=Qx-+$ov{v_4qp;j35(scH$l^-UN&qX|;^Qpcd#exS|> zw36>AtbIVy=YPOgHV@LIPvEUx1yAi~@YJorZ8O%Y(3aT`&5r3f6-*VjG1;+h4Tq418njAdPz!-tq zpPUVE>3kJZeKXbCeqnLh*?&D5Fvowz7S-k)Z70s-4mJwbt%Hw3@YJqO3G07OOBg*# zR)F2qStmOO?d(4o(m;x0Wlqr5?09S>x#%Qkt>?(&XjFRY}*L6BeQ3<6XEBh^S7>AbwGm@XP{R zkeEKj6@_o%oV?hDuUpUJ+r#JZO?!IUc;r0R?>mi)*ZpQ)#((dn=A#i+|E(0h@Z5l}YZL#eq00sELYLBA#NZ01}gr6>{OJDZ*4ElXLIz zghey84@Yi6Qy8!^TQ@Hv`Vy{v#0RPZg+>Hei&JH(<4#+s(~k?Gh~a3lkgC*^G94+s zzNW=AUQs^BwwPcH7PH