diff --git a/packages/hms_room_kit/CHANGELOG.md b/packages/hms_room_kit/CHANGELOG.md index 65fe7a865..e66819d5f 100644 --- a/packages/hms_room_kit/CHANGELOG.md +++ b/packages/hms_room_kit/CHANGELOG.md @@ -5,6 +5,21 @@ | hms_room_kit | [![Pub Version](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) | | hmssdk_flutter | [![Pub Version](https://img.shields.io/pub/v/hmssdk_flutter)](https://pub.dev/packages/hmssdk_flutter) | +## 1.0.12 - 2024-02-12 + +| Package | Version | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| hms_room_kit | 1.0.12 | +| hmssdk_flutter | 1.9.9 | + +### 🚀 Added + +- Introducing polls on prebuilt + + Users can now create, manage, and stop polls directly from the prebuilt interface. + +Updated `hmssdk_flutter` package version to 1.9.9 + ## 1.0.11 - 2024-02-01 | Package | Version | diff --git a/packages/hms_room_kit/example/ios/Flutter/AppFrameworkInfo.plist b/packages/hms_room_kit/example/ios/Flutter/AppFrameworkInfo.plist index 9625e105d..7c5696400 100644 --- a/packages/hms_room_kit/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/hms_room_kit/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/packages/hms_room_kit/example/ios/Podfile.lock b/packages/hms_room_kit/example/ios/Podfile.lock index 78aee02dc..50232f590 100644 --- a/packages/hms_room_kit/example/ios/Podfile.lock +++ b/packages/hms_room_kit/example/ios/Podfile.lock @@ -6,14 +6,14 @@ PODS: - HMSBroadcastExtensionSDK (0.0.9) - HMSHLSPlayerSDK (0.0.2): - HMSAnalyticsSDK (= 0.0.2) - - HMSSDK (1.4.1): + - HMSSDK (1.5.0): - HMSAnalyticsSDK (= 0.0.2) - HMSWebRTC (= 1.0.5116) - hmssdk_flutter (1.9.6): - Flutter - HMSBroadcastExtensionSDK (= 0.0.9) - HMSHLSPlayerSDK (= 0.0.2) - - HMSSDK (= 1.4.1) + - HMSSDK (= 1.5.0) - HMSWebRTC (1.0.5116) - path_provider_foundation (0.0.1): - Flutter @@ -65,20 +65,20 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817 HMSAnalyticsSDK: 4d2a88a729b1eb42f3d25f217c28937ec318a5b7 HMSBroadcastExtensionSDK: d80fe325f6c928bd8e5176290b5a4b7ae15d6fbb HMSHLSPlayerSDK: 6a54ad4d12f3dc2270d1ecd24019d71282a4f6a3 - HMSSDK: 6a579cb806d4760cda149002150ff0beab03749b - hmssdk_flutter: 0b12e79b2f184fa3308fb65dade6e3b022d805c9 + HMSSDK: 0d1901d64faf2661d1183c1ba2881e2531a5eeba + hmssdk_flutter: 9f3b16d9bfc1e9a2ccd63f5d9b6a6d51669ed5ac HMSWebRTC: ae54e9dd91b869051b283b43b14f57d43b7bf8e1 - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.0 diff --git a/packages/hms_room_kit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/hms_room_kit/example/ios/Runner.xcodeproj/project.pbxproj index 16761b9f3..71331a3a9 100644 --- a/packages/hms_room_kit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/hms_room_kit/example/ios/Runner.xcodeproj/project.pbxproj @@ -342,7 +342,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -422,7 +422,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -471,7 +471,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/hms_room_kit/example/pubspec.lock b/packages/hms_room_kit/example/pubspec.lock index c47272d6d..7f0a5092c 100644 --- a/packages/hms_room_kit/example/pubspec.lock +++ b/packages/hms_room_kit/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: archive - sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.4.9" + version: "3.4.10" args: dependency: transitive description: @@ -214,15 +214,15 @@ packages: path: ".." relative: true source: path - version: "1.0.11" + version: "1.0.12" hmssdk_flutter: dependency: transitive description: name: hmssdk_flutter - sha256: ff1697824b42d31cb093fd4319c8fa3ba6872b7877707630062ed3cd9cf40813 + sha256: bad4ff87c677970f0c9acfcd4e84b60089461e1c1eef23e3a3ae6ab191484b95 url: "https://pub.dev" source: hosted - version: "1.9.8" + version: "1.9.9" http: dependency: transitive description: @@ -339,10 +339,10 @@ packages: dependency: transitive description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_android: dependency: transitive description: @@ -355,10 +355,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -371,10 +371,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -435,26 +435,26 @@ packages: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" provider: dependency: transitive description: @@ -499,10 +499,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: @@ -515,10 +515,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_web: dependency: transitive description: @@ -616,26 +616,26 @@ packages: dependency: transitive description: name: url_launcher - sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.2.4" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: @@ -656,10 +656,10 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" url_launcher_web: dependency: transitive description: @@ -688,26 +688,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_math: dependency: transitive description: @@ -744,10 +744,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: diff --git a/packages/hms_room_kit/example/pubspec.yaml b/packages/hms_room_kit/example/pubspec.yaml index f915efd52..34b4c4d6c 100644 --- a/packages/hms_room_kit/example/pubspec.yaml +++ b/packages/hms_room_kit/example/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.11 +version: 1.0.12 environment: sdk: ">=2.19.6 <3.0.0" diff --git a/packages/hms_room_kit/lib/src/assets/icons/add_option.svg b/packages/hms_room_kit/lib/src/assets/icons/add_option.svg new file mode 100644 index 000000000..b6926ba15 --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/add_option.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/hms_room_kit/lib/src/assets/icons/delete_poll.svg b/packages/hms_room_kit/lib/src/assets/icons/delete_poll.svg new file mode 100644 index 000000000..94993e03d --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/delete_poll.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/hms_room_kit/lib/src/assets/icons/poll.svg b/packages/hms_room_kit/lib/src/assets/icons/poll.svg new file mode 100644 index 000000000..15c251898 --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/poll.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/hms_room_kit/lib/src/assets/icons/polls.svg b/packages/hms_room_kit/lib/src/assets/icons/polls.svg new file mode 100644 index 000000000..51b0dc22c --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/polls.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/hms_room_kit/lib/src/assets/icons/quiz.svg b/packages/hms_room_kit/lib/src/assets/icons/quiz.svg new file mode 100644 index 000000000..d6f23aaa0 --- /dev/null +++ b/packages/hms_room_kit/lib/src/assets/icons/quiz.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/hms_room_kit/lib/src/common/utility_components.dart b/packages/hms_room_kit/lib/src/common/utility_components.dart index 0c0ac98e3..7bc5d99ad 100644 --- a/packages/hms_room_kit/lib/src/common/utility_components.dart +++ b/packages/hms_room_kit/lib/src/common/utility_components.dart @@ -37,7 +37,7 @@ class UtilityComponents { ), context: context, builder: (ctx) => ChangeNotifierProvider.value( - value: context.read(), + value: meetingStore, child: LeaveSessionBottomSheet( meetingStore: meetingStore, )), diff --git a/packages/hms_room_kit/lib/src/common/utility_functions.dart b/packages/hms_room_kit/lib/src/common/utility_functions.dart index 8d46d51d4..b7a2d8512 100644 --- a/packages/hms_room_kit/lib/src/common/utility_functions.dart +++ b/packages/hms_room_kit/lib/src/common/utility_functions.dart @@ -5,11 +5,13 @@ import 'dart:math' as math; ///Package imports import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; -import 'package:hms_room_kit/hms_room_kit.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; +///Project imports +import 'package:hms_room_kit/hms_room_kit.dart'; + ///This class contains the utility functions used in the app class Utilities { static RegExp regexEmoji = RegExp( @@ -119,6 +121,25 @@ class Utilities { } } + ///This method returns the scale of the toast according to the index and the total number of toasts + static double getToastScale(int index, int toastsCount) { + if (toastsCount == 1) { + return 1; + } else if (toastsCount == 2) { + if (index == 0) { + return 0.95; + } + return 1; + } else { + if (index == 0) { + return 0.90; + } else if (index == 1) { + return 0.95; + } + return 1; + } + } + ///This contains the list of toasts possible colors static final List _toastColors = [ HMSThemeColors.surfaceDim, @@ -269,6 +290,17 @@ class Utilities { // duration: Duration(seconds: time)); } + static String getQuestionType(HMSPollQuestionType questionType) { + switch (questionType) { + case HMSPollQuestionType.singleChoice: + return "SINGLE CHOICE"; + case HMSPollQuestionType.multiChoice: + return "MULTI CHOICE"; + default: + return "SINGLE CHOICE"; + } + } + static Future getStringData({required String key}) async { final prefs = await SharedPreferences.getInstance(); 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 5b9831978..364b42c10 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 @@ -13,7 +13,7 @@ import 'package:hms_room_kit/src/widgets/bottom_sheets/chat_only_bottom_sheet.da 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_more_options.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'; @@ -242,7 +242,7 @@ class HLSViewerBottomNavigationBar extends StatelessWidget { value: context.read(), child: - const HLSMoreOptionsBottomSheet()), + const HLSAppUtilitiesBottomSheet()), ) }, enabledBorderColor: HMSThemeColors 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 0dcd96025..1b2d934b8 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 @@ -15,6 +15,7 @@ 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 { @@ -74,24 +75,7 @@ class HLSViewerHeader extends StatelessWidget { meetingStore.streamingType['rtmp'] == HMSStreamingState.started), builder: (_, isHLSStarted, __) { - return isHLSStarted - ? Container( - height: 24, - width: 43, - decoration: BoxDecoration( - color: HMSThemeColors.alertErrorDefault, - borderRadius: BorderRadius.circular(4)), - child: Center( - child: HMSTitleText( - text: "LIVE", - fontSize: 10, - lineHeight: 16, - letterSpacing: 1.5, - textColor: - HMSThemeColors.alertErrorBrighter), - ), - ) - : Container(); + return isHLSStarted ? const LiveBadge() : Container(); }), const SizedBox( width: 8, 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 c0e66023a..46c8ea8b2 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 @@ -1,11 +1,17 @@ +///Dart imports +import 'dart:math'; + ///Package imports import 'package:flutter/material.dart'; -import 'package:hms_room_kit/src/widgets/common_widgets/hms_left_room_screen.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/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'; @@ -84,209 +90,332 @@ class _HLSViewerPageState extends State { scaffoldBackgroundColor: HMSThemeColors.backgroundDim), child: SingleChildScrollView( - child: Stack( - children: [ - Selector( - selector: (_, meetingStore) => - 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() - .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()); - }), + child: SizedBox( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: Stack( + children: [ + Selector( + selector: (_, meetingStore) => + 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( - selector: (_, hlsPlayerStore) => - hlsPlayerStore - .areStreamControlsVisible, - builder: (_, - areStreamControlsVisible, __) { - return AnimatedContainer( - duration: const Duration( - milliseconds: 200), - height: areStreamControlsVisible - ? 100 - : 0, - child: areStreamControlsVisible - ? const HLSViewerHeader() - : Container(), - ); - }), - const HLSViewerBottomNavigationBar() - ], + ///Will only be displayed when the controls are visible + SizedBox( + height: + MediaQuery.of(context).size.height, + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore + .areStreamControlsVisible, + builder: (_, + areStreamControlsVisible, + __) { + return AnimatedContainer( + duration: const Duration( + milliseconds: 200), + height: + areStreamControlsVisible + ? 100 + : 0, + child: areStreamControlsVisible + ? const HLSViewerHeader() + : Container(), + ); + }), + const HLSViewerBottomNavigationBar() + ], + ), ), - ), - ///This renders the preview for role component - Selector< - MeetingStore, - Tuple3< - HMSLocalVideoTrack?, - HMSLocalAudioTrack?, - HMSRoleChangeRequest?>>( - selector: (_, meetingStore) => Tuple3( - meetingStore.previewForRoleVideoTrack, - meetingStore.previewForRoleAudioTrack, - meetingStore - .currentRoleChangeRequest), - builder: (_, previewForRoleTracks, __) { - ///If the preview for role tracks are not null - ///we show the preview for role component - ///else we show and empty Container - if (previewForRoleTracks.item1 != null || - previewForRoleTracks.item2 != - null || - previewForRoleTracks.item3 != - null) { - WidgetsBinding.instance - .addPostFrameCallback( - (timeStamp) { - ///For preview for role component we use the [showGeneralDialog] - showGeneralDialog( - context: context, - pageBuilder: (ctx, _, __) { - return ListenableProvider - .value( - value: context - .read(), - child: Scaffold( - body: SafeArea( - child: Container( - color: HMSThemeColors - .backgroundDim, - height: MediaQuery.of( - context) - .size - .height, - width: MediaQuery.of( - context) - .size - .width, + ///This renders the preview for role component + Selector< + MeetingStore, + Tuple3< + HMSLocalVideoTrack?, + HMSLocalAudioTrack?, + HMSRoleChangeRequest?>>( + selector: (_, meetingStore) => Tuple3( + meetingStore + .previewForRoleVideoTrack, + meetingStore + .previewForRoleAudioTrack, + meetingStore + .currentRoleChangeRequest), + builder: (_, previewForRoleTracks, __) { + ///If the preview for role tracks are not null + ///we show the preview for role component + ///else we show and empty Container + if (previewForRoleTracks.item1 != null || + previewForRoleTracks.item2 != + null || + previewForRoleTracks.item3 != + null) { + WidgetsBinding.instance + .addPostFrameCallback( + (timeStamp) { + ///For preview for role component we use the [showGeneralDialog] + showGeneralDialog( + context: context, + pageBuilder: (ctx, _, __) { + return ListenableProvider + .value( + value: context + .read(), + child: Scaffold( + body: SafeArea( + child: Container( + color: HMSThemeColors + .backgroundDim, + height: + MediaQuery.of( + context) + .size + .height, + width: + MediaQuery.of( + context) + .size + .width, - ///We render the preview for role component - child: Stack( - children: [ - ///This renders the video component - ///[HMSVideoView] is only rendered if video is ON - /// - ///else we render the [HMSCircularAvatar] - Selector< - MeetingStore, - bool>( - selector: (_, - meetingStore) => - meetingStore - .isVideoOn, - builder: (_, - isVideoOn, - __) { - return Container( - height: MediaQuery.of( - context) - .size - .height, - width: MediaQuery.of( - context) - .size - .width, - color: HMSThemeColors - .backgroundDim, - child: (isVideoOn && - previewForRoleTracks.item1 != null) - ? Center( - child: HMSVideoView( - scaleType: ScaleType.SCALE_ASPECT_FILL, - track: previewForRoleTracks.item1!, - setMirror: true, + ///We render the preview for role component + child: Stack( + children: [ + ///This renders the video component + ///[HMSVideoView] is only rendered if video is ON + /// + ///else we render the [HMSCircularAvatar] + Selector< + MeetingStore, + bool>( + selector: (_, + meetingStore) => + meetingStore + .isVideoOn, + builder: (_, + isVideoOn, + __) { + return Container( + height: MediaQuery.of(context) + .size + .height, + width: MediaQuery.of(context) + .size + .width, + color: HMSThemeColors + .backgroundDim, + child: (isVideoOn && + previewForRoleTracks.item1 != null) + ? Center( + child: HMSVideoView( + scaleType: ScaleType.SCALE_ASPECT_FILL, + track: previewForRoleTracks.item1!, + setMirror: true, + ), + ) + : Center( + child: HMSCircularAvatar(name: context.read().localPeer?.name ?? ""), ), - ) - : Center( - child: HMSCircularAvatar(name: context.read().localPeer?.name ?? ""), - ), - ); - }), + ); + }), - ///This renders the preview for role header - const PreviewForRoleHeader(), + ///This renders the preview for role header + const PreviewForRoleHeader(), - ///This renders the preview for role bottom sheet - PreviewForRoleBottomSheet( - meetingStore: - context.read< - MeetingStore>(), - roleChangeRequest: context - .read< - MeetingStore>() - .currentRoleChangeRequest, - ) - ], + ///This renders the preview for role bottom sheet + PreviewForRoleBottomSheet( + meetingStore: + context.read< + MeetingStore>(), + roleChangeRequest: context + .read< + MeetingStore>() + .currentRoleChangeRequest, + ) + ], + ), ), ), ), - ), + ); + }); + }); + } + return Container(); + }), + + Selector( + selector: (_, meetingStore) => + meetingStore.hmsTrackChangeRequest, + builder: + (_, hmsTrackChangeRequest, __) { + if (hmsTrackChangeRequest != null) { + HMSTrackChangeRequest + currentRequest = + hmsTrackChangeRequest; + context + .read() + .hmsTrackChangeRequest = null; + WidgetsBinding.instance + .addPostFrameCallback((_) { + UtilityComponents + .showTrackChangeDialog( + context, currentRequest); + }); + } + return const SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore + .showAudioDeviceChangePopup, + builder: (_, showAudioDeviceChangePopup, + __) { + if (showAudioDeviceChangePopup) { + context + .read() + .showAudioDeviceChangePopup = + false; + WidgetsBinding.instance + .addPostFrameCallback((_) { + showDialog( + context: context, + builder: (_) => + AudioDeviceChangeDialog( + currentAudioDevice: context + .read< + MeetingStore>() + .currentAudioOutputDevice!, + audioDevicesList: context + .read< + MeetingStore>() + .availableAudioOutputDevices, + changeAudioDevice: + (audioDevice) { + context + .read< + MeetingStore>() + .switchAudioOutput( + audioDevice: + audioDevice); + }, + )); + }); + } + return const SizedBox(); + }), + Selector, int>>( + selector: (_, meetingStore) => Tuple2( + meetingStore.toasts, + meetingStore.toasts.length), + builder: (_, toastsItem, __) { + if (toastsItem.item1.isEmpty) { + return Container(); + } + return Stack( + children: toastsItem.item1 + .sublist(0, + min(3, toastsItem.item2)) + .asMap() + .entries + .map((toasts) { + var meetingStore = + context.read(); + return Selector( + selector: (_, hlsPlayerStore) => + hlsPlayerStore + .areStreamControlsVisible, + builder: (_, + areStreamControlsVisible, + __) { + return AnimatedPositioned( + duration: const Duration( + milliseconds: 200), + bottom: + (areStreamControlsVisible + ? 28.0 + : 10.0) + + 8 * toasts.key, + left: 5, + child: ToastWidget( + toast: toasts.value, + index: toasts.key, + toastsCount: + toastsItem.item2, + meetingStore: + meetingStore), ); }); - }); - } - return Container(); - }), - Selector( - selector: (_, meetingStore) => - meetingStore.reconnecting, - builder: (_, reconnecting, __) { - if (reconnecting) { - return UtilityComponents - .showReconnectingDialog(context); - } - return const SizedBox(); - }), - if (failureData.item2 != null && - (failureData.item2?.code?.errorCode == - 1003 || - failureData.item2?.code?.errorCode == - 2000 || - failureData.item2?.code?.errorCode == - 4005)) - UtilityComponents.showFailureError( - failureData.item2!, - context, - () => context - .read() - .leave()) - ], + }).toList()); + }), + + Selector( + selector: (_, meetingStore) => + meetingStore.reconnecting, + builder: (_, reconnecting, __) { + if (reconnecting) { + return UtilityComponents + .showReconnectingDialog( + context); + } + return const SizedBox(); + }), + if (failureData.item2 != null && + (failureData.item2?.code?.errorCode == + 1003 || + failureData + .item2?.code?.errorCode == + 2000 || + failureData + .item2?.code?.errorCode == + 4005)) + UtilityComponents.showFailureError( + failureData.item2!, + context, + () => context + .read() + .leave()) + ], + ), ), ), ), diff --git a/packages/hms_room_kit/lib/src/hls_viewer/overlay_chat_component.dart b/packages/hms_room_kit/lib/src/hls_viewer/overlay_chat_component.dart index b051cb8c9..9eca6a19c 100644 --- a/packages/hms_room_kit/lib/src/hls_viewer/overlay_chat_component.dart +++ b/packages/hms_room_kit/lib/src/hls_viewer/overlay_chat_component.dart @@ -223,6 +223,8 @@ class _OverlayChatComponentState extends State { ), GestureDetector( onTap: () { + var meetingStore = + context.read(); showModalBottomSheet( isScrollControlled: true, backgroundColor: @@ -235,8 +237,7 @@ class _OverlayChatComponentState extends State { context: context, builder: (ctx) => ChangeNotifierProvider.value( - value: context - .read(), + value: meetingStore, child: ChatUtilitiesBottomSheet( message: data.item1[index], diff --git a/packages/hms_room_kit/lib/src/hmssdk_interactor.dart b/packages/hms_room_kit/lib/src/hmssdk_interactor.dart index 558007313..8e22d78d4 100644 --- a/packages/hms_room_kit/lib/src/hmssdk_interactor.dart +++ b/packages/hms_room_kit/lib/src/hmssdk_interactor.dart @@ -413,4 +413,40 @@ class HMSSDKInteractor { hmsSDK.lowerRemotePeerHand( forPeer: forPeer, hmsActionResultListener: hmsActionResultListener); } + + void quickStartPoll( + {required HMSPollBuilder pollBuilder, + HMSActionResultListener? hmsActionResultListener}) { + HMSPollInteractivityCenter.quickStartPoll( + pollBuilder: pollBuilder, + hmsActionResultListener: hmsActionResultListener); + } + + Future addSingleChoicePollResponse( + {required HMSPoll poll, + required HMSPollQuestion question, + required HMSPollQuestionOption pollQuestionOption, + HMSPeer? peer}) { + return HMSPollInteractivityCenter.addSingleChoicePollResponse( + hmsPoll: poll, + pollQuestion: question, + optionSelected: pollQuestionOption, + peer: peer); + } + + Future addMultiChoicePollResponse( + {required HMSPoll poll, + required HMSPollQuestion question, + required List pollQuestionOption, + HMSPeer? peer}) { + return HMSPollInteractivityCenter.addMultiChoicePollResponse( + hmsPoll: poll, + pollQuestion: question, + optionsSelected: pollQuestionOption, + peer: peer); + } + + Future stopPoll({required HMSPoll poll}) { + return HMSPollInteractivityCenter.stopPoll(poll: poll); + } } diff --git a/packages/hms_room_kit/lib/src/meeting/meeting_header.dart b/packages/hms_room_kit/lib/src/meeting/meeting_header.dart index 49b4fc786..66bc25957 100644 --- a/packages/hms_room_kit/lib/src/meeting/meeting_header.dart +++ b/packages/hms_room_kit/lib/src/meeting/meeting_header.dart @@ -4,12 +4,13 @@ import 'dart:developer'; ///Package imports import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:hms_room_kit/src/meeting/meeting_navigation_visibility_controller.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/meeting/meeting_navigation_visibility_controller.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/live_badge.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'; @@ -89,24 +90,7 @@ class _MeetingHeaderState extends State { HMSStreamingState.started), builder: (_, isHLSStarted, __) { return isHLSStarted - ? Container( - height: 24, - width: 43, - decoration: BoxDecoration( - color: HMSThemeColors - .alertErrorDefault, - borderRadius: - BorderRadius.circular(4)), - child: Center( - child: HMSTitleText( - text: "LIVE", - fontSize: 10, - lineHeight: 16, - letterSpacing: 1.5, - textColor: HMSThemeColors - .alertErrorBrighter), - ), - ) + ? const LiveBadge() : Container(); }), const SizedBox( diff --git a/packages/hms_room_kit/lib/src/meeting/meeting_page.dart b/packages/hms_room_kit/lib/src/meeting/meeting_page.dart index 2410aa88e..fd27b8d46 100644 --- a/packages/hms_room_kit/lib/src/meeting/meeting_page.dart +++ b/packages/hms_room_kit/lib/src/meeting/meeting_page.dart @@ -5,6 +5,7 @@ import 'dart:math'; ///Package imports import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.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'; @@ -15,11 +16,7 @@ import 'package:hms_room_kit/src/meeting/meeting_grid_component.dart'; import 'package:hms_room_kit/src/meeting/meeting_navigation_visibility_controller.dart'; import 'package:hms_room_kit/src/meeting/meeting_bottom_navigation_bar.dart'; import 'package:hms_room_kit/src/meeting/meeting_header.dart'; -import 'package:hms_room_kit/src/widgets/toasts/hms_bring_on_stage_toast.dart'; -import 'package:hms_room_kit/src/widgets/toasts/hms_local_screen_share_toast.dart'; -import 'package:hms_room_kit/src/widgets/toasts/hms_role_change_decline_toast.dart'; import 'package:hms_room_kit/src/widgets/toasts/hms_toast_model.dart'; -import 'package:hms_room_kit/src/widgets/toasts/hms_toasts_type.dart'; import 'package:hms_room_kit/src/common/utility_components.dart'; import 'package:hms_room_kit/src/widgets/app_dialogs/audio_device_change_dialog.dart'; import 'package:hms_room_kit/src/meeting/meeting_store.dart'; @@ -28,8 +25,6 @@ import 'package:hms_room_kit/src/preview_for_role/preview_for_role_bottom_sheet. 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/widgets/common_widgets/hms_left_room_screen.dart'; -import 'package:hms_room_kit/src/widgets/toasts/hms_recording_error_toast.dart'; -import 'package:hms_room_kit/src/widgets/toasts/hms_chat_pause_resume_toast.dart'; ///[MeetingPage] is the main page of the meeting ///It takes the following parameters: @@ -77,60 +72,6 @@ class _MeetingPageState extends State { context.read().initForegroundTask(); } - ///This method returns the toast according to the type of toast - Widget getToast(HMSToastModel toast, int index, int toastsCount) { - switch (toast.hmsToastType) { - case HMSToastsType.roleChangeToast: - return HMSBringOnStageToast( - toastColor: Utilities.getToastColor(index, toastsCount), - peer: toast.toastData, - meetingStore: context.read(), - ); - case HMSToastsType.recordingErrorToast: - return HMSRecordingErrorToast( - recordingError: toast.toastData, - meetingStore: context.read()); - case HMSToastsType.localScreenshareToast: - return HMSLocalScreenShareToast( - toastColor: Utilities.getToastColor(index, toastsCount), - meetingStore: context.read(), - ); - - case HMSToastsType.roleChangeDeclineToast: - return HMSRoleChangeDeclineToast( - peer: toast.toastData, - toastColor: Utilities.getToastColor(index, toastsCount), - meetingStore: context.read(), - ); - case HMSToastsType.chatPauseResumeToast: - return HMSChatPauseResumeToast( - isChatEnabled: toast.toastData["enabled"], - userName: toast.toastData["updatedBy"], - meetingStore: context.read()); - default: - return const SizedBox(); - } - } - - ///This method returns the scale of the toast according to the index and the total number of toasts - double _getToastScale(int index, int toastsCount) { - if (toastsCount == 1) { - return 1; - } else if (toastsCount == 2) { - if (index == 0) { - return 0.95; - } - return 1; - } else { - if (index == 0) { - return 0.90; - } else if (index == 1) { - return 0.95; - } - return 1; - } - } - @override Widget build(BuildContext context) { return WillPopScope( @@ -419,19 +360,20 @@ class _MeetingPageState extends State { .asMap() .entries .map((toasts) { - return Positioned( - bottom: - 48.0 + 8 * toasts.key, - left: 5, - child: Transform.scale( - scale: _getToastScale( - toasts.key, - toastsItem.item2), - child: getToast( - toasts.value, - toasts.key, - toastsItem.item2), - )); + var meetingStore = context + .read(); + return ChangeNotifierProvider + .value( + value: + _visibilityController, + child: ToastWidget( + toast: toasts.value, + index: toasts.key, + toastsCount: + toastsItem.item2, + meetingStore: + meetingStore), + ); }).toList()); }), Selector( 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 2852ca961..ccd9c7ddc 100644 --- a/packages/hms_room_kit/lib/src/meeting/meeting_store.dart +++ b/packages/hms_room_kit/lib/src/meeting/meeting_store.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; -import 'dart:math' as math; //Package imports import 'package:hmssdk_flutter/hmssdk_flutter.dart'; @@ -12,6 +11,7 @@ import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:intl/intl.dart'; //Project imports +import 'package:hms_room_kit/src/model/poll_store.dart'; import 'package:hms_room_kit/src/model/participant_store.dart'; import 'package:hms_room_kit/hms_room_kit.dart'; import 'package:hms_room_kit/src/enums/meeting_mode.dart'; @@ -34,7 +34,8 @@ class MeetingStore extends ChangeNotifier HMSStatsListener, HMSLogListener, HMSKeyChangeListener, - HMSHLSPlaybackEventsListener { + HMSHLSPlaybackEventsListener, + HMSPollListener { late HMSSDKInteractor _hmsSDKInteractor; MeetingStore({required HMSSDKInteractor hmsSDKInteractor}) { @@ -250,6 +251,15 @@ class MeetingStore extends ChangeNotifier ///Stores whether Chat State Map chatControls = {"enabled": true, "updatedBy": null}; + ///Polls + ///Stores the poll questions + List pollQuestions = []; + + List hlsViewerPolls = []; + + ///List of bottom sheets currently open + List bottomSheets = []; + Future join(String userName, String roomCode, {HMSConfig? roomConfig}) async { //If roomConfig is null then only we call the methods to get the authToken @@ -281,6 +291,7 @@ class MeetingStore extends ChangeNotifier _hmsSDKInteractor.addUpdateListener(this); _hmsSDKInteractor.addLogsListener(this); + HMSPollInteractivityCenter.addPollUpdateListener(listener: this); HMSHLSPlayerController.addHMSHLSPlaybackEventsListener(this); WidgetsBinding.instance.addObserver(this); setMeetingModeUsingLayoutApi(); @@ -454,6 +465,10 @@ class MeetingStore extends ChangeNotifier case HMSToastsType.errorToast: toasts.removeWhere( (toast) => toast.hmsToastType == HMSToastsType.errorToast); + case HMSToastsType.pollStartedToast: + toasts.removeWhere((toast) => + (toast.hmsToastType == HMSToastsType.pollStartedToast) && + (toast.toastData.poll.pollId == data)); } notifyListeners(); } @@ -1365,6 +1380,26 @@ class MeetingStore extends ChangeNotifier } } + ///Function to add bottomsheet context in the bottomsheets list + void addBottomSheet(BuildContext context) { + bottomSheets.add(context); + } + + ///Function to remove bottomsheet context from bottomsheets list + void removeBottomSheet(BuildContext context) { + bottomSheets.remove(context); + } + + ///Function to remove all bottomsheets from list + void removeAllBottomSheets() { + for (var bottomSheetContext in bottomSheets) { + if (bottomSheetContext.mounted) { + Navigator.pop(bottomSheetContext); + } + } + bottomSheets.clear(); + } + void removePeer(HMSPeer peer) { peers.remove(peer); participantsInMeetingMap[peer.role.name] @@ -1506,6 +1541,7 @@ class MeetingStore extends ChangeNotifier case HMSPeerUpdate.roleUpdated: if (peer.isLocal) { + removeAllBottomSheets(); getSpotlightPeer(); setPreviousRole(localPeer?.role.name ?? ""); resetLayout(peer.role.name); @@ -2198,6 +2234,37 @@ class MeetingStore extends ChangeNotifier notifyListeners(); } + ///Polls and Quiz + + ///Method to start poll + void quickStartPoll(HMSPollBuilder pollBuilder) { + _hmsSDKInteractor.quickStartPoll( + pollBuilder: pollBuilder, hmsActionResultListener: this); + } + + ///Method to add Poll Response + void addSingleChoicePollResponse(HMSPoll poll, HMSPollQuestion question, + HMSPollQuestionOption pollQuestionOption) { + _hmsSDKInteractor.addSingleChoicePollResponse( + poll: poll, + question: question, + pollQuestionOption: pollQuestionOption, + peer: localPeer); + } + + void addMultiChoicePollResponse(HMSPoll poll, HMSPollQuestion question, + List pollQuestionOption) { + _hmsSDKInteractor.addMultiChoicePollResponse( + poll: poll, + question: question, + pollQuestionOption: pollQuestionOption, + peer: localPeer); + } + + void stopPoll(HMSPoll poll) { + _hmsSDKInteractor.stopPoll(poll: poll); + } + //Get onSuccess or onException callbacks for HMSActionResultListenerMethod @override void onSuccess( @@ -2330,6 +2397,10 @@ class MeetingStore extends ChangeNotifier break; case HMSActionResultListenerMethod.lowerRemotePeerHand: break; + case HMSActionResultListenerMethod.addSingleChoicePollResponse: + break; + case HMSActionResultListenerMethod.addMultiChoicePollResponse: + break; default: log("ActionResultListener onException-> method: ${methodType.toString()}Could not find a valid case while switching"); break; @@ -2415,6 +2486,10 @@ class MeetingStore extends ChangeNotifier break; case HMSActionResultListenerMethod.lowerRemotePeerHand: break; + case HMSActionResultListenerMethod.addSingleChoicePollResponse: + break; + case HMSActionResultListenerMethod.addMultiChoicePollResponse: + break; default: log("ActionResultListener onException-> method: ${methodType.toString()} Could not find a valid case while switching"); break; @@ -2499,12 +2574,32 @@ class MeetingStore extends ChangeNotifier @override void onCue({required HMSHLSCue hlsCue}) { + log("onCue -> payload:${hlsCue.startDate}"); /** * Here we use a list of alignments and select an alignment at random and use it * to position the toast for timed metadata */ - if (hlsCue.payload != null) { + /* + * Below code shows the poll for hls-viewer who are viewing stream at a delay. + * Here we get pollId from the payload and we find the poll object + * from `onPollUpdate` we use this object to show the toast for the poll. + * Mock payload for poll looks like "poll:{poll_id}" + */ + if (hlsCue.payload!.startsWith("poll:")) { + var pollId = hlsCue.payload?.replaceFirst(RegExp(r'^poll:'), ""); + int? index = hlsViewerPolls + .indexWhere((element) => element.poll.pollId == pollId); + if (index != -1) { + toasts.add(HMSToastModel(hlsViewerPolls[index], + hmsToastType: HMSToastsType.pollStartedToast)); + pollQuestions.add(hlsViewerPolls[index]); + hlsViewerPolls.removeAt(index); + notifyListeners(); + } + } + + /*******************************This is the implementation for showing emoji's in HLS *******************/ /** * Generally we are assuming that the timed metadata payload will be a JSON String * but if it's a normal string then this throws the format exception @@ -2512,23 +2607,24 @@ class MeetingStore extends ChangeNotifier * The toast is displayed for the time duration hlsCue.endDate - hlsCue.startDate * If endDate is null then toast is displayed for 2 seconds by default */ - try { - final Map data = jsonDecode(hlsCue.payload!); - Utilities.showTimedMetadata( - Utilities.getTimedMetadataEmojiFromId(data["emojiId"]), - time: hlsCue.endDate == null - ? 2 - : (hlsCue.endDate!.difference(hlsCue.startDate)).inSeconds, - align: Utilities.timedMetadataAlignment[math.Random() - .nextInt(Utilities.timedMetadataAlignment.length)]); - } catch (e) { - Utilities.showTimedMetadata(hlsCue.payload!, - time: hlsCue.endDate == null - ? 2 - : (hlsCue.endDate!.difference(hlsCue.startDate)).inSeconds, - align: Utilities.timedMetadataAlignment[math.Random() - .nextInt(Utilities.timedMetadataAlignment.length)]); - } + // try { + // final Map data = jsonDecode(hlsCue.payload!); + // Utilities.showTimedMetadata( + // Utilities.getTimedMetadataEmojiFromId(data["emojiId"]), + // time: hlsCue.endDate == null + // ? 2 + // : (hlsCue.endDate!.difference(hlsCue.startDate)).inSeconds, + // align: Utilities.timedMetadataAlignment[math.Random() + // .nextInt(Utilities.timedMetadataAlignment.length)]); + // } catch (e) { + // Utilities.showTimedMetadata(hlsCue.payload!, + // time: hlsCue.endDate == null + // ? 2 + // : (hlsCue.endDate!.difference(hlsCue.startDate)).inSeconds, + // align: Utilities.timedMetadataAlignment[math.Random() + // .nextInt(Utilities.timedMetadataAlignment.length)]); + // } + /************************************************************************************************************/ } } @@ -2573,4 +2669,74 @@ class MeetingStore extends ChangeNotifier addPeer(peer); } } + + @override + void onPollUpdate( + {required HMSPoll poll, required HMSPollUpdateType pollUpdateType}) { + log("onPollUpdate -> poll $poll updateType: $pollUpdateType startedAt: ${poll.startedAt}"); + switch (pollUpdateType) { + ///If the poll is started we add the poll in questions list + case HMSPollUpdateType.started: + + /* + * Here we check whether the peer has permission to view polls + * Then if the user is a realtime user we show the poll immediately + * while for hls viewer we show the poll based on the `onCue` event i.e. + * timed metadata event for poll + */ + if ((localPeer?.role.permissions.pollRead ?? false) || + (localPeer?.role.permissions.pollWrite ?? false)) { + int index = pollQuestions + .indexWhere((element) => element.poll.pollId == poll.pollId); + if (index == -1) { + HMSPollStore store = HMSPollStore(poll: poll); + if (HMSRoomLayout.peerType == PeerRoleType.conferencing) { + pollQuestions.add(store); + toasts.add(HMSToastModel(store, + hmsToastType: HMSToastsType.pollStartedToast)); + notifyListeners(); + } else { + /* + * Here we check whether the poll start time is + * more than 20 secs older from now or not. We have kept 20 secs + * since the stream playback rolling window time is + * set to 20 by default i.e. if the time difference is less than 20 secs + * we will get the [onCue] callback again. If its greater than 20 + * we show the toast immediately for the user to vote. + */ + if (poll.startedAt != null && + (DateTime.now().difference(poll.startedAt!) > + const Duration(seconds: 20))) { + pollQuestions.add(store); + toasts.add(HMSToastModel(store, + hmsToastType: HMSToastsType.pollStartedToast)); + } else { + hlsViewerPolls.add(store); + } + } + } + } + break; + + ///In other cases we just update the state of the poll + case HMSPollUpdateType.resultsupdated: + int index = pollQuestions + .indexWhere((element) => element.poll.pollId == poll.pollId); + if (index != -1) { + pollQuestions[index].updateState(poll); + } + notifyListeners(); + break; + + case HMSPollUpdateType.stopped: + removeToast(HMSToastsType.pollStartedToast, data: poll.pollId); + int index = pollQuestions + .indexWhere((element) => element.poll.pollId == poll.pollId); + if (index != -1) { + pollQuestions[index].updateState(poll); + } + notifyListeners(); + break; + } + } } diff --git a/packages/hms_room_kit/lib/src/meeting/meeting_toasts.dart b/packages/hms_room_kit/lib/src/meeting/meeting_toasts.dart new file mode 100644 index 000000000..9873b83ba --- /dev/null +++ b/packages/hms_room_kit/lib/src/meeting/meeting_toasts.dart @@ -0,0 +1,46 @@ +///Package imports +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +///Project imports +import 'package:hms_room_kit/src/meeting/meeting_navigation_visibility_controller.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_toast_model.dart'; +import 'package:hms_room_kit/src/widgets/toasts/toast_widget.dart'; + +///[MeetingToasts] widget returns toast to be displayed on meeting UI +///A separate widget is used to avoid rebuild of whole meeting ui when controls are hidden +class MeetingToasts extends StatelessWidget { + final HMSToastModel toast; + final int index; + final int toastsCount; + final MeetingStore meetingStore; + + const MeetingToasts({ + super.key, + required this.toast, + required this.index, + required this.toastsCount, + required this.meetingStore, + }); + + @override + Widget build(BuildContext context) { + return Selector( + selector: (_, meetingNavigationVisibilityController) => + meetingNavigationVisibilityController.showControls, + builder: (_, showControls, __) { + return AnimatedPositioned( + duration: const Duration(milliseconds: 200), + bottom: (showControls ? 28.0 : 10.0) + 8 * index, + left: 5, + child: ToastWidget( + toast: toast, + index: index, + toastsCount: toastsCount, + meetingStore: meetingStore), + ); + }, + ); + } +} diff --git a/packages/hms_room_kit/lib/src/model/poll_store.dart b/packages/hms_room_kit/lib/src/model/poll_store.dart new file mode 100644 index 000000000..2925fff52 --- /dev/null +++ b/packages/hms_room_kit/lib/src/model/poll_store.dart @@ -0,0 +1,20 @@ +///Package imports +import 'package:flutter/material.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +///[HMSPollStore] is used to notify updates for the poll state +/// +///This is useful in updating polls UI based on state in realtime +///without rebuilding the whole UI +class HMSPollStore extends ChangeNotifier { + HMSPoll poll; + + HMSPollStore({required this.poll}); + + ///Here we update the poll object with the new object + ///and notify the listeners + void updateState(HMSPoll poll) { + this.poll = poll; + notifyListeners(); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/app_utilities_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/app_utilities_bottom_sheet.dart index 68712e88c..3528d5be7 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/app_utilities_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/app_utilities_bottom_sheet.dart @@ -2,6 +2,7 @@ import 'package:badges/badges.dart' as badge; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hms_room_kit/src/widgets/bottom_sheets/poll_and_quiz_bottom_sheet.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:provider/provider.dart'; @@ -27,6 +28,18 @@ class AppUtilitiesBottomSheet extends StatefulWidget { } class _AppUtilitiesBottomSheetState 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) { MeetingStore meetingStore = context.read(); @@ -177,10 +190,10 @@ class _AppUtilitiesBottomSheetState extends State { ), ), optionText: - meetingStore.isBRB ? "I'm Back" : "Be Right Back") + meetingStore.isBRB ? "I'm Back" : "Be Right Back"), ///This renders the raise hand option - , + MoreOptionItem( onTap: () async { context.read().toggleLocalPeerHandRaise(); @@ -199,6 +212,38 @@ class _AppUtilitiesBottomSheetState extends State { ? "Lower Hand" : "Raise Hand"), + ///This renders the polls and quizzes option + if ((meetingStore.localPeer?.role.permissions.pollRead ?? + false) || + (meetingStore.localPeer?.role.permissions.pollWrite ?? + false)) + MoreOptionItem( + onTap: () { + Navigator.pop(context); + 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: meetingStore, + child: const PollAndQuizBottomSheet()), + ); + }, + optionIcon: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/polls.svg", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, + BlendMode.srcIn), + ), + optionText: "Polls and Quizzes"), + ///This renders the recording option ///This option is only rendered if the local peer has the permission to ///start/stop browser recording @@ -252,31 +297,35 @@ class _AppUtilitiesBottomSheetState extends State { topRight: Radius.circular(16)), ), context: context, - builder: (ctx) => EndServiceBottomSheet( - onButtonPressed: () => - meetingStore.stopRtmpAndRecording(), - title: HMSTitleText( - text: "Stop Recording", - textColor: HMSThemeColors.alertErrorDefault, - letterSpacing: 0.15, - fontSize: 20, - ), - bottomSheetTitleIcon: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/alert.svg", - height: 20, - width: 20, - colorFilter: ColorFilter.mode( - HMSThemeColors.alertErrorDefault, - BlendMode.srcIn), - ), - subTitle: HMSSubheadingText( - text: - "Are you sure you want to stop recording? You\n can’t undo this action.", - maxLines: 2, - textColor: - HMSThemeColors.onSurfaceMediumEmphasis, + builder: (ctx) => ChangeNotifierProvider.value( + value: meetingStore, + child: EndServiceBottomSheet( + onButtonPressed: () => + meetingStore.stopRtmpAndRecording(), + title: HMSTitleText( + text: "Stop Recording", + textColor: + HMSThemeColors.alertErrorDefault, + letterSpacing: 0.15, + fontSize: 20, + ), + bottomSheetTitleIcon: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/alert.svg", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.alertErrorDefault, + BlendMode.srcIn), + ), + subTitle: HMSSubheadingText( + text: + "Are you sure you want to stop recording? You\n can’t undo this action.", + maxLines: 2, + textColor: HMSThemeColors + .onSurfaceMediumEmphasis, + ), + buttonText: "Stop Recording", ), - buttonText: "Stop Recording", ), ); } else { diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/audio_settings_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/audio_settings_bottom_sheet.dart index c4bccd18d..8a38ddb4e 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/audio_settings_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/audio_settings_bottom_sheet.dart @@ -27,6 +27,13 @@ class _AudioSettingsBottomSheetState extends State { @override void initState() { super.initState(); + context.read().addBottomSheet(context); + } + + @override + void deactivate() { + context.read().removeBottomSheet(context); + super.deactivate(); } @override diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/change_name_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/change_name_bottom_sheet.dart index 97f02d7d6..7ba5b474e 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/change_name_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/change_name_bottom_sheet.dart @@ -29,6 +29,7 @@ class _ChangeNameBottomSheetState extends State { void initState() { super.initState(); nameController.text = context.read().localPeer?.name ?? ""; + context.read().addBottomSheet(context); } @override @@ -37,6 +38,12 @@ class _ChangeNameBottomSheetState extends State { super.dispose(); } + @override + void deactivate() { + context.read().removeBottomSheet(context); + super.deactivate(); + } + ///This function is called when the change name button is clicked void _changeName() { ///We only change the name 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 5da03b874..8286a0368 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 @@ -34,6 +34,12 @@ class _ChatBottomSheetState extends State { final ScrollController _scrollController = ScrollController(); final DateFormat formatter = DateFormat('hh:mm a'); + @override + void initState() { + super.initState(); + setRecipientChipValue(); + } + @override void dispose() { _scrollController.dispose(); @@ -49,12 +55,6 @@ class _ChatBottomSheetState extends State { } } - @override - void initState() { - super.initState(); - setRecipientChipValue(); - } - ///This function sets the value of the recipient chip void setRecipientChipValue() { dynamic currentValue = context.read().recipientSelectorValue; diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_only_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_only_bottom_sheet.dart index 3406c8789..7c5ff0568 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_only_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_only_bottom_sheet.dart @@ -14,9 +14,14 @@ import 'package:provider/provider.dart'; ///[ChatOnlyBottomSheet] is a bottom sheet that is used to render the bottom sheet to show chat only when participants ///list is disabled from dashboard -class ChatOnlyBottomSheet extends StatelessWidget { +class ChatOnlyBottomSheet extends StatefulWidget { const ChatOnlyBottomSheet({Key? key}) : super(key: key); + @override + State createState() => _ChatOnlyBottomSheetState(); +} + +class _ChatOnlyBottomSheetState extends State { @override Widget build(BuildContext context) { return SafeArea( diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_utilities_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_utilities_bottom_sheet.dart index 3cf62ec6a..4d24872fd 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_utilities_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/chat_utilities_bottom_sheet.dart @@ -32,6 +32,7 @@ class _ChatUtilitiesBottomSheetState extends State { @override initState() { super.initState(); + context.read().addBottomSheet(context); isPinned = context.read().pinnedMessages.indexWhere( (element) => element["id"] == widget.message.messageId) != -1; @@ -41,6 +42,12 @@ class _ChatUtilitiesBottomSheetState extends State { -1; } + @override + void deactivate() { + context.read().removeBottomSheet(context); + super.deactivate(); + } + @override Widget build(BuildContext context) { return FractionallySizedBox( diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/end_service_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/end_service_bottom_sheet.dart index 7583deb95..9eec948b2 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/end_service_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/end_service_bottom_sheet.dart @@ -3,8 +3,10 @@ import 'package:flutter/material.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_cross_button.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_title_text.dart'; +import 'package:provider/provider.dart'; ///[EndServiceBottomSheet] is a bottom sheet that is used to render the bottom sheet to stop services ///It has following parameters: @@ -14,7 +16,7 @@ import 'package:hms_room_kit/src/widgets/common_widgets/hms_title_text.dart'; /// [buttonText] is the text of the button /// [onButtonPressed] is the function that is called when the button is pressed /// [buttonColor] is the color of the button -class EndServiceBottomSheet extends StatelessWidget { +class EndServiceBottomSheet extends StatefulWidget { final Widget? bottomSheetTitleIcon; final Widget? title; final Widget? subTitle; @@ -31,6 +33,23 @@ class EndServiceBottomSheet extends StatelessWidget { this.onButtonPressed, this.buttonColor}); + @override + State createState() => _EndServiceBottomSheetState(); +} + +class _EndServiceBottomSheetState 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 FractionallySizedBox( @@ -46,11 +65,11 @@ class EndServiceBottomSheet extends StatelessWidget { children: [ Row( children: [ - bottomSheetTitleIcon ?? const SizedBox(), + widget.bottomSheetTitleIcon ?? const SizedBox(), const SizedBox( width: 8, ), - title ?? const SizedBox() + widget.title ?? const SizedBox() ], ), const Row( @@ -64,7 +83,7 @@ class EndServiceBottomSheet extends StatelessWidget { const SizedBox( height: 8, ), - subTitle ?? const SizedBox(), + widget.subTitle ?? const SizedBox(), const SizedBox( height: 16, ), @@ -73,14 +92,15 @@ class EndServiceBottomSheet extends StatelessWidget { shadowColor: MaterialStateProperty.all(HMSThemeColors.surfaceDim), backgroundColor: MaterialStateProperty.all( - buttonColor ?? HMSThemeColors.alertErrorDefault), + widget.buttonColor ?? + HMSThemeColors.alertErrorDefault), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ))), onPressed: () { - if (onButtonPressed != null) { - onButtonPressed!(); + if (widget.onButtonPressed != null) { + widget.onButtonPressed!(); } Navigator.pop(context); }, @@ -88,7 +108,7 @@ class EndServiceBottomSheet extends StatelessWidget { height: 48, child: Center( child: HMSTitleText( - text: buttonText ?? "", + text: widget.buttonText ?? "", textColor: HMSThemeColors.alertErrorBrighter), ), )) diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_more_options.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart similarity index 72% rename from packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_more_options.dart rename to packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart index b7e9e0140..3bcd3860c 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_more_options.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/hls_app_utilities_bottom_sheet.dart @@ -1,6 +1,7 @@ ///Package imports import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hms_room_kit/src/widgets/bottom_sheets/poll_and_quiz_bottom_sheet.dart'; import 'package:provider/provider.dart'; import 'package:badges/badges.dart' as badge; @@ -14,20 +15,27 @@ 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'; -///[HLSMoreOptionsBottomSheet] is a bottom sheet that is used to show more options in the meeting -class HLSMoreOptionsBottomSheet extends StatefulWidget { - const HLSMoreOptionsBottomSheet({super.key}); +///[HLSAppUtilitiesBottomSheet] is a bottom sheet that is used to show more options in the meeting +class HLSAppUtilitiesBottomSheet extends StatefulWidget { + const HLSAppUtilitiesBottomSheet({super.key}); @override - State createState() => + State createState() => _HLSMoreOptionsBottomSheetBottomSheetState(); } class _HLSMoreOptionsBottomSheetBottomSheetState - extends State { + extends State { @override void initState() { super.initState(); + context.read().addBottomSheet(context); + } + + @override + void deactivate() { + context.read().removeBottomSheet(context); + super.deactivate(); } @override @@ -67,8 +75,8 @@ class _HLSMoreOptionsBottomSheetBottomSheetState ///Here we render the participants button and the change name button Wrap( - spacing: 12, runSpacing: 24, + spacing: MediaQuery.of(context).size.width * 0.005, children: [ if (HMSRoomLayout.isParticipantsListEnabled) MoreOptionItem( @@ -160,7 +168,50 @@ class _HLSMoreOptionsBottomSheetBottomSheetState HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), ), - optionText: "Change Name") + optionText: "Change Name"), + + ///This renders the polls and quizzes option + if ((context + .read() + .localPeer + ?.role + .permissions + .pollRead ?? + false) || + (context + .read() + .localPeer + ?.role + .permissions + .pollWrite ?? + false)) + MoreOptionItem( + onTap: () { + var meetingStore = context.read(); + Navigator.pop(context); + 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: meetingStore, + child: const PollAndQuizBottomSheet()), + ); + }, + optionIcon: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/polls.svg", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, + BlendMode.srcIn), + ), + optionText: "Polls and Quizzes"), ], ), ], 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 00dbda0d9..b85347fb4 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 @@ -5,16 +5,37 @@ 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 StatelessWidget { +class LeaveSessionBottomSheet extends StatefulWidget { final MeetingStore meetingStore; const LeaveSessionBottomSheet({super.key, required this.meetingStore}); + @override + State createState() => + _LeaveSessionBottomSheetState(); +} + +class _LeaveSessionBottomSheetState 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 ((meetingStore.localPeer?.role.permissions.endRoom ?? false) || - ((meetingStore.localPeer?.role.permissions.hlsStreaming ?? false) && - meetingStore.hasHlsStarted)) + return ((widget.meetingStore.localPeer?.role.permissions.endRoom ?? + false) || + ((widget.meetingStore.localPeer?.role.permissions.hlsStreaming ?? + false) && + widget.meetingStore.hasHlsStarted)) ? Padding( padding: const EdgeInsets.only(top: 12.0), child: Column( @@ -46,31 +67,34 @@ class LeaveSessionBottomSheet extends StatelessWidget { topRight: Radius.circular(16)), ), context: context, - builder: (ctx) => EndServiceBottomSheet( - onButtonPressed: () => { - meetingStore.leave(), - }, - title: HMSTitleText( - text: "Leave Session", - textColor: HMSThemeColors.alertErrorDefault, - letterSpacing: 0.15, - fontSize: 20, - ), - bottomSheetTitleIcon: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/end_warning.svg", - height: 20, - width: 20, - colorFilter: ColorFilter.mode( - HMSThemeColors.alertErrorDefault, - BlendMode.srcIn), - ), - subTitle: HMSSubheadingText( - text: - "Others will continue after you leave. You can join\n the session again.", - maxLines: 2, - textColor: HMSThemeColors.onSurfaceMediumEmphasis, + builder: (ctx) => ChangeNotifierProvider.value( + value: widget.meetingStore, + child: EndServiceBottomSheet( + onButtonPressed: () => { + widget.meetingStore.leave(), + }, + title: HMSTitleText( + text: "Leave Session", + textColor: HMSThemeColors.alertErrorDefault, + letterSpacing: 0.15, + fontSize: 20, + ), + bottomSheetTitleIcon: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/end_warning.svg", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.alertErrorDefault, + BlendMode.srcIn), + ), + subTitle: HMSSubheadingText( + text: + "Others will continue after you leave. You can join\n the session again.", + maxLines: 2, + textColor: HMSThemeColors.onSurfaceMediumEmphasis, + ), + buttonText: "Leave Session", ), - buttonText: "Leave Session", ), ) }, @@ -83,17 +107,17 @@ class LeaveSessionBottomSheet extends StatelessWidget { HMSThemeColors.alertErrorBrighter, BlendMode.srcIn), semanticsLabel: "leave_room_button", ), - title: - ((meetingStore.localPeer?.role.permissions.hlsStreaming ?? - false) && - meetingStore.hasHlsStarted) - ? "End Stream" - : "End Session", + title: ((widget.meetingStore.localPeer?.role.permissions + .hlsStreaming ?? + false) && + widget.meetingStore.hasHlsStarted) + ? "End Stream" + : "End Session", titleColor: HMSThemeColors.alertErrorBrighter, - subTitle: ((meetingStore - .localPeer?.role.permissions.hlsStreaming ?? + subTitle: ((widget.meetingStore.localPeer?.role.permissions + .hlsStreaming ?? false) && - meetingStore.hasHlsStarted) + widget.meetingStore.hasHlsStarted) ? "The stream will end for everyone after they’ve watched it." : "The session will end for everyone in the room immediately.", subTitleColor: HMSThemeColors.alertErrorBright, @@ -108,57 +132,60 @@ class LeaveSessionBottomSheet extends StatelessWidget { topRight: Radius.circular(16)), ), context: context, - builder: (ctx) => EndServiceBottomSheet( - onButtonPressed: () => { - if ((meetingStore.localPeer?.role.permissions - .hlsStreaming ?? - false) && - meetingStore.hasHlsStarted) - { - meetingStore.stopHLSStreaming(), - meetingStore.leave(), - } - else - { - meetingStore.endRoom( - false, "Room Ended From Flutter"), - }, - }, - title: HMSTitleText( - text: ((meetingStore.localPeer?.role.permissions - .hlsStreaming ?? + builder: (ctx) => ChangeNotifierProvider.value( + value: widget.meetingStore, + child: EndServiceBottomSheet( + onButtonPressed: () => { + if ((widget.meetingStore.localPeer?.role.permissions + .hlsStreaming ?? + false) && + widget.meetingStore.hasHlsStarted) + { + widget.meetingStore.stopHLSStreaming(), + widget.meetingStore.leave(), + } + else + { + widget.meetingStore + .endRoom(false, "Room Ended From Flutter"), + }, + }, + title: HMSTitleText( + text: ((widget.meetingStore.localPeer?.role + .permissions.hlsStreaming ?? + false) && + widget.meetingStore.hasHlsStarted) + ? "End Stream" + : "End Session", + textColor: HMSThemeColors.alertErrorDefault, + letterSpacing: 0.15, + fontSize: 20, + ), + bottomSheetTitleIcon: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/end_warning.svg", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.alertErrorDefault, + BlendMode.srcIn), + ), + subTitle: HMSSubheadingText( + text: ((widget.meetingStore.localPeer?.role + .permissions.hlsStreaming ?? + false) && + widget.meetingStore.hasHlsStarted) + ? "The stream will end for everyone after they’ve watched it." + : "The session will end for everyone in the room immediately.", + maxLines: 3, + textColor: HMSThemeColors.onSurfaceMediumEmphasis, + ), + buttonText: ((widget.meetingStore.localPeer?.role + .permissions.hlsStreaming ?? false) && - meetingStore.hasHlsStarted) + widget.meetingStore.hasHlsStarted) ? "End Stream" : "End Session", - textColor: HMSThemeColors.alertErrorDefault, - letterSpacing: 0.15, - fontSize: 20, - ), - bottomSheetTitleIcon: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/end_warning.svg", - height: 20, - width: 20, - colorFilter: ColorFilter.mode( - HMSThemeColors.alertErrorDefault, - BlendMode.srcIn), - ), - subTitle: HMSSubheadingText( - text: ((meetingStore.localPeer?.role.permissions - .hlsStreaming ?? - false) && - meetingStore.hasHlsStarted) - ? "The stream will end for everyone after they’ve watched it." - : "The session will end for everyone in the room immediately.", - maxLines: 3, - textColor: HMSThemeColors.onSurfaceMediumEmphasis, ), - buttonText: ((meetingStore.localPeer?.role.permissions - .hlsStreaming ?? - false) && - meetingStore.hasHlsStarted) - ? "End Stream" - : "End Session", ), ) }, @@ -166,30 +193,33 @@ class LeaveSessionBottomSheet extends StatelessWidget { ], ), ) - : EndServiceBottomSheet( - onButtonPressed: () => { - meetingStore.leave(), - }, - title: HMSTitleText( - text: "Leave Session", - textColor: HMSThemeColors.alertErrorDefault, - letterSpacing: 0.15, - fontSize: 20, - ), - bottomSheetTitleIcon: SvgPicture.asset( - "packages/hms_room_kit/lib/src/assets/icons/end_warning.svg", - height: 20, - width: 20, - colorFilter: ColorFilter.mode( - HMSThemeColors.alertErrorDefault, BlendMode.srcIn), - ), - subTitle: HMSSubheadingText( - text: - "Others will continue after you leave. You can join\n the session again.", - maxLines: 2, - textColor: HMSThemeColors.onSurfaceMediumEmphasis, + : ChangeNotifierProvider.value( + value: widget.meetingStore, + child: EndServiceBottomSheet( + onButtonPressed: () => { + widget.meetingStore.leave(), + }, + title: HMSTitleText( + text: "Leave Session", + textColor: HMSThemeColors.alertErrorDefault, + letterSpacing: 0.15, + fontSize: 20, + ), + bottomSheetTitleIcon: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/end_warning.svg", + height: 20, + width: 20, + colorFilter: ColorFilter.mode( + HMSThemeColors.alertErrorDefault, BlendMode.srcIn), + ), + subTitle: HMSSubheadingText( + text: + "Others will continue after you leave. You can join\n the session again.", + maxLines: 2, + textColor: HMSThemeColors.onSurfaceMediumEmphasis, + ), + buttonText: "Leave Session", ), - buttonText: "Leave Session", ); } } diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/local_peer_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/local_peer_bottom_sheet.dart index 991e67ac7..6659c1dc7 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/local_peer_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/local_peer_bottom_sheet.dart @@ -34,6 +34,18 @@ class LocalPeerBottomSheet extends StatefulWidget { } class _LocalPeerBottomSheetState 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 FractionallySizedBox( diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/overlay_participants_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/overlay_participants_bottom_sheet.dart index 8f3ca83c5..27a1574fd 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/overlay_participants_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/overlay_participants_bottom_sheet.dart @@ -6,9 +6,28 @@ import 'package:hms_room_kit/src/widgets/common_widgets/hms_cross_button.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; import 'package:provider/provider.dart'; -class OverlayParticipantsBottomSheet extends StatelessWidget { +class OverlayParticipantsBottomSheet extends StatefulWidget { const OverlayParticipantsBottomSheet({Key? key}) : super(key: key); + @override + State createState() => + _OverlayParticipantsBottomSheetState(); +} + +class _OverlayParticipantsBottomSheetState + 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 SafeArea( 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 21a089bd4..90c7753bd 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 @@ -53,6 +53,7 @@ class _ParticipantsBottomSheetState extends State { } void viewAll(String role) { + var meetingStore = context.read(); showModalBottomSheet( isScrollControlled: true, backgroundColor: HMSThemeColors.surfaceDim, @@ -61,7 +62,7 @@ class _ParticipantsBottomSheetState extends State { ), context: context, builder: (ctx) => ChangeNotifierProvider.value( - value: context.read(), + value: meetingStore, child: ParticipantsViewAllBottomSheet(role: role)), ); } diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_view_all_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_view_all_bottom_sheet.dart index 6a6844f16..9f48c5d4a 100644 --- a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_view_all_bottom_sheet.dart +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/participants_view_all_bottom_sheet.dart @@ -40,6 +40,7 @@ class _ParticipantsViewAllBottomSheetState void initState() { super.initState(); context.read().disableRefresh(); + context.read().addBottomSheet(context); _scrollController.addListener(() { if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { @@ -49,9 +50,9 @@ class _ParticipantsViewAllBottomSheetState } @override - void dispose() { - _scrollController.dispose(); - super.dispose(); + void deactivate() { + context.read().removeBottomSheet(context); + super.deactivate(); } Widget _kebabMenu(HMSPeer peer) { diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/poll_and_quiz_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/poll_and_quiz_bottom_sheet.dart new file mode 100644 index 000000000..6d2350ce6 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/poll_and_quiz_bottom_sheet.dart @@ -0,0 +1,187 @@ +///Package imports +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; + +///Project imports +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/model/poll_store.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_cross_button.dart'; +import 'package:hms_room_kit/src/widgets/poll_widgets/poll_creation_widgets/poll_form.dart'; +import 'package:hms_room_kit/src/widgets/poll_widgets/poll_creation_widgets/poll_question_card.dart'; + +///[PollAndQuizBottomSheet] renders the poll and quiz creation UI +class PollAndQuizBottomSheet extends StatefulWidget { + const PollAndQuizBottomSheet({Key? key}) : super(key: key); + + @override + State createState() => _PollAndQuizBottomSheetState(); +} + +class _PollAndQuizBottomSheetState 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 FractionallySizedBox( + heightFactor: 0.87, + child: Padding( + padding: const EdgeInsets.only(top: 12.0, left: 16, right: 16), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ///Top bar + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + HMSTitleText( + text: "Polls and Quizzes", + fontSize: 20, + textColor: HMSThemeColors.onSurfaceHighEmphasis, + ), + const HMSCrossButton(), + ], + ), + + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 16), + child: Divider( + color: HMSThemeColors.borderDefault, + height: 5, + ), + ), + + ///Poll and Quiz selection buttons + ///Will be added in upcoming release + // if (context + // .read() + // .localPeer + // ?.role + // .permissions + // .pollWrite ?? + // false) + // const PollQuizSelectionWidget(), + + if (context + .read() + .localPeer + ?.role + .permissions + .pollWrite ?? + false) + const SizedBox( + height: 24, + ), + + ///Poll or Quiz Section + if (context + .read() + .localPeer + ?.role + .permissions + .pollWrite ?? + false) + const PollForm(), + + ///This section shows all the previous polls + ///which are either started or stopped + if ((context + .read() + .localPeer + ?.role + .permissions + .pollRead ?? + false) || + (context + .read() + .localPeer + ?.role + .permissions + .pollWrite ?? + false)) + Selector( + selector: (_, meetingStore) => + meetingStore.pollQuestions.length, + builder: (_, data, __) { + return data > 0 + ? Padding( + padding: + const EdgeInsets.symmetric(vertical: 24.0), + child: HMSTitleText( + text: "Previous Polls and Quizzes", + textColor: + HMSThemeColors.onSurfaceHighEmphasis, + fontSize: 20, + letterSpacing: 0.15, + ), + ) + : const SizedBox(); + }), + + if ((context + .read() + .localPeer + ?.role + .permissions + .pollRead ?? + false) || + ((context + .read() + .localPeer + ?.role + .permissions + .pollWrite ?? + false))) + Selector>>( + selector: (_, meetingStore) => Tuple2( + meetingStore.pollQuestions.length, + meetingStore.pollQuestions), + builder: (_, data, __) { + if (data.item2.isEmpty && + (!(context + .read() + .localPeer + ?.role + .permissions + .pollWrite ?? + true))) { + return Center( + child: HMSTitleText( + text: "No polls started in this session", + textColor: HMSThemeColors.onPrimaryHighEmphasis), + ); + } else { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: data.item1, + itemBuilder: (BuildContext context, int index) { + return ChangeNotifierProvider.value( + value: data.item2[data.item1 - index - 1], + child: const PollQuestionCard()); + }, + ); + } + }, + ), + const SizedBox( + height: 8, + ) + ], + ), + )), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/bottom_sheets/poll_vote_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/poll_vote_bottom_sheet.dart new file mode 100644 index 000000000..281d4432f --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/bottom_sheets/poll_vote_bottom_sheet.dart @@ -0,0 +1,195 @@ +///Dart imports +import 'dart:math' as math; + +///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/layout_api/hms_theme_colors.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/model/poll_store.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_button.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_cross_button.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_title_text.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/live_badge.dart'; +import 'package:hms_room_kit/src/widgets/poll_widgets/voting_flow_widgets/poll_result_card.dart'; +import 'package:hms_room_kit/src/widgets/poll_widgets/voting_flow_widgets/poll_vote_card.dart'; + +///[PollVoteBottomSheet] renders the voting bottom sheet for polls +class PollVoteBottomSheet extends StatefulWidget { + const PollVoteBottomSheet({super.key}); + + @override + State createState() => _PollVoteBottomSheetState(); +} + +class _PollVoteBottomSheetState 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) { + var hmsPollStore = context.watch(); + return FractionallySizedBox( + heightFactor: 0.87, + child: Padding( + padding: const EdgeInsets.only(top: 12.0, left: 16, right: 16), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + IconButton( + icon: Icon( + Icons.arrow_back_ios_new, + size: 16, + color: HMSThemeColors.onSurfaceHighEmphasis, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.4), + child: HMSTitleText( + text: hmsPollStore.poll.title, + fontSize: 20, + textColor: HMSThemeColors.onSurfaceHighEmphasis, + maxLines: 2, + ), + ), + const SizedBox( + width: 12, + ), + Selector( + selector: (_, hmsPollStore) => + hmsPollStore.poll.state, + builder: (_, state, __) { + return state == HMSPollState.started + ? const LiveBadge() + : LiveBadge( + badgeColor: HMSThemeColors.secondaryDefault, + text: "ENDED", + width: 50, + ); + }), + ], + ), + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HMSCrossButton(), + ], + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 16), + child: Divider( + color: HMSThemeColors.borderDefault, + height: 5, + ), + ), + HMSTitleText( + text: + "${hmsPollStore.poll.createdBy?.name.substring(0, math.min(15, hmsPollStore.poll.createdBy?.name.length ?? 0)) ?? ""} started a poll", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + letterSpacing: 0.15, + ), + const SizedBox( + height: 8, + ), + Selector( + selector: (_, pollStore) => pollStore.poll, + builder: (_, poll, __) { + return ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: poll.questions?.length ?? 0, + shrinkWrap: true, + itemBuilder: (BuildContext context, index) { + if ((poll.questions![index].myResponses.isNotEmpty) || + (poll.state == HMSPollState.stopped)) { + var totalVotes = 0; + for (var element + in poll.questions![index].options) { + totalVotes += element.voteCount; + } + var isVoteCountHidden = false; + if (poll.rolesThatCanViewResponses.isNotEmpty && + !poll.rolesThatCanViewResponses.contains(context + .read() + .localPeer + ?.role)) { + isVoteCountHidden = true; + } + return PollResultCard( + questionNumber: index, + totalQuestions: poll.questions?.length ?? 0, + question: poll.questions![index], + totalVotes: totalVotes, + isVoteCountHidden: isVoteCountHidden, + ); + } else { + return PollVoteCard( + questionNumber: index, + totalQuestions: poll.questions?.length ?? 0, + question: poll.questions![index]); + } + }); + }), + Selector( + selector: (_, pollStore) => pollStore.poll.state, + builder: (_, pollState, __) { + ///End Poll is only shown when user has permission to end Poll and poll is not stopped. + return (pollState != HMSPollState.stopped && + (context + .read() + .localPeer + ?.role + .permissions + .pollWrite ?? + false)) + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HMSButton( + width: MediaQuery.of(context).size.width * 0.3, + onPressed: () { + context + .read() + .stopPoll(hmsPollStore.poll); + }, + childWidget: HMSTitleText( + text: "End Poll", + textColor: + HMSThemeColors.onPrimaryHighEmphasis), + buttonBackgroundColor: + HMSThemeColors.alertErrorDefault, + ), + ], + ) + : const SizedBox(); + }) + ], + ), + ), + ), + ); + } +} 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 709b7a608..be92c7d03 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 @@ -8,6 +8,7 @@ import 'package:hms_room_kit/src/meeting/meeting_store.dart'; import 'package:hms_room_kit/src/model/peer_track_node.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_cross_button.dart'; import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; +import 'package:provider/provider.dart'; ///[RemotePeerBottomSheet] is a widget that is used to render the bottom sheet for remote peers ///This bottom sheet is shown when the more option button is clicked on a remote peer tile @@ -23,6 +24,18 @@ class RemotePeerBottomSheet extends StatefulWidget { } class _RemotePeerBottomSheetState 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 FractionallySizedBox( diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_button.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_button.dart index c091f4fac..d60388e2b 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_button.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_button.dart @@ -1,6 +1,8 @@ +///Package imports import 'package:flutter/material.dart'; -import 'package:hms_room_kit/src/common/app_color.dart'; +import 'package:hms_room_kit/hms_room_kit.dart'; +///[HMSButton] is a button based on HMS theme colors class HMSButton extends StatelessWidget { final double width; final Color? shadowColor; @@ -20,10 +22,8 @@ class HMSButton extends StatelessWidget { width: width, child: ElevatedButton( style: ButtonStyle( - shadowColor: - MaterialStateProperty.all(shadowColor ?? themeSurfaceColor), backgroundColor: MaterialStateProperty.all( - buttonBackgroundColor ?? hmsdefaultColor), + buttonBackgroundColor ?? HMSThemeColors.primaryDefault), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_listenable_button.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_listenable_button.dart index 57283b4dc..6a1686b32 100644 --- a/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_listenable_button.dart +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/hms_listenable_button.dart @@ -45,11 +45,12 @@ class HMSListenableButton extends StatelessWidget { style: ButtonStyle( shadowColor: MaterialStateProperty.all( shadowColor ?? HMSThemeColors.surfaceDim), - backgroundColor: (textController.text.isEmpty || isDisabled) - ? MaterialStateProperty.all( - HMSThemeColors.primaryDisabled) - : MaterialStateProperty.all( - HMSThemeColors.primaryDefault), + backgroundColor: + (textController.text.trim().isEmpty || isDisabled) + ? MaterialStateProperty.all( + HMSThemeColors.primaryDisabled) + : MaterialStateProperty.all( + HMSThemeColors.primaryDefault), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), 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 050c68b66..bba895b99 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 @@ -13,6 +13,7 @@ import 'package:hms_room_kit/src/widgets/common_widgets/hms_text_style.dart'; ///[fontSize] is the size of the font, default value is 16 ///[fontWeight] is the weight of the font, default value is FontWeight.w600 ///[textOverflow] is the overflow of the text, default value is TextOverflow.ellipsis +///[maxLines] are the max number of lines that HMSTitleText will render after that it will overflow class HMSTitleText extends StatelessWidget { final String text; final Color textColor; @@ -21,6 +22,7 @@ class HMSTitleText extends StatelessWidget { final double? fontSize; final FontWeight? fontWeight; final TextOverflow? textOverflow; + final int maxLines; const HMSTitleText( {Key? key, @@ -30,13 +32,15 @@ class HMSTitleText extends StatelessWidget { this.lineHeight = 24, this.fontSize = 16, this.fontWeight = FontWeight.w600, - this.textOverflow = TextOverflow.ellipsis}) + this.textOverflow = TextOverflow.ellipsis, + this.maxLines = 1}) : super(key: key); @override Widget build(BuildContext context) { return Text(text, overflow: textOverflow, + maxLines: maxLines, style: HMSTextStyle.setTextStyle( color: textColor, height: lineHeight! / fontSize!, diff --git a/packages/hms_room_kit/lib/src/widgets/common_widgets/live_badge.dart b/packages/hms_room_kit/lib/src/widgets/common_widgets/live_badge.dart new file mode 100644 index 000000000..478f42118 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/common_widgets/live_badge.dart @@ -0,0 +1,36 @@ +///Package imports +import 'package:flutter/cupertino.dart'; + +///Project imports +import 'package:hms_room_kit/src/layout_api/hms_theme_colors.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_title_text.dart'; + +///[LiveBadge] renders the badge based on the text and colors provided. +///By default it renders a live badge as the name suggests. +///It also provides parameters to configure the text, color and size of the badge +class LiveBadge extends StatelessWidget { + final double? height; + final double? width; + final Color? badgeColor; + final String? text; + const LiveBadge( + {super.key, this.height, this.width, this.badgeColor, this.text}); + + @override + Widget build(BuildContext context) { + return Container( + height: height ?? 24, + width: width ?? 43, + decoration: BoxDecoration( + color: badgeColor ?? HMSThemeColors.alertErrorDefault, + borderRadius: BorderRadius.circular(4)), + child: Center( + child: HMSTitleText( + text: text ?? "LIVE", + fontSize: 10, + lineHeight: 16, + letterSpacing: 1.5, + textColor: HMSThemeColors.alertErrorBrighter), + )); + } +} 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 77e310f81..bdd456ea9 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 @@ -100,6 +100,7 @@ class MessageContainer extends StatelessWidget { ), GestureDetector( onTap: () { + var meetingStore = context.read(); showModalBottomSheet( isScrollControlled: true, backgroundColor: HMSThemeColors.surfaceDim, @@ -110,7 +111,7 @@ class MessageContainer extends StatelessWidget { ), context: context, builder: (ctx) => ChangeNotifierProvider.value( - value: context.read(), + value: meetingStore, child: ChatUtilitiesBottomSheet( message: message, )), diff --git a/packages/hms_room_kit/lib/src/widgets/peer_widgets/local_peer_more_option.dart b/packages/hms_room_kit/lib/src/widgets/peer_widgets/local_peer_more_option.dart index 8e64da5a8..3b1c5bf22 100644 --- a/packages/hms_room_kit/lib/src/widgets/peer_widgets/local_peer_more_option.dart +++ b/packages/hms_room_kit/lib/src/widgets/peer_widgets/local_peer_more_option.dart @@ -30,6 +30,7 @@ class LocalPeerMoreOption extends StatelessWidget { ///[peerTrackNode] is the peerTrackNode of the peer whose more option is clicked ///We only show the modal bottom sheet if the peer is the local peer var peerTrackNode = context.read(); + var meetingStore = context.read(); showModalBottomSheet( isScrollControlled: true, backgroundColor: HMSThemeColors.surfaceDim, @@ -40,10 +41,10 @@ class LocalPeerMoreOption extends StatelessWidget { ), context: context, builder: (ctx) => ChangeNotifierProvider.value( - value: context.read(), + value: meetingStore, child: LocalPeerBottomSheet( isInsetTile: isInsetTile, - meetingStore: context.read(), + meetingStore: meetingStore, peerTrackNode: peerTrackNode, callbackFunction: callbackFunction, )), diff --git a/packages/hms_room_kit/lib/src/widgets/peer_widgets/more_option.dart b/packages/hms_room_kit/lib/src/widgets/peer_widgets/more_option.dart index 41b70925c..6fe4fa69e 100644 --- a/packages/hms_room_kit/lib/src/widgets/peer_widgets/more_option.dart +++ b/packages/hms_room_kit/lib/src/widgets/peer_widgets/more_option.dart @@ -36,6 +36,7 @@ class _MoreOptionState extends State { ///[peerTrackNode] is the peerTrackNode of the peer whose more option is clicked ///We only show the modal bottom sheet if the peer is not the local peer var peerTrackNode = context.read(); + var meetingStore = context.read(); if (peerTrackNode.peer.peerId != meetingStore.localPeer!.peerId) { showModalBottomSheet( @@ -48,7 +49,7 @@ class _MoreOptionState extends State { ), context: context, builder: (ctx) => ChangeNotifierProvider.value( - value: context.read(), + value: meetingStore, child: RemotePeerBottomSheet( meetingStore: meetingStore, peerTrackNode: peerTrackNode, diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/create_poll_form.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/create_poll_form.dart new file mode 100644 index 000000000..daa7efc2f --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/create_poll_form.dart @@ -0,0 +1,476 @@ +///Package imports +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:tuple/tuple.dart'; + +///Project imports +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_dropdown.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; + +///[CreatePollForm] widget renders the poll creation form +class CreatePollForm extends StatefulWidget { + final int questionNumber; + final int totalQuestions; + final HMSPollQuestionType questionType; + final TextEditingController questionController; + final List optionsTextController; + final HMSPollQuestionBuilder questionBuilder; + final Function deleteQuestionCallback; + final Function savePollCallback; + const CreatePollForm( + {super.key, + required this.questionNumber, + required this.totalQuestions, + required this.questionType, + required this.optionsTextController, + required this.questionController, + required this.questionBuilder, + required this.deleteQuestionCallback, + required this.savePollCallback}); + + @override + State createState() => _CreatePollFormState(); +} + +class _CreatePollFormState extends State { + late TextEditingController _questionController; + late List _optionsTextController; + // bool _isSkippable = false; + // bool _canChangeResponse = false; + + List> getPollQuestionType() { + return const [ + Tuple2("Single Choice", HMSPollQuestionType.singleChoice), + Tuple2("Multiple Choice", HMSPollQuestionType.multiChoice) + ]; + } + + @override + void initState() { + ///Here we initialise the quesetion controller with + ///the value passed from widget. It's empty when a fresh poll is created + ///while it has `text` value when it's being edited + _questionController = widget.questionController; + + ///Here options text controller gets initialised. + ///If controllers are passed on to the widget then we just + ///assign those controllers to _optionsTextController + if (widget.optionsTextController.isEmpty) { + _optionsTextController = [ + TextEditingController(), + TextEditingController() + ]; + } else { + _optionsTextController = widget.optionsTextController; + } + super.initState(); + } + + @override + void dispose() { + ///Here we dispose the question and options controller + _questionController.dispose(); + for (var element in _optionsTextController) { + element.dispose(); + } + super.dispose(); + } + + ///This function set's whether the question is skippable or not + // void setIsSkippable(value) { + // widget.questionBuilder.withCanSkip = value; + // setState(() { + // _isSkippable = value; + // }); + // } + + ///This function set's [canChangeResponse] which decides can the answer be changed + ///once voted + // void setCanChangeResponse(value) { + // widget.questionBuilder.withCanChangeResponse = value; + // setState(() { + // _canChangeResponse = value; + // }); + // } + + ///This adds a new option controller + void _addOption() { + _optionsTextController.add(TextEditingController()); + setState(() {}); + } + + ///This function checks whether the poll is valid or not + ///This is checked before launching the poll + bool _isPollValid() { + bool areOptionsFilled = _optionsTextController.length >= 2; + for (var optionController in _optionsTextController) { + areOptionsFilled = areOptionsFilled && (optionController.text.isNotEmpty); + } + return (areOptionsFilled && _questionController.text.isNotEmpty); + } + + ///This function set's the text for the question + void _setText(String text) { + widget.questionBuilder.withText = text; + } + + ///This function save the poll option + void _savePollOption(String option, int index) { + _optionsTextController[index].text = option; + } + + ///This function saves the option and also fires a callback + ///to save the question + void saveQuestion() { + widget.questionBuilder.withOption = + _optionsTextController.map((e) => e.text).toList(); + if (_isPollValid()) { + widget.savePollCallback(widget.questionBuilder); + } + } + + ///This function updates the poll type selection + void _updatePollType(HMSPollQuestionType questionType) { + widget.questionBuilder.withType = questionType; + } + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: HMSThemeColors.surfaceDefault, + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + HMSTitleText( + text: + "QUESTION ${widget.questionNumber + 1} OF ${widget.totalQuestions}", + textColor: HMSThemeColors.onSurfaceLowEmphasis, + fontSize: 10, + letterSpacing: 1.5, + lineHeight: 16), + const SizedBox( + height: 8, + ), + HMSSubheadingText( + text: "Question Type", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + ), + const SizedBox( + height: 8, + ), + + ///Dropdown for poll type + DropdownButtonHideUnderline( + child: HMSDropDown( + dropDownItems: getPollQuestionType() + .map((e) => DropdownMenuItem( + value: e.item2, + child: HMSTitleText( + text: e.item1, + textColor: HMSThemeColors.onSurfaceHighEmphasis, + fontWeight: FontWeight.w400, + ), + )) + .toList(), + buttonStyleData: ButtonStyleData( + width: double.infinity, + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + decoration: BoxDecoration( + color: HMSThemeColors.surfaceBright, + borderRadius: BorderRadius.circular(8), + )), + dropdownStyleData: DropdownStyleData( + padding: const EdgeInsets.symmetric( + vertical: 12, horizontal: 16), + decoration: BoxDecoration( + color: HMSThemeColors.surfaceBright, + )), + selectedValue: widget.questionType, + updateSelectedValue: (value) { + _updatePollType(value); + })), + const SizedBox( + height: 8, + ), + HMSSubheadingText( + text: "Question", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + ), + const SizedBox( + height: 8, + ), + + ///Textfield for setting the question + SizedBox( + height: 48, + child: TextField( + cursorColor: HMSThemeColors.onSurfaceHighEmphasis, + onTapOutside: (event) => + FocusManager.instance.primaryFocus?.unfocus(), + textInputAction: TextInputAction.done, + textCapitalization: TextCapitalization.words, + style: HMSTextStyle.setTextStyle( + color: HMSThemeColors.onSurfaceHighEmphasis), + controller: _questionController, + keyboardType: TextInputType.text, + onChanged: (value) { + _setText(value.trim()); + setState(() {}); + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + fillColor: HMSThemeColors.surfaceBright, + filled: true, + hintText: "e.g. Who will win the match?", + hintStyle: HMSTextStyle.setTextStyle( + color: HMSThemeColors.onSurfaceLowEmphasis, + height: 1.5, + fontSize: 16, + letterSpacing: 0.5, + fontWeight: FontWeight.w400), + focusedBorder: OutlineInputBorder( + borderRadius: + const BorderRadius.all(Radius.circular(8)), + borderSide: + BorderSide(color: HMSThemeColors.primaryDefault)), + border: + const OutlineInputBorder(borderSide: BorderSide.none)), + ), + ), + const SizedBox( + height: 8, + ), + + ///Divider + Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, + ), + child: Divider( + height: 5, + color: HMSThemeColors.borderBright, + ), + ), + + HMSSubheadingText( + text: "Options", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + ), + + const SizedBox( + height: 8, + ), + + ///Here we set the options + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _optionsTextController.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + height: 48, + width: MediaQuery.of(context).size.width * 0.67, + child: TextField( + cursorColor: HMSThemeColors.onSurfaceHighEmphasis, + onTapOutside: (event) => + FocusManager.instance.primaryFocus?.unfocus(), + textInputAction: TextInputAction.done, + textCapitalization: TextCapitalization.words, + style: HMSTextStyle.setTextStyle( + color: HMSThemeColors.onSurfaceHighEmphasis), + controller: _optionsTextController[index], + keyboardType: TextInputType.text, + onChanged: (value) { + _savePollOption(value.trim(), index); + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + fillColor: HMSThemeColors.surfaceBright, + filled: true, + hintText: "Option ${index + 1}", + hintStyle: HMSTextStyle.setTextStyle( + color: HMSThemeColors.onSurfaceLowEmphasis, + height: 1.5, + fontSize: 16, + letterSpacing: 0.5, + fontWeight: FontWeight.w400), + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all( + Radius.circular(8)), + borderSide: BorderSide( + color: HMSThemeColors.primaryDefault)), + border: const OutlineInputBorder( + borderSide: BorderSide.none)), + ), + ), + if (_optionsTextController.length > 2) + IconButton( + onPressed: () { + _optionsTextController.removeAt(index); + setState(() {}); + }, + icon: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/delete_poll.svg", + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceLowEmphasis, + BlendMode.srcIn))) + ], + ), + ); + }), + if (_optionsTextController.length < 8) + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: GestureDetector( + onTap: () { + _addOption(); + }, + child: Row( + children: [ + SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/add_option.svg", + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceMediumEmphasis, + BlendMode.srcIn)), + const SizedBox( + width: 16, + ), + HMSSubheadingText( + text: "Add an option", + textColor: HMSThemeColors.onSurfaceMediumEmphasis, + ), + ], + ), + ), + ), + + Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, + ), + child: Divider( + height: 5, + color: HMSThemeColors.borderBright, + ), + ), + + ///Not supported now + ///Switch for setting skippable and change response variables. + // ListTile( + // horizontalTitleGap: 1, + // enabled: false, + // dense: true, + // contentPadding: EdgeInsets.zero, + // title: HMSSubheadingText( + // text: "Allow to skip", + // textColor: HMSThemeColors.onSurfaceMediumEmphasis), + // trailing: SizedBox( + // height: 24, + // width: 40, + // child: FittedBox( + // fit: BoxFit.contain, + // child: CupertinoSwitch( + // value: _isSkippable, + // onChanged: (value) => setIsSkippable(value), + // activeColor: HMSThemeColors.primaryDefault, + // ), + // ), + // ), + // ), + + ///Not supported as of now + // ListTile( + // horizontalTitleGap: 1, + // enabled: false, + // dense: true, + // contentPadding: EdgeInsets.zero, + // title: HMSSubheadingText( + // text: "Allow to vote again", + // textColor: HMSThemeColors.onSurfaceMediumEmphasis), + // trailing: SizedBox( + // height: 24, + // width: 40, + // child: FittedBox( + // fit: BoxFit.contain, + // child: CupertinoSwitch( + // value: _canChangeResponse, + // onChanged: (value) => setCanChangeResponse(value), + // activeColor: HMSThemeColors.primaryDefault, + // ), + // ), + // ), + // ), + + ///Save the question + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // if (widget.totalQuestions > 1) + // HMSEmbeddedButton( + // onTap: () => + // widget.deleteQuestionCallback(widget.questionBuilder), + // isActive: true, + // onColor: HMSThemeColors.surfaceDefault, + // child: SvgPicture.asset( + // "packages/hms_room_kit/lib/src/assets/icons/delete_poll.svg", + // colorFilter: ColorFilter.mode( + // HMSThemeColors.onSurfaceHighEmphasis, + // BlendMode.srcIn), + // fit: BoxFit.scaleDown, + // ), + // ), + // const SizedBox( + // width: 8, + // ), + ElevatedButton( + style: ButtonStyle( + elevation: MaterialStateProperty.all(0), + backgroundColor: (_questionController.text.isNotEmpty) + ? MaterialStateProperty.all( + HMSThemeColors.secondaryDefault) + : MaterialStateProperty.all( + HMSThemeColors.secondaryDim), + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () { + saveQuestion(); + }, + child: HMSTitleText( + text: "Save", + textColor: _isPollValid() + ? HMSThemeColors.onSecondaryHighEmphasis + : HMSThemeColors.onSecondaryLowEmphasis)) + ], + ) + ], + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_form.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_form.dart new file mode 100644 index 000000000..b36bedda5 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_form.dart @@ -0,0 +1,223 @@ +///Package imports +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:hms_room_kit/src/widgets/poll_widgets/poll_creation_widgets/poll_question_bottom_sheet.dart'; +import 'package:provider/provider.dart'; + +///Project imports +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_listenable_button.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; + +///[PollForm] widget renders the poll creation form with poll title. +class PollForm extends StatefulWidget { + const PollForm({super.key}); + + @override + State createState() => _PollFormState(); +} + +class _PollFormState extends State { + late TextEditingController _pollNameController; + bool _hideVoteCount = false; + // bool _isAnonymous = false; + + @override + void initState() { + _pollNameController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _pollNameController.dispose(); + super.dispose(); + } + + ///This method sets the value of the hide vote count switch + void setHideVoteCount(bool value) { + setState(() { + _hideVoteCount = value; + }); + } + + ///This method sets the value of the is anonymous switch + // void setIsAnonymous(bool value) { + // setState(() { + // _isAnonymous = value; + // }); + // } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HMSSubheadingText( + text: "Poll Name", textColor: HMSThemeColors.onSurfaceHighEmphasis), + const SizedBox( + height: 8, + ), + + ///Text Field + SizedBox( + height: 48, + child: TextField( + cursorColor: HMSThemeColors.onSurfaceHighEmphasis, + onTapOutside: (event) => + FocusManager.instance.primaryFocus?.unfocus(), + textInputAction: TextInputAction.done, + textCapitalization: TextCapitalization.words, + style: HMSTextStyle.setTextStyle( + color: HMSThemeColors.onSurfaceHighEmphasis), + controller: _pollNameController, + keyboardType: TextInputType.text, + onChanged: (value) { + setState(() {}); + }, + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + fillColor: HMSThemeColors.surfaceDefault, + filled: true, + hintText: "Name this Poll", + hintStyle: HMSTextStyle.setTextStyle( + color: HMSThemeColors.onSurfaceLowEmphasis, + height: 1.5, + fontSize: 16, + letterSpacing: 0.5, + fontWeight: FontWeight.w400), + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(8)), + borderSide: + BorderSide(color: HMSThemeColors.primaryDefault)), + border: const OutlineInputBorder(borderSide: BorderSide.none)), + ), + ), + + ///Padding + const SizedBox( + height: 24, + ), + + ///Divider + Padding( + padding: const EdgeInsets.symmetric( + vertical: 12.0, + ), + child: Divider( + height: 5, + color: HMSThemeColors.borderBright, + ), + ), + + ///Settings section + Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: HMSSubheadingText( + text: "Settings", + textColor: HMSThemeColors.onSurfaceHighEmphasis), + ), + + ///Hide vote count switch + ListTile( + horizontalTitleGap: 1, + enabled: false, + dense: true, + contentPadding: EdgeInsets.zero, + title: HMSSubheadingText( + text: "Hide vote count", + textColor: HMSThemeColors.onSurfaceMediumEmphasis), + trailing: SizedBox( + height: 24, + width: 40, + child: FittedBox( + fit: BoxFit.contain, + child: CupertinoSwitch( + value: _hideVoteCount, + onChanged: (value) => setHideVoteCount(value), + activeColor: HMSThemeColors.primaryDefault, + ), + ), + ), + ), + + ///Is the poll anonymous switch + // ListTile( + // horizontalTitleGap: 1, + // enabled: false, + // dense: true, + // contentPadding: EdgeInsets.zero, + // title: Row( + // children: [ + // HMSSubheadingText( + // text: "Make results anonymous", + // textColor: HMSThemeColors.onSurfaceMediumEmphasis), + // // const SizedBox( + // // width: 8, + // // ), + // // SvgPicture.asset( + // // "packages/hms_room_kit/lib/src/assets/icons/info.svg", + // // height: 16, + // // width: 16, + // // colorFilter: ColorFilter.mode( + // // HMSThemeColors.onSurfaceLowEmphasis, BlendMode.srcIn), + // // ) + // ], + // ), + // trailing: SizedBox( + // height: 24, + // width: 40, + // child: FittedBox( + // fit: BoxFit.contain, + // child: CupertinoSwitch( + // value: _isAnonymous, + // onChanged: (value) => setIsAnonymous(value), + // activeColor: HMSThemeColors.primaryDefault, + // ), + // ), + // ), + // ), + + ///Padding + const SizedBox( + height: 24, + ), + + ///Button to add question + SizedBox( + height: 40, + child: HMSListenableButton( + textController: _pollNameController, + width: MediaQuery.of(context).size.width - 40, + onPressed: () { + if (_pollNameController.text.trim().isEmpty) return; + var meetingStore = context.read(); + 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: meetingStore, + child: PollQuestionBottomSheet( + pollName: _pollNameController.text.trim(), + )), + ); + }, + childWidget: HMSTitleText( + text: 'Create Poll', + textColor: _pollNameController.text.isEmpty + ? HMSThemeColors.onPrimaryLowEmphasis + : HMSThemeColors.onPrimaryHighEmphasis, + )), + ) + ], + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_question_bottom_sheet.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_question_bottom_sheet.dart new file mode 100644 index 000000000..c0e1b3f0d --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_question_bottom_sheet.dart @@ -0,0 +1,262 @@ +///Package imports +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hms_room_kit/src/widgets/poll_widgets/poll_creation_widgets/create_poll_form.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_cross_button.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'; +import 'package:hms_room_kit/src/widgets/poll_widgets/poll_creation_widgets/saved_question_widget.dart'; + +///[PollQuestionBottomSheet] renders the poll question form sheet +class PollQuestionBottomSheet extends StatefulWidget { + final String pollName; + + const PollQuestionBottomSheet({Key? key, required this.pollName}) + : super(key: key); + + @override + State createState() => + _PollQuestionBottomSheetState(); +} + +class _PollQuestionBottomSheetState extends State { + late HMSPollBuilder pollBuilder; + bool _isPollValid = false; + Map pollQuestionBuilders = {}; + + @override + void initState() { + ///Here we create a new poll builder object with single question + pollBuilder = HMSPollBuilder(); + pollQuestionBuilders[HMSPollQuestionBuilder()] = false; + + ///Setting the title of the poll + pollBuilder.withTitle = widget.pollName; + super.initState(); + } + + ///This function adds a new question builder + void _addQuestion() { + pollQuestionBuilders[HMSPollQuestionBuilder()] = false; + setState(() {}); + } + + void _deleteQuestionCallback(HMSPollQuestionBuilder pollQuestionBuilder) { + pollQuestionBuilders.remove(pollQuestionBuilder); + setState(() {}); + } + + void _savePollCallback(HMSPollQuestionBuilder pollQuestionBuilder) { + pollQuestionBuilders[pollQuestionBuilder] = true; + _checkPollValidity(); + setState(() {}); + } + + void _editPollCallback(HMSPollQuestionBuilder pollQuestionBuilder) { + pollQuestionBuilders[pollQuestionBuilder] = false; + _isPollValid = false; + setState(() {}); + } + + void _checkPollValidity() { + var isValid = true; + pollQuestionBuilders.forEach((key, value) { + isValid &= value; + }); + _isPollValid = isValid; + } + + @override + Widget build(BuildContext context) { + return FractionallySizedBox( + heightFactor: 0.87, + child: Padding( + padding: const EdgeInsets.only(top: 12.0, left: 16, right: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ///Top bar + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + IconButton( + icon: Icon( + Icons.arrow_back_ios_new, + size: 16, + color: HMSThemeColors.onSurfaceHighEmphasis, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + Expanded( + child: HMSTitleText( + text: widget.pollName, + fontSize: 20, + textColor: HMSThemeColors.onSurfaceHighEmphasis, + maxLines: 2, + ), + ), + ], + ), + ), + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HMSCrossButton(), + ], + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 16), + child: Divider( + color: HMSThemeColors.borderDefault, + height: 5, + ), + ), + ], + ), + Expanded( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ///List to render poll form + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: pollQuestionBuilders.length, + shrinkWrap: true, + itemBuilder: (context, index) => Padding( + padding: + const EdgeInsets.symmetric(vertical: 8.0), + child: pollQuestionBuilders.values + .elementAt(index) + ? SavedQuestionWidget( + questionNumber: index, + totalQuestions: + pollQuestionBuilders.length, + pollQuestionBuilder: pollQuestionBuilders + .keys + .elementAt(index), + editPollCallback: _editPollCallback, + ) + : CreatePollForm( + questionNumber: index, + totalQuestions: + pollQuestionBuilders.length, + questionType: pollQuestionBuilders.keys + .elementAt(index) + .type, + questionController: TextEditingController( + text: pollQuestionBuilders.keys + .elementAt(index) + .text), + optionsTextController: + pollQuestionBuilders.keys + .elementAt(index) + .pollOptions + .map((e) => TextEditingController( + text: e)) + .toList(), + deleteQuestionCallback: + _deleteQuestionCallback, + questionBuilder: pollQuestionBuilders.keys + .elementAt(index), + savePollCallback: _savePollCallback, + ), + )), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: GestureDetector( + onTap: () => _addQuestion(), + child: Row( + children: [ + SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/add_option.svg", + width: 24, + height: 24, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceMediumEmphasis, + BlendMode.srcIn)), + const SizedBox( + width: 16, + ), + HMSSubheadingText( + text: "Add another question", + textColor: HMSThemeColors.onSurfaceMediumEmphasis, + ), + ], + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton( + style: ButtonStyle( + shadowColor: MaterialStateProperty.all( + HMSThemeColors.surfaceDim), + backgroundColor: _isPollValid + ? MaterialStateProperty.all( + HMSThemeColors.primaryDefault) + : MaterialStateProperty.all( + HMSThemeColors.primaryDisabled), + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () { + if (_isPollValid) { + pollQuestionBuilders.forEach((key, value) { + if (value) { + pollBuilder.addQuestion(key); + } + }); + pollBuilder.withAnonymous = false; + pollBuilder.withCategory = HMSPollCategory.poll; + pollBuilder.withMode = + HMSPollUserTrackingMode.user_id; + + context + .read() + .quickStartPoll(pollBuilder); + Navigator.pop(context); + Navigator.pop(context); + } + }, + child: Center( + child: HMSTitleText( + text: "Launch Poll", + textColor: _isPollValid + ? HMSThemeColors.onPrimaryHighEmphasis + : HMSThemeColors.onPrimaryLowEmphasis, + )), + ) + ], + ), + const SizedBox( + height: 64, + ) + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_question_card.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_question_card.dart new file mode 100644 index 000000000..0543c44dc --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/poll_question_card.dart @@ -0,0 +1,99 @@ +///Package imports +import 'package:flutter/material.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/widgets/bottom_sheets/poll_vote_bottom_sheet.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:provider/provider.dart'; + +///Project imports +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/model/poll_store.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_button.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/live_badge.dart'; + +///[PollQuestionCard] widget renders the cards for poll which are either started, ended, created or are in draft +class PollQuestionCard extends StatelessWidget { + const PollQuestionCard({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 24), + child: Container( + decoration: BoxDecoration( + color: HMSThemeColors.surfaceDefault, + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Selector( + selector: (_, hmsPollStore) => hmsPollStore.poll.title, + builder: (_, title, __) { + return HMSTitleText( + text: title, + textColor: HMSThemeColors.onSurfaceHighEmphasis, + letterSpacing: 0.15, + maxLines: 3, + ); + }), + ), + Selector( + selector: (_, hmsPollStore) => hmsPollStore.poll.state, + builder: (_, pollState, __) { + return pollState == HMSPollState.stopped + ? LiveBadge( + text: "ENDED", + badgeColor: HMSThemeColors.surfaceBrighter, + width: 50, + ) + : const LiveBadge(); + }) + ], + ), + const SizedBox( + height: 16, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HMSButton( + width: MediaQuery.of(context).size.width * 0.23, + onPressed: () { + var meetingStore = context.read(); + var pollStore = context.read(); + 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: meetingStore, + child: ChangeNotifierProvider.value( + value: pollStore, + child: const PollVoteBottomSheet(), + ), + )); + }, + childWidget: HMSTitleText( + text: "View", + textColor: HMSThemeColors.onSurfaceHighEmphasis)) + ], + ) + ], + ), + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/saved_question_widget.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/saved_question_widget.dart new file mode 100644 index 000000000..bfd793927 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_creation_widgets/saved_question_widget.dart @@ -0,0 +1,123 @@ +///Package imports +import 'package:flutter/material.dart'; +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +///Project imports +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; + +///[SavedQuestionWidget] widget renders the saved question UI +class SavedQuestionWidget extends StatelessWidget { + final int questionNumber; + final int totalQuestions; + final HMSPollQuestionBuilder pollQuestionBuilder; + final Function editPollCallback; + + const SavedQuestionWidget( + {super.key, + required this.questionNumber, + required this.totalQuestions, + required this.pollQuestionBuilder, + required this.editPollCallback}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: HMSThemeColors.surfaceDefault, + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + HMSTitleText( + text: "QUESTION ${questionNumber + 1} OF $totalQuestions: ", + textColor: HMSThemeColors.onSurfaceLowEmphasis, + fontSize: 10, + letterSpacing: 1.5, + lineHeight: 16), + HMSTitleText( + fontSize: 10, + letterSpacing: 1.5, + lineHeight: 16, + text: Utilities.getQuestionType(pollQuestionBuilder.type), + textColor: HMSThemeColors.onSurfaceLowEmphasis) + ], + ), + const SizedBox( + height: 16, + ), + Theme( + data: ThemeData().copyWith(dividerColor: Colors.transparent), + child: ExpansionTile( + expandedCrossAxisAlignment: CrossAxisAlignment.start, + expandedAlignment: Alignment.centerLeft, + initiallyExpanded: true, + iconColor: HMSThemeColors.onSurfaceHighEmphasis, + collapsedIconColor: HMSThemeColors.onSurfaceHighEmphasis, + tilePadding: EdgeInsets.zero, + childrenPadding: EdgeInsets.zero, + title: HMSTitleText( + text: pollQuestionBuilder.text ?? "", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + fontWeight: FontWeight.w400, + maxLines: 3, + ), + children: pollQuestionBuilder.pollOptions + .map((e) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: HMSSubheadingText( + text: e, + textColor: HMSThemeColors.onSurfaceMediumEmphasis, + maxLines: 3, + ), + )) + .toList()), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // HMSEmbeddedButton( + // onTap: () => {}, + // // widget.deleteQuestionCallback(widget.questionBuilder), + // isActive: true, + // onColor: HMSThemeColors.surfaceDefault, + // child: SvgPicture.asset( + // "packages/hms_room_kit/lib/src/assets/icons/delete_poll.svg", + // colorFilter: ColorFilter.mode( + // HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), + // fit: BoxFit.scaleDown, + // ), + // ), + const SizedBox( + width: 16, + ), + ElevatedButton( + style: ButtonStyle( + elevation: MaterialStateProperty.all(0), + backgroundColor: MaterialStateProperty.all( + HMSThemeColors.secondaryDefault), + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () { + editPollCallback(pollQuestionBuilder); + }, + child: HMSTitleText( + text: "Edit", + textColor: HMSThemeColors.onSecondaryHighEmphasis)) + ], + ) + ], + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_quiz_selection_button.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_quiz_selection_button.dart new file mode 100644 index 000000000..8505133f1 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_quiz_selection_button.dart @@ -0,0 +1,68 @@ +///Package imports +import 'package:flutter/cupertino.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hms_room_kit/hms_room_kit.dart'; + +///[PollQuizSelectionButton] widget renders the option with an icon and text side by side +///Used here for poll or quiz selection button +class PollQuizSelectionButton extends StatelessWidget { + final bool isSelected; + final String iconName; + final String text; + + const PollQuizSelectionButton( + {Key? key, + required this.isSelected, + required this.iconName, + required this.text}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 64, + width: (MediaQuery.of(context).size.width - 48) / 2, + decoration: BoxDecoration( + border: Border.all( + color: isSelected + ? HMSThemeColors.primaryDefault + : HMSThemeColors.borderBright, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Container( + height: 48, + width: 48, + decoration: BoxDecoration( + color: HMSThemeColors.borderBright, + border: Border.all( + color: isSelected + ? HMSThemeColors.primaryDefault + : HMSThemeColors.borderBright, + ), + borderRadius: BorderRadius.circular(4), + ), + child: Center( + child: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/$iconName.svg", + fit: BoxFit.scaleDown, + ), + )), + const SizedBox( + width: 16, + ), + HMSTitleText( + text: text, + textColor: HMSThemeColors.onSurfaceHighEmphasis, + letterSpacing: 0.15, + ), + ], + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_quiz_selection_widget.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_quiz_selection_widget.dart new file mode 100644 index 000000000..5f86b1b4b --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/poll_quiz_selection_widget.dart @@ -0,0 +1,62 @@ +///Package imports +import 'package:flutter/cupertino.dart'; +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/widgets/poll_widgets/poll_quiz_selection_button.dart'; + +///[PollQuizSelectionWidget] renders the widget for poll or quiz selection +class PollQuizSelectionWidget extends StatefulWidget { + const PollQuizSelectionWidget({super.key}); + + @override + State createState() => + _PollQuizSelectionWidgetState(); +} + +class _PollQuizSelectionWidgetState extends State { + int index = 0; + + void updateSelection(newIndex) { + setState(() { + index = newIndex; + }); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HMSSubtitleText( + text: "Select the type you want to continue with", + textColor: HMSThemeColors.onSurfaceMediumEmphasis), + const SizedBox( + height: 8, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () => updateSelection(0), + child: PollQuizSelectionButton( + isSelected: (index == 0), + iconName: "poll", + text: "Poll", + ), + ), + const SizedBox( + width: 16, + ), + GestureDetector( + onTap: () => updateSelection(1), + child: PollQuizSelectionButton( + isSelected: (index == 1), + iconName: "quiz", + text: "Quiz", + ), + ), + ], + ), + ], + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/voting_flow_widgets/poll_result_card.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/voting_flow_widgets/poll_result_card.dart new file mode 100644 index 000000000..60b7f46f5 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/voting_flow_widgets/poll_result_card.dart @@ -0,0 +1,177 @@ +///Package imports +import 'package:flutter/material.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +///Project imports +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; + +///[PollResultCard] renders the results for polls +class PollResultCard extends StatelessWidget { + final int questionNumber; + final int totalQuestions; + final HMSPollQuestion question; + final int totalVotes; + final bool isVoteCountHidden; + + const PollResultCard( + {super.key, + required this.questionNumber, + required this.totalQuestions, + required this.question, + required this.totalVotes, + required this.isVoteCountHidden}); + + bool isSelectedOption(int index) { + if (question.type == HMSPollQuestionType.singleChoice) { + for (var response in question.myResponses) { + if (index == response.selectedOption) { + return true; + } + } + } else if (question.type == HMSPollQuestionType.multiChoice) { + for (var response in question.myResponses) { + if (response.selectedOptions?.contains(index) ?? false) { + return true; + } + } + } + return false; + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 24), + child: Container( + decoration: BoxDecoration( + color: HMSThemeColors.surfaceDefault, + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + HMSTitleText( + text: + "QUESTION ${questionNumber + 1} OF $totalQuestions: ", + textColor: HMSThemeColors.onSurfaceLowEmphasis, + fontSize: 10, + letterSpacing: 1.5, + lineHeight: 16), + HMSTitleText( + fontSize: 10, + letterSpacing: 1.5, + lineHeight: 16, + text: Utilities.getQuestionType(question.type), + textColor: HMSThemeColors.onSurfaceLowEmphasis) + ], + ), + const SizedBox( + height: 16, + ), + HMSTitleText( + text: question.text, + textColor: HMSThemeColors.onSurfaceHighEmphasis, + maxLines: 3, + fontWeight: FontWeight.w400, + ), + const SizedBox( + height: 16, + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: question.options.length, + itemBuilder: (BuildContext context, index) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: HMSSubheadingText( + text: question.options[index].text ?? "", + maxLines: 3, + textColor: + HMSThemeColors.onSurfaceHighEmphasis), + ), + if (isVoteCountHidden && + isSelectedOption( + question.options[index].index)) + Checkbox( + activeColor: + HMSThemeColors.onSurfaceHighEmphasis, + checkColor: HMSThemeColors.surfaceDefault, + shape: const CircleBorder(), + value: true, + onChanged: (value) {}), + if (!isVoteCountHidden) + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: HMSSubheadingText( + text: + "${question.options[index].voteCount.toString()} vote${question.options[index].voteCount > 1 ? "s" : ""}", + textColor: HMSThemeColors + .onSurfaceMediumEmphasis), + ) + ], + ), + if (!isVoteCountHidden) + const SizedBox( + height: 8, + ), + if (!isVoteCountHidden) + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + height: 8, + child: Row( + children: [ + Expanded( + flex: (question.options[index].voteCount), + child: Container( + decoration: BoxDecoration( + color: HMSThemeColors.primaryDefault, + borderRadius: + BorderRadius.circular(8), + ), + )), + Expanded( + flex: totalVotes - + question.options[index].voteCount, + child: Container( + decoration: BoxDecoration( + color: HMSThemeColors.surfaceBright, + borderRadius: BorderRadius.circular(8), + ))) + ], + ), + ) + ], + ), + ); + }), + if (question.myResponses.isNotEmpty) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HMSTitleText( + text: "Voted", + textColor: HMSThemeColors.onSurfaceLowEmphasis) + ], + ) + ], + ), + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/poll_widgets/voting_flow_widgets/poll_vote_card.dart b/packages/hms_room_kit/lib/src/widgets/poll_widgets/voting_flow_widgets/poll_vote_card.dart new file mode 100644 index 000000000..2d8e393a2 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/poll_widgets/voting_flow_widgets/poll_vote_card.dart @@ -0,0 +1,180 @@ +///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/hms_room_kit.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/model/poll_store.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_button.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; + +///[PollVoteCard] renders the vote card for polls +class PollVoteCard extends StatefulWidget { + final int questionNumber; + final int totalQuestions; + final HMSPollQuestion question; + + const PollVoteCard( + {super.key, + required this.questionNumber, + required this.totalQuestions, + required this.question}); + + @override + State createState() => _PollVoteCardState(); +} + +class _PollVoteCardState extends State { + HMSPollQuestionOption? selectedOption; + List selectedOptions = []; + bool isPollAnswerValid = false; + + void resetPollAnswerValidity() { + if (selectedOption != null || selectedOptions.isNotEmpty) { + isPollAnswerValid = true; + } else { + isPollAnswerValid = false; + } + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 24), + child: Container( + decoration: BoxDecoration( + color: HMSThemeColors.surfaceDefault, + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + HMSTitleText( + text: + "QUESTION ${widget.questionNumber + 1} OF ${widget.totalQuestions}: ", + textColor: HMSThemeColors.onSurfaceLowEmphasis, + fontSize: 10, + letterSpacing: 1.5, + lineHeight: 16), + HMSTitleText( + fontSize: 10, + letterSpacing: 1.5, + lineHeight: 16, + text: Utilities.getQuestionType(widget.question.type), + textColor: HMSThemeColors.onSurfaceLowEmphasis) + ], + ), + const SizedBox( + height: 16, + ), + HMSTitleText( + text: widget.question.text, + textColor: HMSThemeColors.onSurfaceHighEmphasis, + maxLines: 3, + fontWeight: FontWeight.w400, + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.question.options.length, + itemBuilder: (BuildContext context, index) { + ///TODO: Add dynamic padding based on text length + return Row( + children: [ + Checkbox( + activeColor: HMSThemeColors.onSurfaceHighEmphasis, + checkColor: HMSThemeColors.surfaceDefault, + value: ((widget.question.type == + HMSPollQuestionType.singleChoice) + ? selectedOption == + widget.question.options[index] + : selectedOptions + .contains(widget.question.options[index])), + shape: widget.question.type == + HMSPollQuestionType.singleChoice + ? const CircleBorder() + : const RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(4))), + onChanged: (value) { + if (value == true) { + if (widget.question.type == + HMSPollQuestionType.singleChoice) { + selectedOption = + widget.question.options[index]; + } else if (widget.question.type == + HMSPollQuestionType.multiChoice) { + selectedOptions + .add(widget.question.options[index]); + } + } else { + if (widget.question.type == + HMSPollQuestionType.multiChoice) { + selectedOptions + .remove(widget.question.options[index]); + } + } + resetPollAnswerValidity(); + setState(() {}); + }), + Expanded( + child: HMSSubheadingText( + text: widget.question.options[index].text ?? "", + maxLines: 3, + textColor: HMSThemeColors.onSurfaceHighEmphasis), + ) + ], + ); + }), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HMSButton( + width: MediaQuery.of(context).size.width * 0.21, + onPressed: () { + if (isPollAnswerValid) { + if (widget.question.type == + HMSPollQuestionType.singleChoice && + selectedOption != null) { + context + .read() + .addSingleChoicePollResponse( + context.read().poll, + widget.question, + selectedOption!); + } else if (widget.question.type == + HMSPollQuestionType.multiChoice && + selectedOptions.isNotEmpty) { + context + .read() + .addMultiChoicePollResponse( + context.read().poll, + widget.question, + selectedOptions); + } + } + }, + buttonBackgroundColor: isPollAnswerValid + ? HMSThemeColors.primaryDefault + : HMSThemeColors.primaryDisabled, + childWidget: HMSTitleText( + text: "Vote", + textColor: isPollAnswerValid + ? HMSThemeColors.onPrimaryHighEmphasis + : HMSThemeColors.onPrimaryLowEmphasis)) + ], + ) + ], + ), + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/toasts/hms_poll_start_toast.dart b/packages/hms_room_kit/lib/src/widgets/toasts/hms_poll_start_toast.dart new file mode 100644 index 000000000..42967add7 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/toasts/hms_poll_start_toast.dart @@ -0,0 +1,83 @@ +///Dart imports +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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/model/poll_store.dart'; +import 'package:hms_room_kit/src/widgets/bottom_sheets/poll_vote_bottom_sheet.dart'; +import 'package:hms_room_kit/src/widgets/common_widgets/hms_subheading_text.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_toast.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_toast_button.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_toasts_type.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:provider/provider.dart'; + +class HMSPollStartToast extends StatelessWidget { + final HMSPoll poll; + final MeetingStore meetingStore; + + const HMSPollStartToast( + {Key? key, required this.poll, required this.meetingStore}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return HMSToast( + leading: SvgPicture.asset( + "packages/hms_room_kit/lib/src/assets/icons/polls.svg", + height: 24, + width: 24, + colorFilter: ColorFilter.mode( + HMSThemeColors.onSurfaceHighEmphasis, BlendMode.srcIn), + ), + subtitle: SizedBox( + width: MediaQuery.of(context).size.width * 0.5, + child: HMSSubheadingText( + text: + "${poll.createdBy?.name.substring(0, math.min(8, poll.createdBy?.name.length ?? 0)) ?? ""}${(poll.createdBy?.name.length ?? 0) > 8 ? "..." : ""} started a new poll", + textColor: HMSThemeColors.onSurfaceHighEmphasis, + fontWeight: FontWeight.w600, + letterSpacing: 0.1, + maxLines: 2, + ), + ), + action: HMSToastButton( + buttonTitle: "Vote", + action: () { + var pollStore = context.read(); + 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: meetingStore, + child: ChangeNotifierProvider.value( + value: pollStore, + child: const PollVoteBottomSheet(), + ))); + meetingStore.removeToast(HMSToastsType.pollStartedToast, + data: poll.pollId); + }, + height: 36, + buttonColor: HMSThemeColors.secondaryDefault, + textColor: HMSThemeColors.onSecondaryHighEmphasis, + ), + cancelToastButton: GestureDetector( + onTap: () => meetingStore.removeToast(HMSToastsType.pollStartedToast, + data: poll.pollId), + child: Icon( + Icons.close, + color: HMSThemeColors.onSurfaceHighEmphasis, + size: 24, + ), + ), + ); + } +} diff --git a/packages/hms_room_kit/lib/src/widgets/toasts/hms_toasts_type.dart b/packages/hms_room_kit/lib/src/widgets/toasts/hms_toasts_type.dart index f366d16d0..491fb7430 100644 --- a/packages/hms_room_kit/lib/src/widgets/toasts/hms_toasts_type.dart +++ b/packages/hms_room_kit/lib/src/widgets/toasts/hms_toasts_type.dart @@ -5,5 +5,6 @@ enum HMSToastsType { localScreenshareToast, roleChangeDeclineToast, chatPauseResumeToast, - errorToast + errorToast, + pollStartedToast } diff --git a/packages/hms_room_kit/lib/src/widgets/toasts/toast_widget.dart b/packages/hms_room_kit/lib/src/widgets/toasts/toast_widget.dart new file mode 100644 index 000000000..6607376d6 --- /dev/null +++ b/packages/hms_room_kit/lib/src/widgets/toasts/toast_widget.dart @@ -0,0 +1,79 @@ +///Package imports +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +///Project imports +import 'package:hms_room_kit/hms_room_kit.dart'; +import 'package:hms_room_kit/src/meeting/meeting_store.dart'; +import 'package:hms_room_kit/src/model/poll_store.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_bring_on_stage_toast.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_chat_pause_resume_toast.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_local_screen_share_toast.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_poll_start_toast.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_recording_error_toast.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_role_change_decline_toast.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_toast_model.dart'; +import 'package:hms_room_kit/src/widgets/toasts/hms_toasts_type.dart'; + +///[ToastWidget] returns toast based on the toast type +class ToastWidget extends StatelessWidget { + final HMSToastModel toast; + final int index; + final int toastsCount; + final MeetingStore meetingStore; + + const ToastWidget({ + super.key, + required this.toast, + required this.index, + required this.toastsCount, + required this.meetingStore, + }); + + @override + Widget build(BuildContext context) { + switch (toast.hmsToastType) { + case HMSToastsType.roleChangeToast: + return HMSBringOnStageToast( + toastColor: Utilities.getToastColor(index, toastsCount), + peer: toast.toastData, + meetingStore: meetingStore, + ); + case HMSToastsType.recordingErrorToast: + return HMSRecordingErrorToast( + recordingError: toast.toastData, + meetingStore: meetingStore, + ); + case HMSToastsType.localScreenshareToast: + return HMSLocalScreenShareToast( + toastColor: Utilities.getToastColor(index, toastsCount), + meetingStore: meetingStore, + ); + case HMSToastsType.roleChangeDeclineToast: + return HMSRoleChangeDeclineToast( + peer: toast.toastData, + toastColor: Utilities.getToastColor(index, toastsCount), + meetingStore: meetingStore, + ); + case HMSToastsType.chatPauseResumeToast: + return HMSChatPauseResumeToast( + isChatEnabled: toast.toastData["enabled"], + userName: toast.toastData["updatedBy"], + meetingStore: meetingStore, + ); + case HMSToastsType.pollStartedToast: + return ChangeNotifierProvider.value( + value: toast.toastData! as HMSPollStore, + child: Transform.scale( + scale: Utilities.getToastScale(index, toastsCount), + child: HMSPollStartToast( + poll: toast.toastData.poll, + meetingStore: meetingStore, + ), + ), + ); + default: + return const SizedBox(); + } + } +} diff --git a/packages/hms_room_kit/pubspec.lock b/packages/hms_room_kit/pubspec.lock index 7ec7253f4..d803a2f6b 100644 --- a/packages/hms_room_kit/pubspec.lock +++ b/packages/hms_room_kit/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: archive - sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.4.9" + version: "3.4.10" args: dependency: transitive description: @@ -204,10 +204,10 @@ packages: dependency: "direct main" description: name: hmssdk_flutter - sha256: ff1697824b42d31cb093fd4319c8fa3ba6872b7877707630062ed3cd9cf40813 + sha256: bad4ff87c677970f0c9acfcd4e84b60089461e1c1eef23e3a3ae6ab191484b95 url: "https://pub.dev" source: hosted - version: "1.9.8" + version: "1.9.9" http: dependency: transitive description: @@ -324,10 +324,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_android: dependency: transitive description: @@ -340,10 +340,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -356,10 +356,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -420,26 +420,26 @@ packages: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" provider: dependency: "direct main" description: @@ -484,10 +484,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: @@ -500,10 +500,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_web: dependency: transitive description: @@ -601,26 +601,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.2.4" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: @@ -641,10 +641,10 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" url_launcher_web: dependency: transitive description: @@ -673,26 +673,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_math: dependency: transitive description: @@ -729,10 +729,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: diff --git a/packages/hms_room_kit/pubspec.yaml b/packages/hms_room_kit/pubspec.yaml index ae5649d88..2916cfb6c 100644 --- a/packages/hms_room_kit/pubspec.yaml +++ b/packages/hms_room_kit/pubspec.yaml @@ -1,6 +1,6 @@ name: hms_room_kit description: 100ms Room Kit provides simple & easy to use UI components to build Live Streaming & Video Conferencing experiences in your apps. -version: 1.0.11 +version: 1.0.12 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues @@ -14,7 +14,7 @@ dependencies: flutter: sdk: flutter - hmssdk_flutter: 1.9.8 + hmssdk_flutter: 1.9.9 intl: ^0.18.0 permission_handler: ^11.0.0 provider: ^6.0.5 diff --git a/packages/hmssdk_flutter/CHANGELOG.md b/packages/hmssdk_flutter/CHANGELOG.md index 611dd8110..d47ace539 100644 --- a/packages/hmssdk_flutter/CHANGELOG.md +++ b/packages/hmssdk_flutter/CHANGELOG.md @@ -5,6 +5,34 @@ | hms_room_kit | [![Pub Version](https://img.shields.io/pub/v/hms_room_kit)](https://pub.dev/packages/hms_room_kit) | | hmssdk_flutter | [![Pub Version](https://img.shields.io/pub/v/hmssdk_flutter)](https://pub.dev/packages/hmssdk_flutter) | +# 1.9.9 - 2024-02-12 + +| Package | Version | +| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| hms_room_kit | 1.0.12 | +| hmssdk_flutter | 1.9.9 | + +### ✨ Added + +- Introducing Polls + + - APIs for Comprehensive Poll Management: + - `quickStartPoll` to start polls + - `addSingleChoicePollResponse` & `addMultiChoicePollResponse` for adding single and multi choice response + - `stopPoll` to stop polls + + - Poll Update Listeners for Real-Time Notifications: + - Use `addPollUpdateListener` to start listening to poll updates + - Use `removePollUpdateListener` to stop listening to poll updates + + - Enhanced Poll Permissions within `HMSPermissions`: + - `pollRead` property ensures controlled read access to poll results and details. + - `pollWrite` property enables secure write access, allowing for poll creation and response submission. + +Updated to Android SDK 2.9.0 & iOS SDK 1.5.0 + +**Full Changelog**: [1.9.8...1.9.9](https://github.com/100mslive/100ms-flutter/compare/1.9.8...1.9.9) + # 1.9.8 - 2024-02-01 | Package | Version | 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 df1bb4cf6..8b76aca57 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 @@ -3,7 +3,6 @@ package live.hms.hmssdk_flutter import android.app.Activity import android.content.BroadcastReceiver import android.content.Context -import android.content.Context.* import android.content.Intent import android.content.IntentFilter import android.media.projection.MediaProjectionManager @@ -29,6 +28,7 @@ import kotlinx.coroutines.launch import live.hms.hmssdk_flutter.Constants.Companion.METHOD_CALL import live.hms.hmssdk_flutter.hls_player.HMSHLSPlayerAction import live.hms.hmssdk_flutter.methods.* +import live.hms.hmssdk_flutter.poll_extension.HMSPollExtension import live.hms.hmssdk_flutter.views.HMSHLSPlayerFactory import live.hms.hmssdk_flutter.views.HMSTextureView import live.hms.hmssdk_flutter.views.HMSVideoViewFactory @@ -36,7 +36,11 @@ import live.hms.video.audio.HMSAudioManager.* import live.hms.video.connection.stats.* 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.* import live.hms.video.sdk.models.* import live.hms.video.sdk.models.enums.* @@ -61,12 +65,14 @@ class HmssdkFlutterPlugin : private var rtcStatsChannel: EventChannel? = null private var sessionStoreChannel: EventChannel? = null var hlsPlayerChannel: 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 private var rtcSink: EventChannel.EventSink? = null private var sessionStoreSink: EventChannel.EventSink? = null var hlsPlayerSink: EventChannel.EventSink? = null + private var pollsSink: EventChannel.EventSink? = null private lateinit var activity: Activity var hmssdk: HMSSDK? = null private lateinit var hmsVideoFactory: HMSVideoViewFactory @@ -106,6 +112,9 @@ class HmssdkFlutterPlugin : this.hlsPlayerChannel = EventChannel(flutterPluginBinding.binaryMessenger, "hls_player_channel") + this.pollsEventChannel = + EventChannel(flutterPluginBinding.binaryMessenger, "polls_event_channel") + this.meetingEventChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "Meeting event channel not found") this.channel?.setMethodCallHandler(this) ?: Log.e("Channel Error", "Event channel not found") this.previewChannel?.setStreamHandler(this) ?: Log.e("Channel Error", "Preview channel not found") @@ -113,6 +122,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.hmsVideoFactory = HMSVideoViewFactory(this) this.hmsHLSPlayerFactory = HMSHLSPlayerFactory(this) @@ -276,6 +286,10 @@ 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" -> { + pollActions(call,result) + } + else -> { result.notImplemented() } @@ -453,6 +467,21 @@ class HmssdkFlutterPlugin : } } + private var currentPolls = ArrayList() + 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) + } + } + } + override fun onDetachedFromEngine( @NonNull binding: FlutterPlugin.FlutterPluginBinding, ) { @@ -464,12 +493,14 @@ 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") eventSink = null previewSink = null rtcSink = null logsSink = null sessionStoreSink = null hlsPlayerSink = null + pollsSink = null hmssdkFlutterPlugin = null hmsBinaryMessenger = null hmsTextureRegistry = null @@ -600,6 +631,8 @@ class HmssdkFlutterPlugin : this.sessionStoreSink = events } else if (nameOfEventSink == "hls_player") { this.hlsPlayerSink = events + } else if(nameOfEventSink == "polls") { + this.pollsSink = events } } @@ -1544,11 +1577,7 @@ 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"), RECEIVER_EXPORTED) - }else { - activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER")) - } + activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER")) val mediaProjectionManager: MediaProjectionManager = activity.getSystemService( Context.MEDIA_PROJECTION_SERVICE, @@ -1613,11 +1642,7 @@ 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"), RECEIVER_EXPORTED) - }else { - activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER"),) - } + activity.applicationContext?.registerReceiver(activityBroadcastReceiver, IntentFilter("ACTIVITY_RECEIVER")) val mediaProjectionManager: MediaProjectionManager? = activity.getSystemService( Context.MEDIA_PROJECTION_SERVICE, @@ -2076,4 +2101,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) + } + } + + 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 + + CoroutineScope(Dispatchers.Main).launch { + pollsSink?.success(args) + } + } + } + + } diff --git a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/PermissionParamsExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/PermissionParamsExtension.kt index a7123ba96..0ea6541f8 100644 --- a/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/PermissionParamsExtension.kt +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/hms_role_components/PermissionParamsExtension.kt @@ -15,6 +15,8 @@ class PermissionParamsExtension { args["remove_others"] = permissionsParams.removeOthers args["rtmp_streaming"] = permissionsParams.rtmpStreaming args["un_mute"] = permissionsParams.unmute + args["poll_read"] = permissionsParams.pollRead + args["poll_write"] = permissionsParams.pollWrite return args } } 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 new file mode 100644 index 000000000..1386e1c2f --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSPollAction.kt @@ -0,0 +1,186 @@ +package live.hms.hmssdk_flutter.methods + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +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.video.error.HMSException +import live.hms.video.polls.HMSPollResponseBuilder +import live.hms.video.polls.models.HmsPoll +import live.hms.video.polls.models.HmsPollState +import live.hms.video.polls.models.answer.PollAnswerResponse +import live.hms.video.polls.models.question.HMSPollQuestion +import live.hms.video.polls.models.question.HMSPollQuestionOption +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) + } + } + + private fun quickStartPoll(call: MethodCall, result: MethodChannel.Result, hmssdk: HMSSDK){ + + val pollBuilderMap = call.argument?>("poll_builder") + + val pollBuilder = HMSPollBuilderExtension.toHMSPollBuilder(pollBuilderMap,hmssdk) + + pollBuilder?.let { + 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?){ + + val pollId = call.argument("poll_id") + val index = call.argument("question_index") + val userId = call.argument("user_id") + val answer = call.argument>("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 + } + + /* + * We fetch the polls which are currently active and find the poll matching the pollId + * passed from flutter. + * We use the poll object and find the question which the user has answered based on the + * index passed from flutter. + * After getting the question object we use the index from above to get the HMSPollQuestionOption + * object + * Finally we build the response builder and add the response. + * + * If anywhere the sdk is unable to find the property we return the error + */ + currentPolls?.find { it.pollId == pollId }?.let {poll -> + index?.let {questionIndex -> + + 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 = 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))) + } + override fun onError(error: HMSException) { + methodChannelResult.success(HMSResultExtension.toDictionary(false,HMSExceptionExtension.toDictionary(error))) + } + }) + } + + }?:run { + HMSErrorLogger.returnArgumentsError("Question not found") + return + } + }?: 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?){ + + val pollId = call.argument("poll_id") + val index = call.argument("question_index") + val userId = call.argument("user_id") + val answer = call.argument?>>("answer") + + /* + * We fetch the polls which are currently active and find the poll matching the pollId + * passed from flutter. + * We use the poll object and find the question which the user has answered based on the + * index passed from flutter. + * After getting the question object we use the index from above to get the HMSPollQuestionOption + * object + * Finally we build the response builder and add the response. + * + * If anywhere the sdk is unable to find the property we return the error + */ + currentPolls?.find { it.pollId == pollId }?.let { poll -> + index?.let {questionIndex -> + poll.questions?.get(questionIndex)?.let { currentQuestion -> + val questionOptions = ArrayList() + answer?.forEach { selectedOptions -> + 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 + val questionOption = currentQuestion.options?.get(index - 1) + questionOption?.let {option -> + questionOptions.add(option) + } + } + } + + val response = 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 { + HMSErrorLogger.returnArgumentsError("Question not found") + return + } + }?:run{ + HMSErrorLogger.returnArgumentsError("Incorrect question index") + return + } + + }?:run { + HMSErrorLogger.returnArgumentsError("No poll with given pollId found") + return + } + } + + 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)) + + } + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..0d4fa4718 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerExtension.kt @@ -0,0 +1,97 @@ +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() + map["answer"] = it.answerText + map["duration"] = it.durationMillis + map["question_id"] = it.questionId + map["question_type"] = getStringFromPollQuestionType(it.questionType) + map["selected_option"] = it.selectedOption + map["selected_options"] = it.selectedOptions + map["skipped"] = it.skipped + map["update"] = it.update + return map + } ?: run { + return null + } + } + + 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 Long + }?: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 new file mode 100644 index 000000000..cf0c2e38e --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollAnswerResponseExtension.kt @@ -0,0 +1,27 @@ +package live.hms.hmssdk_flutter.poll_extension + +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() + resultMap["correct"] = it.correct + resultMap["error"] = HMSExceptionExtension.toDictionary(it.error) + resultMap["question_index"] = it.questionIndex + + resultMapList.add(resultMap) + } + + map["result"] = resultMapList + + 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 new file mode 100644 index 000000000..ab86242c6 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollBuilderExtension.kt @@ -0,0 +1,241 @@ +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?{ + + pollBuilderMap?.let { + val pollBuilder = HMSPollBuilder.Builder() + pollBuilderMap["anonymous"]?.let{ + pollBuilder.withAnonymous(it as Boolean) + } + pollBuilderMap["duration"]?.let { + pollBuilder.withDuration(it as Long) + } + pollBuilderMap["mode"]?.let { + pollBuilder.withUserTrackingMode(getPollUserTrackingModeFromString(it as String)) + } + pollBuilderMap["poll_category"]?.let { + pollBuilder.withCategory(getPollCategoryFromString(it as String)) + } + + pollBuilderMap["poll_id"]?.let { + pollBuilder.withPollId(it as String) + } + + pollBuilderMap["questions"]?.let { + val questions = it as ArrayList<*> + val pollQuestions = ArrayList() + questions.forEach { pollQuestion -> + pollQuestion as HashMap? + val pollQuestionBuilder = getPollQuestionBuilder(pollQuestion) + pollQuestionBuilder?.let {questionBuilder -> + pollBuilder.addQuestion(questionBuilder) + } + } + + } + + val availableRoles = hmssdk.getRoles() + pollBuilderMap["roles_that_can_view_responses"]?.let { + val roles = it as ArrayList<*> + val rolesThatCanViewResponses = ArrayList() + roles.forEach { forEveryRole -> + val role = availableRoles.find { role -> role.name == forEveryRole } + role?.let { currentRole -> + rolesThatCanViewResponses.add(currentRole) + } + } + pollBuilder.withRolesThatCanViewResponses(rolesThatCanViewResponses) + } + + pollBuilderMap["roles_that_can_vote"]?.let { + val roles = it as ArrayList<*> + val rolesThatCanVote = ArrayList() + roles.forEach{ forEveryRole -> + val role = availableRoles.find { role -> role.name == forEveryRole } + role?.let {currentRole -> + rolesThatCanVote.add(currentRole) + } + } + pollBuilder.withRolesThatCanVote(rolesThatCanVote) + } + + pollBuilderMap["title"]?.let { + pollBuilder.withTitle(it as String) + } + + return pollBuilder.build() + + }?: run { + return null + } + + + } + + private fun getPollCategoryFromString(category: String): HmsPollCategory { + return when(category) { + "poll" -> HmsPollCategory.POLL + "quiz" -> HmsPollCategory.QUIZ + else -> HmsPollCategory.POLL + } + } + + + private fun getPollUserTrackingModeFromString(pollUserTrackingMode: String):HmsPollUserTrackingMode{ + return when(pollUserTrackingMode){ + "user_id" -> HmsPollUserTrackingMode.USER_ID + "peer_id" -> HmsPollUserTrackingMode.PEER_ID + "username"-> HmsPollUserTrackingMode.USERNAME + else -> HmsPollUserTrackingMode.USER_ID + + } + } + + private fun getPollQuestionTypeFromString(pollQuestionType: String): HMSPollQuestionType{ + return when(pollQuestionType){ + "multi_choice" -> HMSPollQuestionType.multiChoice + "short_answer" -> HMSPollQuestionType.shortAnswer + "long_answer" -> HMSPollQuestionType.longAnswer + "single_choice" -> HMSPollQuestionType.singleChoice + else -> HMSPollQuestionType.singleChoice + } + } + + 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 + } + + type.let {questionType -> + pollQuestionBuilder = HMSPollQuestionBuilder.Builder(questionType) + } + + 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 + } + + text?.let { + pollQuestionBuilder.withTitle(text) + } + + val duration = pollQuestion["duration"]?.let { duration -> + duration as Long + } + + duration?.let { duration -> + pollQuestionBuilder.withDuration(duration) + } + + val weight = pollQuestion["weight"]?.let {weight -> + weight as Int + } + + weight?.let { + pollQuestionBuilder.withWeight(weight) + } + + val answerHidden = pollQuestion["answer_hidden"]?.let {answerHidden -> + answerHidden as Boolean + } + + answerHidden?.let { + pollQuestionBuilder.withAnswerHidden(answerHidden) + } + + val maxLength = pollQuestion["max_length"]?.let { maxLength -> + maxLength as Long + } + + maxLength?.let { + pollQuestionBuilder.withMaxLength(maxLength) + } + + + val minLength = pollQuestion["min_length"]?.let { minLength -> + minLength as Long + } + + minLength?.let { + pollQuestionBuilder.withMinLength(minLength) + } + + val pollOptions = pollQuestion["poll_options"]?.let { options -> + options as ArrayList + }?:run { + HMSErrorLogger.returnArgumentsError("pollOptions should not be null") + null + } + + pollOptions?.let { + pollOptions.forEach { + pollQuestionBuilder.addOption(it) + } + } + + val option = pollQuestion["options"]?.let { options -> + + options as ArrayList> + val optionMap = ArrayList>() + + options.forEach { + optionMap.add(Pair(it.keys.first(),it.values.first())) + } + optionMap + }?:run { + HMSErrorLogger.returnArgumentsError("options should not be null") + null + } + + option?.let { + option.forEach { + pollQuestionBuilder.addQuizOption(it.first,it.second) + } + } + + val canChangeResponse = pollQuestion["can_change_response"]?.let { canChangeResponse -> + canChangeResponse as Boolean + } + + canChangeResponse?.let { + pollQuestionBuilder.withCanChangeResponse(it) + } + + return pollQuestionBuilder.build() + + }?: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 new file mode 100644 index 000000000..e70358047 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollExtension.kt @@ -0,0 +1,91 @@ +package live.hms.hmssdk_flutter.poll_extension + +import live.hms.hmssdk_flutter.HMSPeerExtension +import live.hms.hmssdk_flutter.HMSRoleExtension +import live.hms.video.polls.models.* + +class HMSPollExtension { + + companion object { + fun toDictionary(poll: HmsPoll): HashMap { + + val map = HashMap() + + map["anonymous"] = poll.anonymous + map["category"] = getPollCategory(poll.category) + map["created_by"] = HMSPeerExtension.toDictionary(poll.createdBy) + map["duration"] = poll.duration + map["mode"] = getPollUserTrackingMode(poll.mode) + map["poll_id"] = poll.pollId + map["question_count"] = poll.questionCount + + 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{ + rolesThatCanViewResponses.add(HMSRoleExtension.toDictionary(it)) + } + map["roles_that_can_view_responses"] = rolesThatCanViewResponses + + val rolesThatCanVote = ArrayList?>() + poll.rolesThatCanVote.forEach{ + rolesThatCanVote.add(HMSRoleExtension.toDictionary(it)) + } + map["roles_that_can_vote"] = rolesThatCanVote + + map["started_at"] = poll.startedAt * 1000 + map["started_by"] = HMSPeerExtension.toDictionary(poll.startedBy) + map["state"] = getPollState(poll.state) + map["stopped_at"] = poll.stoppedAt?.let { + it * 1000 + }?:run { + null + } + map["title"] = poll.title + + return map + } + + 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){ + HmsPollUserTrackingMode.USER_ID -> "user_id" + HmsPollUserTrackingMode.PEER_ID -> "peer_id" + HmsPollUserTrackingMode.USERNAME -> "username" + else -> null + } + } + + private fun getPollState(pollState: HmsPollState):String?{ + return when(pollState){ + HmsPollState.CREATED -> "created" + HmsPollState.STARTED -> "started" + HmsPollState.STOPPED -> "stopped" + else -> null + } + } + + fun getPollUpdateType(hmsPollUpdateType: HMSPollUpdateType):String?{ + return when(hmsPollUpdateType){ + HMSPollUpdateType.started -> "started" + HMSPollUpdateType.stopped -> "stopped" + HMSPollUpdateType.resultsupdated -> "results_updated" + else -> null + } + } + } +} + 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 new file mode 100644 index 000000000..e46e44a35 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionAnswerExtension.kt @@ -0,0 +1,50 @@ +package live.hms.hmssdk_flutter.poll_extension + +import live.hms.hmssdk_flutter.HMSErrorLogger +import live.hms.video.polls.models.answer.HMSPollQuestionAnswer + +class HMSPollQuestionAnswerExtension { + + companion object { + fun toDictionary(answer: HMSPollQuestionAnswer?):HashMap?{ + answer?.let { + val map = HashMap() + map["hidden"] = it.hidden + map["option"] = it.option + map["options"] = it.options + return map + }?:run{ + return null + } + } + + 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 + } + + 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 new file mode 100644 index 000000000..9414555e9 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionExtension.kt @@ -0,0 +1,152 @@ +package live.hms.hmssdk_flutter.poll_extension + +import live.hms.hmssdk_flutter.HMSErrorLogger +import live.hms.video.polls.models.answer.HmsPollAnswer +import live.hms.video.polls.models.question.HMSPollQuestion +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 + + 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?{ + + pollQuestion?.let { + + 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 Long + }?: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) + } + } + 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) + } + } + 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 + } + + 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){ + HMSPollQuestionType.multiChoice -> "multi_choice" + HMSPollQuestionType.shortAnswer -> "short_answer" + HMSPollQuestionType.longAnswer -> "long_answer" + HMSPollQuestionType.singleChoice -> "single_choice" + else -> null + } + } + + fun getPollQuestionTypeFromString(pollQuestionType: String):HMSPollQuestionType?{ + return when(pollQuestionType){ + "multi_choice" -> HMSPollQuestionType.multiChoice + "short_answer" -> HMSPollQuestionType.shortAnswer + "long_answer" -> HMSPollQuestionType.longAnswer + "single_choice" -> HMSPollQuestionType.singleChoice + else -> null + } + } + } +} \ 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 new file mode 100644 index 000000000..6ae6fcbd9 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollQuestionOptionExtension.kt @@ -0,0 +1,67 @@ +package live.hms.hmssdk_flutter.poll_extension + +import live.hms.hmssdk_flutter.HMSErrorLogger +import live.hms.video.polls.models.question.HMSPollQuestionOption + +class HMSPollQuestionOptionExtension { + + companion object{ + + fun toDictionary(pollOptions: HMSPollQuestionOption?): HashMap?{ + + pollOptions?.let { + val map = HashMap() + map["index"] = it.index + map["text"] = it.text + map["vote_count"] = it.voteCount + map["weight"] = it.weight + return map + }?:run { + return null + } + } + + 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 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 Long + }?: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 + } + + return HMSPollQuestionOption(index,text,weight, + case = false, + trim = false, + voteCount = voteCount + ) + + }?: 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/HMSPollResultDisplayExtension.kt b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResultDisplayExtension.kt new file mode 100644 index 000000000..18aeef3b9 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollResultDisplayExtension.kt @@ -0,0 +1,28 @@ +package live.hms.hmssdk_flutter.poll_extension + +import live.hms.video.polls.network.PollResultsDisplay + +class HMSPollResultDisplayExtension { + + companion object{ + + fun toDictionary(pollResultsDisplay: PollResultsDisplay?):HashMap?{ + + pollResultsDisplay?.let { + val map = HashMap() + + val questions = ArrayList?>() + it.questions.forEach{ question -> questions.add(HMSPollStatsQuestionsExtension.toDictionary(question))} + map["questions"] = questions + + map["total_distinct_users"] = it.totalDistinctUsers + map["total_responses"] = it.totalResponses + map["voting_users"] = it.votingUsers + + return map + }?: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 new file mode 100644 index 000000000..104976624 --- /dev/null +++ b/packages/hmssdk_flutter/android/src/main/kotlin/live/hms/hmssdk_flutter/poll_extension/HMSPollStatsQuestionsExtension.kt @@ -0,0 +1,24 @@ +package live.hms.hmssdk_flutter.poll_extension + +import live.hms.video.polls.models.PollStatsQuestions + +class HMSPollStatsQuestionsExtension { + + companion object{ + + fun toDictionary(pollStatsQuestions: PollStatsQuestions?):HashMap?{ + + pollStatsQuestions?.let { + 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 { + return null + } + } + } +} \ No newline at end of file diff --git a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt index 90051131e..751b44559 100644 --- a/packages/hmssdk_flutter/example/ExampleAppChangelog.txt +++ b/packages/hmssdk_flutter/example/ExampleAppChangelog.txt @@ -1,12 +1,33 @@ Board: https://100ms.atlassian.net/jira/software/projects/FLUT/boards/34/ -- Resolve issues reported by VAPT testing -https://100ms.atlassian.net/browse/FLUT-218 +- Polls: Create Polls initial Screen +https://100ms.atlassian.net/browse/FLUT-194 -- Resolve crash on Android 14 devices due to Broadcast Receiver in HMSVideoView -https://100ms.atlassian.net/browse/FLUT-219 +- Polls: Ask a Question Screen +https://100ms.atlassian.net/browse/FLUT-195 -Room Kit: 1.0.11 -Core SDK: 1.9.8 -Android SDK: 2.8.8 -iOS SDK: 1.4.2 +- Polls: Single Choice Question Screen +https://100ms.atlassian.net/browse/FLUT-196 + +- Polls: Multiple Choice Question Screen +https://100ms.atlassian.net/browse/FLUT-197 + +- Polls: Launch Poll Screen +https://100ms.atlassian.net/browse/FLUT-202 + +- Polls: Poll Panel with a live Poll Screen +https://100ms.atlassian.net/browse/FLUT-204 + +- Created poll with hide count, can see count as VNRT +https://100ms.atlassian.net/browse/FLUT-233 + +- host created poll but VNRT getting notification instantly even when timed metadata is ON +https://100ms.atlassian.net/browse/FLUT-220 + +- VNRT user not able to see poll if rejoin session +https://100ms.atlassian.net/browse/FLUT-235 + +Room Kit: 1.0.12 +Core SDK: 1.9.9 +Android SDK: 2.9.0 +iOS SDK: 1.5.0 diff --git a/packages/hmssdk_flutter/example/android/Gemfile.lock b/packages/hmssdk_flutter/example/android/Gemfile.lock index 1d185f94f..99a461c55 100644 --- a/packages/hmssdk_flutter/example/android/Gemfile.lock +++ b/packages/hmssdk_flutter/example/android/Gemfile.lock @@ -13,8 +13,8 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.885.0) - aws-sdk-core (3.191.0) + aws-partitions (1.889.0) + aws-sdk-core (3.191.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) @@ -111,11 +111,11 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.8.1) + 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.55.0) + google-apis-androidpublisher_v3 (0.57.0) google-apis-core (>= 0.12.0, < 2.a) google-apis-core (0.13.0) addressable (~> 2.5, >= 2.5.1) @@ -138,7 +138,7 @@ GEM google-cloud-core (1.6.1) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.1.0) + google-cloud-env (2.1.1) faraday (>= 1.0, < 3.a) google-cloud-errors (1.3.1) google-cloud-storage (1.48.1) @@ -150,7 +150,7 @@ GEM google-cloud-core (~> 1.6) googleauth (~> 1.9) mini_mime (~> 1.0) - googleauth (1.9.2) + googleauth (1.10.0) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) @@ -168,9 +168,9 @@ GEM jwt (2.7.1) mini_magick (4.12.0) mini_mime (1.1.5) - minitest (5.21.2) + minitest (5.22.2) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) nanaimo (0.3.0) naturally (2.2.1) optparse (0.4.0) @@ -222,7 +222,6 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS - arm64-darwin-22 arm64-darwin-23 DEPENDENCIES @@ -231,4 +230,4 @@ DEPENDENCIES fastlane-plugin-firebase_app_distribution BUNDLED WITH - 2.4.22 + 2.5.6 diff --git a/packages/hmssdk_flutter/example/android/app/build.gradle b/packages/hmssdk_flutter/example/android/app/build.gradle index f11e098ed..3082e60b0 100644 --- a/packages/hmssdk_flutter/example/android/app/build.gradle +++ b/packages/hmssdk_flutter/example/android/app/build.gradle @@ -31,8 +31,8 @@ android { applicationId "live.hms.flutter" minSdkVersion 21 targetSdkVersion 34 - versionCode 430 - versionName "1.5.130" + versionCode 439 + versionName "1.5.139" } signingConfigs { diff --git a/packages/hmssdk_flutter/example/ios/Gemfile.lock b/packages/hmssdk_flutter/example/ios/Gemfile.lock index d08516818..90a3dbf12 100644 --- a/packages/hmssdk_flutter/example/ios/Gemfile.lock +++ b/packages/hmssdk_flutter/example/ios/Gemfile.lock @@ -13,8 +13,8 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.885.0) - aws-sdk-core (3.191.0) + aws-partitions (1.889.0) + aws-sdk-core (3.191.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) @@ -111,12 +111,12 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-firebase_app_distribution (0.8.1) + 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.55.0) + google-apis-androidpublisher_v3 (0.57.0) google-apis-core (>= 0.12.0, < 2.a) google-apis-core (0.13.0) addressable (~> 2.5, >= 2.5.1) @@ -139,7 +139,7 @@ GEM google-cloud-core (1.6.1) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.1.0) + google-cloud-env (2.1.1) faraday (>= 1.0, < 3.a) google-cloud-errors (1.3.1) google-cloud-storage (1.48.1) @@ -151,7 +151,7 @@ GEM google-cloud-core (~> 1.6) googleauth (~> 1.9) mini_mime (~> 1.0) - googleauth (1.9.2) + googleauth (1.10.0) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) @@ -169,9 +169,9 @@ GEM jwt (2.7.1) mini_magick (4.12.0) mini_mime (1.1.5) - minitest (5.21.2) + minitest (5.22.2) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) nanaimo (0.3.0) naturally (2.2.1) optparse (0.4.0) @@ -223,7 +223,6 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS - arm64-darwin-22 arm64-darwin-23 DEPENDENCIES diff --git a/packages/hmssdk_flutter/example/ios/Podfile b/packages/hmssdk_flutter/example/ios/Podfile index b2636e014..81e6580ff 100644 --- a/packages/hmssdk_flutter/example/ios/Podfile +++ b/packages/hmssdk_flutter/example/ios/Podfile @@ -46,7 +46,7 @@ post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| - config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" + config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "x86_64" config.build_settings['ENABLE_BITCODE'] = 'NO' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', diff --git a/packages/hmssdk_flutter/example/ios/Podfile.lock b/packages/hmssdk_flutter/example/ios/Podfile.lock index 85f7c2846..875258308 100644 --- a/packages/hmssdk_flutter/example/ios/Podfile.lock +++ b/packages/hmssdk_flutter/example/ios/Podfile.lock @@ -97,14 +97,14 @@ PODS: - HMSBroadcastExtensionSDK (0.0.9) - HMSHLSPlayerSDK (0.0.2): - HMSAnalyticsSDK (= 0.0.2) - - HMSSDK (1.4.2): + - HMSSDK (1.5.0): - HMSAnalyticsSDK (= 0.0.2) - HMSWebRTC (= 1.0.5116) - hmssdk_flutter (1.9.6): - Flutter - HMSBroadcastExtensionSDK (= 0.0.9) - HMSHLSPlayerSDK (= 0.0.2) - - HMSSDK (= 1.4.2) + - HMSSDK (= 1.5.0) - HMSWebRTC (1.0.5116) - MTBBarcodeScanner (5.0.11) - nanopb (2.30909.1): @@ -228,29 +228,29 @@ SPEC CHECKSUMS: FirebaseRemoteConfig: b873a427a48159082361343a85649eed3f5377ea FirebaseSessions: 2f348975f6d1c139231c180e12194161da2e0cd6 FirebaseSharedSwift: 2fbf73618288b7a36b2014b957745dcdd781389e - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_foreground_task: 21ef182ab0a29a3005cc72cd70e5f45cb7f7f817 GoogleDataTransport: 57c22343ab29bc686febbf7cbb13bad167c2d8fe GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34 HMSAnalyticsSDK: 4d2a88a729b1eb42f3d25f217c28937ec318a5b7 HMSBroadcastExtensionSDK: d80fe325f6c928bd8e5176290b5a4b7ae15d6fbb HMSHLSPlayerSDK: 6a54ad4d12f3dc2270d1ecd24019d71282a4f6a3 - HMSSDK: 7be2385e9e3ddbd5c03a228934622d679d033e15 - hmssdk_flutter: 1cd1e55001bdfd1b015eebced25ba3a7fb64649b + HMSSDK: 0d1901d64faf2661d1183c1ba2881e2531a5eeba + hmssdk_flutter: 9f3b16d9bfc1e9a2ccd63f5d9b6a6d51669ed5ac HMSWebRTC: ae54e9dd91b869051b283b43b14f57d43b7bf8e1 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265 qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 uni_links: d97da20c7701486ba192624d99bffaaffcfc298a - url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 -PODFILE CHECKSUM: 919064996fff867cd85dbf9e7730ff45bac23884 +PODFILE CHECKSUM: 9fb9f6e431a2c6c79942252e94b241ac7972ac90 -COCOAPODS: 1.15.0 +COCOAPODS: 1.14.3 diff --git a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj index 3f12e3fba..dd0e12912 100644 --- a/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/hmssdk_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -672,6 +672,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5N85PP82A9; ENABLE_BITCODE = NO; + "EXCLUDED_ARCHS[sdk=*]" = x86_64; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -745,7 +746,8 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5N85PP82A9; ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; + EXCLUDED_ARCHS = x86_64; + "EXCLUDED_ARCHS[sdk=*]" = x86_64; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FlutterBroadcastUploadExtension/Info.plist; @@ -789,6 +791,7 @@ CURRENT_PROJECT_VERSION = 185; DEVELOPMENT_TEAM = 5N85PP82A9; ENABLE_BITCODE = NO; + EXCLUDED_ARCHS = x86_64; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FlutterBroadcastUploadExtension/Info.plist; @@ -829,6 +832,7 @@ CURRENT_PROJECT_VERSION = 185; DEVELOPMENT_TEAM = 5N85PP82A9; ENABLE_BITCODE = NO; + EXCLUDED_ARCHS = x86_64; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FlutterBroadcastUploadExtension/Info.plist; diff --git a/packages/hmssdk_flutter/example/ios/Runner/Info.plist b/packages/hmssdk_flutter/example/ios/Runner/Info.plist index df8e2a6ba..d7533962e 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.130 + 1.5.139 CFBundleSignature ???? CFBundleURLTypes @@ -48,7 +48,7 @@ CFBundleVersion - 430 + 439 ITSAppUsesNonExemptEncryption LSApplicationCategoryType @@ -88,6 +88,8 @@ Main UIRequiresFullScreen + UIStatusBarHidden + UIStatusBarStyle UIStatusBarStyleDarkContent UISupportedInterfaceOrientations diff --git a/packages/hmssdk_flutter/example/lib/main.dart b/packages/hmssdk_flutter/example/lib/main.dart index cf391ed08..f4e7dddac 100644 --- a/packages/hmssdk_flutter/example/lib/main.dart +++ b/packages/hmssdk_flutter/example/lib/main.dart @@ -1,5 +1,6 @@ //Dart imports import 'dart:async'; +import 'dart:ui'; //Package imports import 'package:firebase_core/firebase_core.dart'; diff --git a/packages/hmssdk_flutter/example/pubspec.lock b/packages/hmssdk_flutter/example/pubspec.lock index 409983a24..3f81f85e8 100644 --- a/packages/hmssdk_flutter/example/pubspec.lock +++ b/packages/hmssdk_flutter/example/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: archive - sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.4.9" + version: "3.4.10" args: dependency: transitive description: @@ -286,15 +286,15 @@ packages: path: "../../hms_room_kit" relative: true source: path - version: "1.0.11" + version: "1.0.12" hmssdk_flutter: dependency: transitive description: name: hmssdk_flutter - sha256: ff1697824b42d31cb093fd4319c8fa3ba6872b7877707630062ed3cd9cf40813 + sha256: bad4ff87c677970f0c9acfcd4e84b60089461e1c1eef23e3a3ae6ab191484b95 url: "https://pub.dev" source: hosted - version: "1.9.8" + version: "1.9.9" http: dependency: transitive description: @@ -419,10 +419,10 @@ packages: dependency: transitive description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_android: dependency: transitive description: @@ -435,10 +435,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -451,10 +451,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -515,26 +515,26 @@ packages: dependency: transitive description: name: platform - sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" provider: dependency: transitive description: @@ -587,10 +587,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: @@ -603,10 +603,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_web: dependency: transitive description: @@ -728,26 +728,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.2.4" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: @@ -768,10 +768,10 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" url_launcher_web: dependency: transitive description: @@ -800,26 +800,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_math: dependency: transitive description: @@ -856,10 +856,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" xml: dependency: transitive description: diff --git a/packages/hmssdk_flutter/example/pubspec.yaml b/packages/hmssdk_flutter/example/pubspec.yaml index 91957822f..d9065c665 100644 --- a/packages/hmssdk_flutter/example/pubspec.yaml +++ b/packages/hmssdk_flutter/example/pubspec.yaml @@ -4,7 +4,7 @@ description: Demonstrates how to use the hmssdk_flutter plugin. # The following line prevents the package from being accidentally published to # pub.dev using `pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.9.8 +version: 1.9.9 environment: sdk: ">=2.16.0 <4.0.0" diff --git a/packages/hmssdk_flutter/ios/Classes/Actions/HMSPollAction.swift b/packages/hmssdk_flutter/ios/Classes/Actions/HMSPollAction.swift new file mode 100644 index 000000000..26793b472 --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Actions/HMSPollAction.swift @@ -0,0 +1,194 @@ +// +// HMSPollAction.swift +// hmssdk_flutter +// +// Created by Pushpam on 19/01/24. +// + +import Foundation +import HMSSDK + +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 + case "add_single_choice_poll_response": + addSingleChoicePollResponse(call, result, hmsSDK, polls) + break + case "add_multi_choice_poll_response": + addMultiChoicePollResponse(call, result, hmsSDK, polls) + break + case "stop_poll": + stopPoll(call, result, hmsSDK, polls) + default: + result(FlutterMethodNotImplemented) + } + } + + 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{ + result(HMSErrorExtension.getError("No pollBuilder found in \(#function)")) + return + } + + if let hmsSDK{ + guard let pollBuilder = HMSPollBuilderExtension.toHMSPollBuilder(pollBuilderMap, hmsSDK) + else{ + HMSErrorLogger.returnArgumentsError("pollBuilder parsing failed") + return + } + + hmsSDK.interactivityCenter.quickStartPoll(with: (pollBuilder), completion: {_ , error in + if let error = error { + result(HMSErrorExtension.toDictionary(error)) + } else { + result(nil) + }}) + } else{ + result(HMSErrorExtension.getError("hmsSDK is nil in \(#function)")) + return + } + + } + + + 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?] + else { + HMSErrorLogger.returnArgumentsError("Invalid arguments") + return + } + + 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]{ + let response = HMSPollResponseBuilder(poll: poll) + response.addResponse(for: question, options: [optionSelected]) + hmsSDK?.interactivityCenter.add(response: response){ pollResult, error in + + if let error = error{ + result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) + }else{ + var pollResults = [[String: Any?]]() + + pollResult?.forEach{ + pollResults.append(HMSPollAnswerResponseExtension.toDictionary(pollAnswerResponse: $0)) + } + var map = [String:Any?]() + map["result"] = pollResults + result(HMSResultExtension.toDictionary(true, map)) + } + } + }else{ + HMSErrorLogger.returnArgumentsError("No option found at given index") + } + + }else{ + HMSErrorLogger.returnArgumentsError("No question found at given index") + } + + }else{ + HMSErrorLogger.returnArgumentsError("No poll with given pollId found") + return + } + } else { + HMSErrorLogger.returnArgumentsError("Invalid option index") + return + } + } + + + 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?]] + else { + HMSErrorLogger.returnArgumentsError("Invalid arguments") + return + } + 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{ + selectedOptions.append(option) + }else{ + HMSErrorLogger.returnArgumentsError("Invalid option index") + return + } + }else{ + HMSErrorLogger.returnArgumentsError("Invalid index") + return + } + } + let response = HMSPollResponseBuilder(poll: poll) + response.addResponse(for: question, options: selectedOptions) + hmsSDK?.interactivityCenter.add(response: response){ pollResult, error in + + if let error = error{ + result(HMSResultExtension.toDictionary(false, HMSErrorExtension.toDictionary(error))) + }else{ + var pollResults = [[String: Any?]]() + + pollResult?.forEach{ + pollResults.append(HMSPollAnswerResponseExtension.toDictionary(pollAnswerResponse: $0)) + } + var map = [String:Any?]() + map["result"] = pollResults + result(HMSResultExtension.toDictionary(true, map)) + } + } + }else{ + HMSErrorLogger.returnArgumentsError("No question found at given index") + return + } + }else{ + HMSErrorLogger.returnArgumentsError("No poll with given pollId found") + return + } + } + + 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{ + HMSErrorLogger.returnArgumentsError("pollId can't be null") + return + } + + if let poll = currentPolls?.first(where: { + $0.pollID == pollId + }){ + hmsSDK?.interactivityCenter.stop(poll: poll){ + _, error in + if let error = error{ + result(HMSErrorExtension.toDictionary(error)) + }else{ + result(nil) + } + } + } + } +} diff --git a/packages/hmssdk_flutter/ios/Classes/Models/HMSPermissionExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/HMSPermissionExtension.swift index b643c4e65..bd4a755db 100644 --- a/packages/hmssdk_flutter/ios/Classes/Models/HMSPermissionExtension.swift +++ b/packages/hmssdk_flutter/ios/Classes/Models/HMSPermissionExtension.swift @@ -19,8 +19,10 @@ class HMSPermissionExtension { "mute": permission.mute ?? false, "remove_others": permission.removeOthers ?? false, "rtmp_streaming": permission.rtmpStreaming ?? false, - "un_mute": permission.unmute ?? false - + "un_mute": permission.unmute ?? false, + "poll_read": permission.pollRead ?? false, + "poll_write": permission.pollWrite ?? false + ] } } diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerExtension.swift new file mode 100644 index 000000000..d326e3d45 --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerExtension.swift @@ -0,0 +1,28 @@ +// +// HMSPollAnswerExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 18/01/24. +// + +import Foundation +import HMSSDK + +class HMSPollAnswerExtension{ + + static func toDictionary(answer: HMSPollQuestionResponse) -> [String:Any]{ + + var map = [String:Any]() + + map["answer"] = answer.text + map["duration"] = answer.duration + map["question_id"] = answer.questionID + map["question_type"] = HMSPollQuestionExtension.getPollQuestionType(pollQuestionType: answer.type) + map["selected_option"] = answer.option + 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 new file mode 100644 index 000000000..7c90a6fb8 --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollAnswerResponseExtension.swift @@ -0,0 +1,28 @@ +// +// HMSPollAnswerResponseExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 31/01/24. +// + +import Foundation +import HMSSDK + +class HMSPollAnswerResponseExtension{ + + static func toDictionary(pollAnswerResponse: HMSPollQuestionResponseResult) -> [String: Any?]{ + + var map = [String:Any?]() + + map["question_index"] = pollAnswerResponse.question + 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 new file mode 100644 index 000000000..ec455e3f1 --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollBuilderExtension.swift @@ -0,0 +1,178 @@ +// +// HMSPollBuilderExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 23/01/24. +// + +import Foundation +import HMSSDK + +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{ + pollBuilder.withAnonymous(anonymous) + } + + if let duration = pollBuilderMap["duration"] as? Int { + pollBuilder.withDuration(duration) + } + + if let mode = pollBuilderMap["mode"] as? String { + pollBuilder.withUserTrackingMode(getPollUserTrackingModeFromString(mode)) + } + + if let pollCategory = pollBuilderMap["poll_category"] as? String { + pollBuilder.withCategory(getPollCategoryFromString(pollCategory)) + } + + 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 + availableRoles.first { $0.name == roleName } + } + pollBuilder.withRolesThatCanViewResponses(roles) + } + + if let rolesThatCanVote = pollBuilderMap["roles_that_can_vote"] as? [String] { + let roles = rolesThatCanVote.compactMap { roleName in + availableRoles.first { $0.name == roleName } + } + pollBuilder.withRolesThatCanVote(roles) + } + + 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){ + pollBuilder.addQuestion(with: questionBuilder) + } + } + + } + return pollBuilder + } + + private static func getPollCategoryFromString(_ pollCategory:String) -> HMSPollCategory{ + + switch pollCategory{ + case "poll": + return .poll + case "quiz": + return .quiz + default: + return .poll + } + } + + private static func getPollUserTrackingModeFromString(_ pollUserTrackingMode: String) -> HMSPollUserTrackingMode { + switch pollUserTrackingMode { + case "user_id": + return .customerUserID + case "peer_id": + return .peerID + case "username": + return .userName + default: + return .customerUserID + } + } + + private static func getPollQuestionTypeFromString(_ pollQuestionType: String) -> HMSPollQuestionType { + switch pollQuestionType { + case "multi_choice": + return .multipleChoice + case "short_answer": + return .shortAnswer + case "long_answer": + return .longAnswer + case "single_choice": + return .singleChoice + default: + return .singleChoice + } + } + + private static func getPollQuestionBuilder(_ pollQuestion: [String: Any?]?) -> HMSPollQuestionBuilder? { + guard let pollQuestion = pollQuestion else { + return nil + } + + let pollQuestionBuilder = HMSPollQuestionBuilder() + + if let typeString = pollQuestion["type"] as? String{ + let type = getPollQuestionTypeFromString(typeString) + pollQuestionBuilder.withType(type) + } else { + HMSErrorLogger.returnArgumentsError("type should not be null") + return nil + } + + + if let canSkip = pollQuestion["can_skip"] as? Bool { + pollQuestionBuilder.withCanBeSkipped(canSkip) + } + + if let text = pollQuestion["text"] as? String { + pollQuestionBuilder.withTitle(text) + } + + if let duration = pollQuestion["duration"] as? Int { + pollQuestionBuilder.withDuration(duration) + } + + if let weight = pollQuestion["weight"] as? Int { + pollQuestionBuilder.withWeight(weight: weight) + } + + if let answerHidden = pollQuestion["answer_hidden"] as? Bool { + pollQuestionBuilder.withAnswerHidden(answerHidden: answerHidden) + } + + if let maxLength = pollQuestion["max_length"] as? Int { + pollQuestionBuilder.withMaxLength(maxLength: maxLength) + } + + if let minLength = pollQuestion["min_length"] as? Int { + pollQuestionBuilder.withMinLength(minLength: minLength) + } + + if let pollOptions = pollQuestion["poll_options"] as? [String] { + pollOptions.forEach { option in + pollQuestionBuilder.addOption(with: option) + } + } + + if let options = pollQuestion["options"] as? [[String: Bool]] { + options.forEach { option in + if let key = option.keys.first, let value = option.values.first { + pollQuestionBuilder.addQuizOption(with: key, isCorrect: value) + } + } + } + + 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 new file mode 100644 index 000000000..fd52f54c7 --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollExtension.swift @@ -0,0 +1,114 @@ +// +// File.swift +// hmssdk_flutter +// +// Created by Pushpam on 18/01/24. +// + +import Foundation +import HMSSDK + +class HMSPollExtension { + + static func toDictionary(poll: HMSPoll) -> [String: Any?] { + var map = [String: Any?]() + + map["anonymous"] = poll.anonymous + map["category"] = getPollCategory(pollCategory: poll.category) + + if let createdBy = poll.createdBy{ + map["created_by"] = HMSPeerExtension.toDictionary(createdBy) + } + + map["duration"] = poll.duration + + 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{ + map["result"] = HMSPollResultExtension.toDictionary(pollResult: result) + } + + let rolesThatCanViewResponses = poll.rolesThatCanViewResponses.map { HMSRoleExtension.toDictionary($0) } + map["roles_that_can_view_responses"] = rolesThatCanViewResponses + + let rolesThatCanVote = poll.rolesThatCanVote.map { HMSRoleExtension.toDictionary($0) } + map["roles_that_can_vote"] = rolesThatCanVote + + if let startedAt = poll.startedAt{ + map["started_at"] = Int(startedAt.timeIntervalSince1970 * 1000) + } + + if let startedBy = poll.startedBy{ + map["started_by"] = HMSPeerExtension.toDictionary(startedBy) + } + + map["state"] = getPollState(state: poll.state) + + if let stoppedAt = poll.stoppedAt{ + map["stopped_at"] = Int(stoppedAt.timeIntervalSince1970 * 1000) + } + + map["title"] = poll.title + + return map + } + + private static func getPollCategory(pollCategory: HMSPollCategory) -> String? { + switch pollCategory { + case .poll: + return "poll" + case .quiz: + return "quiz" + default: + return nil + } + } + + private static func getPollUserTrackingMode(mode: HMSPollUserTrackingMode) -> String? { + switch mode { + case .customerUserID: + return "user_id" + case .peerID: + return "peer_id" + case .userName: + return "username" + default: + return nil + } + } + + private static func getPollState(state: HMSPollState) -> String? { + switch state { + case .created: + return "created" + case .started: + return "started" + case .stopped: + return "stopped" + default: + return nil + } + } + + static func getPollUpdateType(updateType: HMSPollUpdateType) -> String? { + switch updateType { + case .started: + return "started" + case .stopped: + return "stopped" + case .resultsUpdated: + return "results_updated" + default: + return nil + } + } +} + diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionAnswerExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionAnswerExtension.swift new file mode 100644 index 000000000..870c71e0a --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionAnswerExtension.swift @@ -0,0 +1,23 @@ +// +// HMSPollQuestionAnswerExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 18/01/24. +// + +import Foundation +import HMSSDK + +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 new file mode 100644 index 000000000..79129307c --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionExtension.swift @@ -0,0 +1,54 @@ +// +// HMSPollQuestionExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 18/01/24. +// + +import Foundation +import HMSSDK + +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{ + map["correct_answer"] = HMSPollQuestionAnswerExtension.toDictionary(answer: answer) + } + + map["duration"] = question.duration + + let myResponses = question.myResponses.map{ (HMSPollAnswerExtension.toDictionary(answer:$0)) } + map["my_responses"] = myResponses + + 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?{ + switch pollQuestionType { + case .longAnswer: + return "long_answer" + case .multipleChoice: + return "multi_choice" + case .shortAnswer: + return "short_answer" + case .singleChoice: + return "single_choice" + default: + return nil + } + } +} diff --git a/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionOptionExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionOptionExtension.swift new file mode 100644 index 000000000..5bead12e2 --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionOptionExtension.swift @@ -0,0 +1,24 @@ +// +// HMSPollQuestionOptionExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 18/01/24. +// + +import Foundation +import HMSSDK + +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 new file mode 100644 index 000000000..2889979d3 --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollQuestionResultExtension.swift @@ -0,0 +1,25 @@ +// +// HMSPollQuestionResultExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 18/01/24. +// + +import Foundation +import HMSSDK + +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/HMSPollResultExtension.swift b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResultExtension.swift new file mode 100644 index 000000000..1b0d78136 --- /dev/null +++ b/packages/hmssdk_flutter/ios/Classes/Models/polls/HMSPollResultExtension.swift @@ -0,0 +1,26 @@ +// +// HMSPollResultDisplayExtension.swift +// hmssdk_flutter +// +// Created by Pushpam on 18/01/24. +// + +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))} + 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 ef9e71cb5..62ebacd91 100644 --- a/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift +++ b/packages/hmssdk_flutter/ios/Classes/SwiftHmssdkFlutterPlugin.swift @@ -16,13 +16,15 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene var rtcStatsEventChannel: FlutterEventChannel? var sessionEventChannel: FlutterEventChannel? var hlsPlayerChannel: FlutterEventChannel? - + var pollsEventChannel: FlutterEventChannel? + var eventSink: FlutterEventSink? var previewSink: FlutterEventSink? var logsSink: FlutterEventSink? var rtcSink: FlutterEventSink? var sessionSink: FlutterEventSink? var hlsPlayerSink: FlutterEventSink? + var pollsEventSink: FlutterEventSink? var roleChangeRequest: HMSRoleChangeRequest? @@ -54,14 +56,16 @@ 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 instance = SwiftHmssdkFlutterPlugin(channel: channel, meetingEventChannel: eventChannel, previewEventChannel: previewChannel, logsEventChannel: logsChannel, rtcStatsEventChannel: rtcChannel, sessionEventChannel: sessionChannel, - hlsPlayerChannel: hlsChannel) + hlsPlayerChannel: hlsChannel, + pollsEventChannel: pollsChannel) let videoViewFactory = HMSFlutterPlatformViewFactory(plugin: instance) registrar.register(videoViewFactory, withId: "HMSFlutterPlatformView") @@ -75,6 +79,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene rtcChannel.setStreamHandler(instance) sessionChannel.setStreamHandler(instance) hlsChannel.setStreamHandler(instance) + pollsChannel.setStreamHandler(instance) registrar.addMethodCallDelegate(instance, channel: channel) } @@ -85,7 +90,8 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene logsEventChannel: FlutterEventChannel, rtcStatsEventChannel: FlutterEventChannel, sessionEventChannel: FlutterEventChannel, - hlsPlayerChannel: FlutterEventChannel) { + hlsPlayerChannel: FlutterEventChannel, + pollsEventChannel: FlutterEventChannel) { self.channel = channel self.meetingEventChannel = meetingEventChannel @@ -94,6 +100,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene self.rtcStatsEventChannel = rtcStatsEventChannel self.sessionEventChannel = sessionEventChannel self.hlsPlayerChannel = hlsPlayerChannel + self.pollsEventChannel = pollsEventChannel } public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { @@ -116,6 +123,8 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene sessionSink = events case "hls_player": hlsPlayerSink = events + case "polls": + pollsEventSink = events default: return FlutterError(code: #function, message: "invalid event sink name", details: arguments) } @@ -164,6 +173,12 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } else { print(#function, "hlsPlayerChannel not found") } + if pollsEventChannel != nil { + pollsEventChannel!.setStreamHandler(nil) + pollsEventSink = nil + } else{ + print(#function, "pollsEventChannel not found") + } } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -294,9 +309,15 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene case "get_room_layout": getRoomLayout(call, result) + // MARK: - Large room APIs case "get_peer_list_iterator", "peer_list_iterator_has_next", "peer_list_iterator_next": 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": + pollsAction(call, result) + default: result(FlutterMethodNotImplemented) } @@ -401,6 +422,43 @@ 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": + var listener : HMSInteractivityCenter.HMSPollListener = { [weak self] hmsPoll, hmsPollUpdateType in + + guard let self = self else { return } + + var 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){ + currentPolls.append(hmsPoll) + }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}){ + currentPolls[index] = hmsPoll + } + } + self.pollsEventSink?(map) + } + hmsSDK?.interactivityCenter.addPollUpdateListner(listener) + break + case "remove_poll_update_listener": + break + default: + HMSPollAction.pollActions(call, result, hmsSDK, currentPolls) + } + } // MARK: - Track Setting var audioMixerSourceMap = [String: HMSAudioNode]() diff --git a/packages/hmssdk_flutter/lib/assets/sdk-versions.json b/packages/hmssdk_flutter/lib/assets/sdk-versions.json index f78aa5d02..669853ab3 100644 --- a/packages/hmssdk_flutter/lib/assets/sdk-versions.json +++ b/packages/hmssdk_flutter/lib/assets/sdk-versions.json @@ -1,7 +1,7 @@ { - "flutter": "1.9.6", - "ios": "1.4.2", + "flutter": "1.9.9", + "ios": "1.5.0", "iOSBroadcastExtension": "0.0.9", "iOSHLSPlayerSDK": "0.0.2", - "android": "2.8.8" + "android": "2.9.0" } diff --git a/packages/hmssdk_flutter/lib/hmssdk_flutter.dart b/packages/hmssdk_flutter/lib/hmssdk_flutter.dart index 121adadd8..5ab479460 100644 --- a/packages/hmssdk_flutter/lib/hmssdk_flutter.dart +++ b/packages/hmssdk_flutter/lib/hmssdk_flutter.dart @@ -18,10 +18,11 @@ export 'src/enum/hms_message_recipient_type.dart'; export 'src/enum/hms_log_level.dart'; export 'src/enum/hms_stats_listener_method.dart'; export 'src/enum/hms_track_init_state.dart'; -export 'src/enum/hms_Quality_limitation_reason.dart'; +export 'src/enum/hms_quality_limitation_reason_enum.dart'; export 'src/enum/hms_simulcast_layer.dart'; export 'src/enum/hms_audio_mode.dart'; export 'src/enum/hms_hls_playback_state.dart'; +export 'src/enum/hms_poll_enum.dart'; //EXCEPTIONS export 'src/exceptions/hms_exception.dart'; @@ -62,7 +63,6 @@ export 'src/model/hms_peer_removed_from_room.dart'; export 'src/model/hms_message_recipient.dart'; export 'src/model/hms_logs_listener.dart'; export 'src/model/hms_actions_result_listener.dart'; -export 'src/model/hms_message_result_listener.dart'; export 'src/enum/hms_action_result_listener_method.dart'; export 'src/model/hms_remote_peer.dart'; export 'src/model/hms_hls_config.dart'; @@ -102,6 +102,12 @@ export 'src/model/hms_peer_list_iterator.dart'; export 'src/model/peer_list_iterator_options.dart'; export 'src/enum/hms_recording_state.dart'; export 'src/enum/hms_streaming_state.dart'; +export 'src/model/polls/hms_poll_interactivity_center.dart'; +export 'src/model/polls/hms_poll_listener.dart'; +export 'src/model/polls/hms_poll_question_builder.dart'; +export 'src/model/polls/hms_poll.dart'; +export 'src/model/polls/hms_poll_question.dart'; +export 'src/model/polls/hms_poll_question_option.dart'; //Views export 'src/ui/meeting/hms_texture_view.dart'; diff --git a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart index 1d22ec82c..2a67661bc 100644 --- a/packages/hmssdk_flutter/lib/src/common/platform_methods.dart +++ b/packages/hmssdk_flutter/lib/src/common/platform_methods.dart @@ -193,14 +193,26 @@ enum PlatformMethod { getPeerListIterator, peerListIteratorHasNext, peerListIteratorNext, + + ///Raise/Lower Hand lowerLocalPeerHand, lowerRemotePeerHand, raiseLocalPeerHand, + + ///TextureView methods createTextureView, disposeTextureView, addTrack, removeTrack, - setDisplayResolution + setDisplayResolution, + + ///Poll methods + addPollUpdateListener, + removePollUpdateListener, + quickStartPoll, + addSingleChoicePollResponse, + addMultiChoicePollResponse, + stopPoll } extension PlatformMethodValues on PlatformMethod { @@ -487,12 +499,15 @@ extension PlatformMethodValues on PlatformMethod { case PlatformMethod.peerListIteratorNext: return "peer_list_iterator_next"; + ///Raise/Lower Hand case PlatformMethod.lowerLocalPeerHand: return "lower_local_peer_hand"; case PlatformMethod.lowerRemotePeerHand: return "lower_remote_peer_hand"; case PlatformMethod.raiseLocalPeerHand: return "raise_local_peer_hand"; + + ///TextureView methods case PlatformMethod.createTextureView: return "create_texture_view"; case PlatformMethod.disposeTextureView: @@ -503,6 +518,20 @@ extension PlatformMethodValues on PlatformMethod { return "remove_track"; case PlatformMethod.setDisplayResolution: return "set_display_resolution"; + + ///Poll Methods + case PlatformMethod.addPollUpdateListener: + return "add_poll_update_listener"; + case PlatformMethod.removePollUpdateListener: + return "remove_poll_update_listener"; + case PlatformMethod.quickStartPoll: + return "quick_start_poll"; + case PlatformMethod.addSingleChoicePollResponse: + return "add_single_choice_poll_response"; + case PlatformMethod.addMultiChoicePollResponse: + return "add_multi_choice_poll_response"; + case PlatformMethod.stopPoll: + return "stop_poll"; default: return 'unknown'; } @@ -546,9 +575,6 @@ extension PlatformMethodValues on PlatformMethod { case 'on_re_connected': return PlatformMethod.onReconnected; - case 'on_re_connected': - return PlatformMethod.onReconnected; - case 'switch_audio': return PlatformMethod.switchAudio; @@ -791,12 +817,15 @@ extension PlatformMethodValues on PlatformMethod { case "peer_list_iterator_next": return PlatformMethod.peerListIteratorNext; + ///raise/lower hand case "lower_local_peer_hand": return PlatformMethod.lowerLocalPeerHand; case "lower_remote_peer_hand": return PlatformMethod.lowerRemotePeerHand; case "raise_local_peer_hand": return PlatformMethod.raiseLocalPeerHand; + + ///TextureView methods case "create_texture_view": return PlatformMethod.createTextureView; case "dispose_texture_view": @@ -807,6 +836,20 @@ extension PlatformMethodValues on PlatformMethod { return PlatformMethod.removeTrack; case "set_display_resolution": return PlatformMethod.setDisplayResolution; + + ///Poll methods + case "add_poll_update_listener": + return PlatformMethod.addPollUpdateListener; + case "remove_poll_update_listener": + return PlatformMethod.removePollUpdateListener; + case "quick_start_poll": + return PlatformMethod.quickStartPoll; + case "add_single_choice_poll_response": + return PlatformMethod.addSingleChoicePollResponse; + case "add_multi_choice_poll_response": + return PlatformMethod.addMultiChoicePollResponse; + case "stop_poll": + return PlatformMethod.stopPoll; default: return PlatformMethod.unknown; } diff --git a/packages/hmssdk_flutter/lib/src/enum/hms_action_result_listener_method.dart b/packages/hmssdk_flutter/lib/src/enum/hms_action_result_listener_method.dart index 13c400c4b..54beb1935 100644 --- a/packages/hmssdk_flutter/lib/src/enum/hms_action_result_listener_method.dart +++ b/packages/hmssdk_flutter/lib/src/enum/hms_action_result_listener_method.dart @@ -26,5 +26,8 @@ enum HMSActionResultListenerMethod { lowerLocalPeerHand, lowerRemotePeerHand, raiseLocalPeerHand, + quickStartPoll, + addSingleChoicePollResponse, + addMultiChoicePollResponse, unknown } diff --git a/packages/hmssdk_flutter/lib/src/enum/hms_poll_enum.dart b/packages/hmssdk_flutter/lib/src/enum/hms_poll_enum.dart new file mode 100644 index 000000000..322137b9b --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/enum/hms_poll_enum.dart @@ -0,0 +1,160 @@ +///[HMSPollUserTrackingMode] is the mode based on which the app identifies the user +enum HMSPollUserTrackingMode { user_id, peer_id, username } + +extension HMSPollUserTrackingModeValues on HMSPollUserTrackingMode { + static HMSPollUserTrackingMode getHMSPollUserTrackingModeFromString( + String pollTrackingMode) { + switch (pollTrackingMode) { + case "user_id": + return HMSPollUserTrackingMode.user_id; + case "peer_id": + return HMSPollUserTrackingMode.peer_id; + case "username": + return HMSPollUserTrackingMode.username; + default: + return HMSPollUserTrackingMode.user_id; + } + } + + static String getStringFromHMSPollUserTrackingMode( + HMSPollUserTrackingMode hmsPollUserTrackingMode) { + switch (hmsPollUserTrackingMode) { + case HMSPollUserTrackingMode.user_id: + return "user_id"; + case HMSPollUserTrackingMode.peer_id: + return "peer_id"; + case HMSPollUserTrackingMode.username: + return "username"; + default: + return "user_id"; + } + } +} + +///The [HMSPollCategory] enum categorizes whether a poll or quiz is being represented. +enum HMSPollCategory { poll, quiz } + +extension HMSPollCategoryValues on HMSPollCategory { + static HMSPollCategory getHMSPollCategoryFromString(String pollCategory) { + switch (pollCategory) { + case "poll": + return HMSPollCategory.poll; + case "quiz": + return HMSPollCategory.quiz; + default: + return HMSPollCategory.poll; + } + } + + static String getStringFromHMSPollCategory(HMSPollCategory hmsPollCategory) { + switch (hmsPollCategory) { + case HMSPollCategory.poll: + return "poll"; + case HMSPollCategory.quiz: + return "quiz"; + default: + return "poll"; + } + } +} + +///[HMSPollQuestionType] enum categorizes the type of question +enum HMSPollQuestionType { singleChoice, multiChoice, shortAnswer, longAnswer } + +extension HMSPollQuestionTypeValues on HMSPollQuestionType { + static HMSPollQuestionType getHMSPollQuestionTypeFromString( + String pollQuestionType) { + switch (pollQuestionType) { + case "single_choice": + return HMSPollQuestionType.singleChoice; + case "multi_choice": + return HMSPollQuestionType.multiChoice; + case "short_answer": + return HMSPollQuestionType.shortAnswer; + case "long_answer": + return HMSPollQuestionType.longAnswer; + default: + return HMSPollQuestionType.singleChoice; + } + } + + static String getStringFromHMSPollQuestionType( + HMSPollQuestionType hmsPollQuestionType) { + switch (hmsPollQuestionType) { + case HMSPollQuestionType.singleChoice: + return "single_choice"; + case HMSPollQuestionType.multiChoice: + return "multi_choice"; + case HMSPollQuestionType.shortAnswer: + return "short_answer"; + case HMSPollQuestionType.longAnswer: + return "long_answer"; + default: + return "single_choice"; + } + } +} + +///[HMSPollState] enum represents the different states a poll can be in. +enum HMSPollState { started, stopped, created } + +extension HMSPollStateValues on HMSPollState { + static HMSPollState getHMSPollStateFromString(String pollState) { + switch (pollState) { + case "started": + return HMSPollState.started; + case "stopped": + return HMSPollState.stopped; + case "created": + return HMSPollState.created; + default: + return HMSPollState.created; + } + } + + static String getStringFromHMSPollState(HMSPollState hmsPollState) { + switch (hmsPollState) { + case HMSPollState.started: + return "started"; + case HMSPollState.stopped: + return "stopped"; + case HMSPollState.created: + return "created"; + default: + return "created"; + } + } +} + +///[HMSPollUpdateType] enum represents different types of updates that can occur in a poll. +enum HMSPollUpdateType { started, stopped, resultsupdated } + +extension HMSPollUpdateTypeValues on HMSPollUpdateType { + static HMSPollUpdateType getHMSPollUpdateTypeFromString( + String pollUpdateType) { + switch (pollUpdateType) { + case "started": + return HMSPollUpdateType.started; + case "stopped": + return HMSPollUpdateType.stopped; + case "results_updated": + return HMSPollUpdateType.resultsupdated; + default: + return HMSPollUpdateType.resultsupdated; + } + } +} + +///[HMSPollListenerMethod] contains the [HMSPollListener] methods +enum HMSPollListenerMethod { onPollUpdate, unknown } + +extension HMSPollListenerMethodValues on HMSPollListenerMethod { + static HMSPollListenerMethod getMethodFromName(String name) { + switch (name) { + case 'on_poll_update': + return HMSPollListenerMethod.onPollUpdate; + default: + return HMSPollListenerMethod.unknown; + } + } +} diff --git a/packages/hmssdk_flutter/lib/src/enum/hms_Quality_limitation_reason.dart b/packages/hmssdk_flutter/lib/src/enum/hms_quality_limitation_reason_enum.dart similarity index 100% rename from packages/hmssdk_flutter/lib/src/enum/hms_Quality_limitation_reason.dart rename to packages/hmssdk_flutter/lib/src/enum/hms_quality_limitation_reason_enum.dart diff --git a/packages/hmssdk_flutter/lib/src/model/hms_message_result_listener.dart b/packages/hmssdk_flutter/lib/src/model/hms_message_result_listener.dart deleted file mode 100644 index 1e33a3d28..000000000 --- a/packages/hmssdk_flutter/lib/src/model/hms_message_result_listener.dart +++ /dev/null @@ -1,8 +0,0 @@ -// Project imports: -import '../../hmssdk_flutter.dart'; - -abstract class HMSMessageResultListener { - void onSuccess({required HMSMessage hmsMessage}); - - void onError({HMSException? hmsException}); -} diff --git a/packages/hmssdk_flutter/lib/src/model/hms_permissions.dart b/packages/hmssdk_flutter/lib/src/model/hms_permissions.dart index d4f60d2d1..bc1792034 100644 --- a/packages/hmssdk_flutter/lib/src/model/hms_permissions.dart +++ b/packages/hmssdk_flutter/lib/src/model/hms_permissions.dart @@ -10,6 +10,8 @@ class HMSPermissions { final bool? removeOthers; final bool? rtmpStreaming; final bool? unMute; + final bool? pollRead; + final bool? pollWrite; HMSPermissions( {this.endRoom, @@ -19,19 +21,22 @@ class HMSPermissions { this.browserRecording, this.mute, this.unMute, - this.changeRole}); + this.changeRole, + this.pollRead, + this.pollWrite}); factory HMSPermissions.fromMap(Map map) { return HMSPermissions( - browserRecording: map["browser_recording"], - changeRole: map['change_role'], - endRoom: map['end_room'], - hlsStreaming: map['hls_streaming'], - mute: map['mute'], - removeOthers: map['remove_others'], - rtmpStreaming: map['rtmp_streaming'], - unMute: map['un_mute'], - ); + browserRecording: map["browser_recording"], + changeRole: map['change_role'], + endRoom: map['end_room'], + hlsStreaming: map['hls_streaming'], + mute: map['mute'], + removeOthers: map['remove_others'], + rtmpStreaming: map['rtmp_streaming'], + unMute: map['un_mute'], + pollRead: map['poll_read'], + pollWrite: map['poll_write']); } Map toJson() { @@ -43,7 +48,9 @@ class HMSPermissions { 'un_mute': unMute, 'hls_streaming': hlsStreaming, 'rtmp_streaming': rtmpStreaming, - 'change_role': changeRole + 'change_role': changeRole, + 'poll_read': pollRead, + 'poll_write': pollWrite }; } } diff --git a/packages/hmssdk_flutter/lib/src/model/hms_quality_limitation_reasons.dart b/packages/hmssdk_flutter/lib/src/model/hms_quality_limitation_reasons.dart index b401d1c60..e951f4beb 100644 --- a/packages/hmssdk_flutter/lib/src/model/hms_quality_limitation_reasons.dart +++ b/packages/hmssdk_flutter/lib/src/model/hms_quality_limitation_reasons.dart @@ -1,4 +1,4 @@ -import 'package:hmssdk_flutter/src/enum/hms_Quality_limitation_reason.dart'; +import 'package:hmssdk_flutter/src/enum/hms_quality_limitation_reason_enum.dart'; class HMSQualityLimitationReasons { double? bandWidth; diff --git a/packages/hmssdk_flutter/lib/src/model/platform_method_response.dart b/packages/hmssdk_flutter/lib/src/model/platform_method_response.dart index 130cb25cd..223fe02f6 100644 --- a/packages/hmssdk_flutter/lib/src/model/platform_method_response.dart +++ b/packages/hmssdk_flutter/lib/src/model/platform_method_response.dart @@ -82,3 +82,13 @@ class HMSHLSPlayerPlaybackEventResponse { HMSHLSPlayerPlaybackEventResponse({required this.method, required this.data}); } + +///HMSPollListenerMethodResponse contains all the responses sent from the poll channel +/// +/// Checkout different responses in [HMSPollListenerMethod] enum +class HMSPollListenerMethodResponse { + final HMSPollListenerMethod method; + final Map data; + + HMSPollListenerMethodResponse({required this.method, required this.data}); +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll.dart new file mode 100644 index 000000000..973a538fc --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll.dart @@ -0,0 +1,92 @@ +///Project imports +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/model/hms_date_extension.dart'; +import 'package:hmssdk_flutter/src/model/polls/hms_poll_result_display.dart'; + +///[HMSPoll] class represents poll +/// +///This class encapsulates various properties and methods related to a poll, including its ID, title, anonymity status, +///category (poll or quiz), creator, duration, user tracking mode, question count, questions, result display settings, +///roles that can view responses, roles that can vote, start time, starter, current state, stop time, and stopper. +class HMSPoll { + final String pollId; + final String title; + final bool anonymous; + final HMSPollCategory category; + final HMSPeer? createdBy; + final Duration? duration; + final HMSPollUserTrackingMode? pollUserTrackingMode; + final int? questionCount; + final List? questions; + final HMSPollResultDisplay? result; + final List rolesThatCanViewResponses; + final List rolesThatCanVote; + final DateTime? startedAt; + final HMSPeer? startedBy; + final HMSPollState state; + final DateTime? stoppedAt; + final HMSPeer? stoppedBy; + + HMSPoll({ + required this.pollId, + required this.title, + required this.anonymous, + required this.category, + required this.createdBy, + required this.duration, + required this.pollUserTrackingMode, + required this.questionCount, + required this.questions, + required this.result, + required this.rolesThatCanViewResponses, + required this.rolesThatCanVote, + required this.startedAt, + required this.startedBy, + required this.state, + required this.stoppedAt, + required this.stoppedBy, + }); + + factory HMSPoll.fromMap(Map map) { + return HMSPoll( + pollId: map['poll_id'], + title: map['title'], + anonymous: map['anonymous'], + category: + HMSPollCategoryValues.getHMSPollCategoryFromString(map['category']), + createdBy: + map['created_by'] != null ? HMSPeer.fromMap(map['created_by']) : null, + duration: map['duration'] != null + ? Duration(milliseconds: map['duration']) + : null, + pollUserTrackingMode: map['mode'] != null + ? HMSPollUserTrackingModeValues.getHMSPollUserTrackingModeFromString( + map['mode']) + : null, + questionCount: map['question_count'], + questions: (map['questions'] as List) + .map((e) => HMSPollQuestion.fromMap(e)) + .toList(), + result: map['result'] != null + ? HMSPollResultDisplay.fromMap(map['result']) + : null, + rolesThatCanViewResponses: (map['roles_that_can_view_responses'] as List) + .map((e) => HMSRole.fromMap(e)) + .toList(), + rolesThatCanVote: (map['roles_that_can_vote'] as List) + .map((e) => HMSRole.fromMap(e)) + .toList(), + startedAt: map.containsKey("started_at") + ? HMSDateExtension.convertDateFromEpoch(map['started_at']) + : null, + startedBy: + map['started_by'] != null ? HMSPeer.fromMap(map['started_by']) : null, + state: HMSPollStateValues.getHMSPollStateFromString(map['state']), + stoppedAt: map['stopped_at'] != null + ? HMSDateExtension.convertDateFromEpoch(map['stopped_at']) + : null, + stoppedBy: + map['stopped_by'] != null ? HMSPeer.fromMap(map['stopped_by']) : null, + ); + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_answer.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_answer.dart new file mode 100644 index 000000000..7e00f466c --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_answer.dart @@ -0,0 +1,56 @@ +///Project imports +import 'package:hmssdk_flutter/src/enum/hms_poll_enum.dart'; + +///[HMSPollAnswer] class represents answer to poll questions +class HMSPollAnswer { + final String? answerText; + final Duration duration; + final int questionId; + final HMSPollQuestionType questionType; + final int? selectedOption; + final List? selectedOptions; + final bool skipped; + final bool update; + + HMSPollAnswer({ + this.answerText, + required this.duration, + required this.questionId, + required this.questionType, + this.selectedOption, + this.selectedOptions, + required this.skipped, + required this.update, + }); + + ///Method to get HMSPollAnswer from map + factory HMSPollAnswer.fromMap(Map map) { + return HMSPollAnswer( + answerText: map['answer'], + duration: Duration(seconds: map['duration']), + questionId: map['question_id'], + questionType: HMSPollQuestionTypeValues.getHMSPollQuestionTypeFromString( + map['question_type']), + selectedOption: map['selected_option'], + selectedOptions: map["selected_options"] != null + ? List.from(map['selected_options']) + : null, + skipped: map['skipped'], + update: map['update'], + ); + } + + ///Method to get map from HMSPollAnswer Object + Map toMap() { + return { + 'answer': answerText, + 'duration': duration.inSeconds, + 'question_id': questionId, + 'question_type': questionType.toString(), + 'selected_option': selectedOption, + 'selected_options': selectedOptions, + 'skipped': skipped, + 'update': update, + }; + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_answer_response.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_answer_response.dart new file mode 100644 index 000000000..88cc899cd --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_answer_response.dart @@ -0,0 +1,19 @@ +///Project imports +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +///The[HMSPollAnswerResponse] class represents the poll answer +class HMSPollAnswerResponse { + final bool correct; + final HMSException? error; + final int questionIndex; + + HMSPollAnswerResponse(this.error, this.questionIndex, + {required this.correct}); + + factory HMSPollAnswerResponse.fromMap(Map map) { + return HMSPollAnswerResponse( + map["error"] == null ? null : HMSException.fromMap(map["error"]), + map["question_index"], + correct: map["correct"]); + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_interactivity_center.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_interactivity_center.dart new file mode 100644 index 000000000..4e4c0e846 --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_interactivity_center.dart @@ -0,0 +1,267 @@ +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:hmssdk_flutter/src/model/polls/hms_poll_answer_response.dart'; +import 'package:hmssdk_flutter/src/service/platform_service.dart'; + +abstract class HMSPollInteractivityCenter { + ///[addPollUpdateListener] adds the poll update listener to send + ///the poll events to the application + ///**Parameters**: + /// + ///**listener** - [listener] + static void addPollUpdateListener({required HMSPollListener listener}) { + PlatformService.addPollUpdateListener(listener); + } + + ///[removePollUpdateListener] removes the poll update listener + ///Thus the application no longer receives poll events + static void removePollUpdateListener() { + PlatformService.removePollUpdateListener(); + } + + ///[quickStartPoll] starts a quick poll with supplied arguments + /// + ///**Parameters**: + /// + ///**pollBuilder** - [pollBuilder] is an object of HMSPollBuilder containing the poll configurations + /// + ///**hmsActionResultListener** - [hmsActionResultListener] is a callback whose [HMSActionResultListener.onSuccess] will be called when the action completes successfully. + /// + ///**Returns** + /// + /// Future - A Future representing the asynchronous operation. It will return either null if the operation is successful, or an [HMSException] if an error occurs. + /// + ///Refer [Quick Start Poll](Add docs link here) + static void quickStartPoll( + {required HMSPollBuilder pollBuilder, + required HMSActionResultListener? hmsActionResultListener}) async { + PlatformService.invokeMethod(PlatformMethod.quickStartPoll, + arguments: {"poll_builder": pollBuilder.toMap()}); + } + + ///[addSingleChoicePollResponse] method is used to answer a single choice poll + /// + ///**Parameters** + /// + ///**hmsPoll** - [hmsPoll] object for the poll that is being answered + /// + ///**pollQuestion** - [pollQuestion] object for the question that is being answered + /// + ///**optionSelected** - [optionSelected] object for the option that is selected + /// + ///**peer** - [peer] who is answering the poll + /// + ///**Returns** + /// + /// Future - A Future representing the asynchronous operation. It will return either null if the operation is successful, or an [HMSException] if an error occurs. + /// + ///Refer [addSingleChoicePollResponse](Add docs link here) + static Future addSingleChoicePollResponse( + {required HMSPoll hmsPoll, + required HMSPollQuestion pollQuestion, + required HMSPollQuestionOption optionSelected, + HMSPeer? peer}) async { + int questionIndex = + hmsPoll.questions?.indexWhere((element) => element == pollQuestion) ?? + -1; + if (questionIndex == -1) { + return HMSException( + message: "Question not found", + description: + "Question passed above does not match any question in the poll", + action: "Please pass correct question", + isTerminal: false); + } + var result = await PlatformService.invokeMethod( + PlatformMethod.addSingleChoicePollResponse, + arguments: { + "poll_id": hmsPoll.pollId, + "question_index": questionIndex, + "user_id": peer?.customerUserId, + "answer": optionSelected.toMap() + }); + + if (result != null) { + if (result["success"]) { + if (result["data"]["result"] != null) { + var data = result["data"]["result"]; + List pollResponses = []; + data.forEach((response) { + if (response != null) { + pollResponses.add(HMSPollAnswerResponse.fromMap(response)); + } + }); + return pollResponses; + } + } else { + if (result["data"]["error"] != null) { + return HMSException.fromMap(result["data"]["error"]); + } + } + } + } + + /// [stopPoll] method is used to stop a poll. + /// + ///**Parameters** + /// + ///**poll** - [poll] object representing the poll to be stopped. + /// + ///**Returns** + /// + ///Future - A Future representing the asynchronous operation. It will return either null if the operation is successful, or an [HMSException] if an error occurs. + /// + ///Refer [stopPoll](Add docs link here) + static Future stopPoll({required HMSPoll poll}) async { + var result = await PlatformService.invokeMethod(PlatformMethod.stopPoll, + arguments: {"poll_id": poll.pollId}); + + if (result != null) { + if (result["error"] != null) { + return HMSException.fromMap(result["error"]); + } else { + return null; + } + } + } + + ///[addMultiChoicePollResponse] method is used to answer a single choice poll + /// + ///**Parameters** + /// + ///**hmsPoll** - [hmsPoll] object for the poll that is being answered + /// + ///**pollQuestion** - [pollQuestion] object for the question that is being answered + /// + ///**optionsSelected** - [optionsSelected] list containing objects for the options selected + /// + ///**peer** - [peer] who is answering the poll + /// + ///Refer [addSingleChoicePollResponse](Add docs link here) + static Future addMultiChoicePollResponse( + {required HMSPoll hmsPoll, + required HMSPollQuestion pollQuestion, + required List optionsSelected, + HMSPeer? peer}) async { + int questionIndex = + hmsPoll.questions?.indexWhere((element) => element == pollQuestion) ?? + -1; + if (questionIndex == -1) { + HMSException( + message: "Question not found", + description: + "Question passed above does not match any question in the poll", + action: "Please pass correct question", + isTerminal: false); + return; + } + var optionsSelectedMap = optionsSelected.map((e) => e.toMap()).toList(); + var result = await PlatformService.invokeMethod( + PlatformMethod.addMultiChoicePollResponse, + arguments: { + "poll_id": hmsPoll.pollId, + "question_index": questionIndex, + "user_id": peer?.customerUserId, + "answer": optionsSelectedMap + }); + + if (result != null) { + if (result["success"]) { + if (result["data"]["result"] != null) { + var data = result["data"]["result"]; + List pollResponses = []; + data.forEach((response) { + if (response != null) { + pollResponses.add(HMSPollAnswerResponse.fromMap(response)); + } + }); + return pollResponses; + } + } else { + if (result["data"]["error"] != null) { + return HMSException.fromMap(result["data"]["error"]); + } + } + } + } +} + +///[HMSPollBuilder] is used to create polls +///It contains getters and setters for poll builder properties +class HMSPollBuilder { + bool? _isAnonymous; + Duration? _duration; + late HMSPollUserTrackingMode _mode; + late HMSPollCategory _pollCategory; + String? _pollId; + List _questions = []; + List? _rolesThatCanViewResponse; + List? _rolesThatCanVote; + late String _title; + + set withAnonymous(bool isAnonymous) { + _isAnonymous = isAnonymous; + } + + set withDuration(Duration duration) { + _duration = duration; + } + + set withMode(HMSPollUserTrackingMode userTrackingMode) { + _mode = userTrackingMode; + } + + set withCategory(HMSPollCategory pollCategory) { + _pollCategory = pollCategory; + } + + set withPollId(String pollId) { + _pollId = pollId; + } + + HMSPollBuilder addQuestion(HMSPollQuestionBuilder questionBuilder) { + _questions.add(questionBuilder); + return this; + } + + set withRolesThatCanViewResponses(List rolesThatCanViewResponses) { + if (_rolesThatCanViewResponse == null) { + _rolesThatCanViewResponse = []; + } + _rolesThatCanViewResponse?.addAll(rolesThatCanViewResponses); + } + + set withRolesThatCanVote(List rolesThatCanVote) { + if (_rolesThatCanVote == null) { + _rolesThatCanVote = []; + } + _rolesThatCanVote?.addAll(rolesThatCanVote); + } + + set withTitle(String title) { + _title = title; + } + + List get questions => _questions; + + HMSPollBuilder build() { + return this; + } + + Map toMap() { + return { + "anonymous": _isAnonymous, + "duration": _duration?.inMilliseconds, + "mode": + HMSPollUserTrackingModeValues.getStringFromHMSPollUserTrackingMode( + _mode), + "poll_category": + HMSPollCategoryValues.getStringFromHMSPollCategory(_pollCategory), + "poll_id": _pollId, + "questions": _questions.map((e) => e.toMap()).toList(), + "roles_that_can_view_responses": + _rolesThatCanViewResponse?.map((e) => e.name).toList(), + "roles_that_can_vote": _rolesThatCanVote?.map((e) => e.name).toList(), + "title": _title + }; + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_listener.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_listener.dart new file mode 100644 index 000000000..28e7dc90f --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_listener.dart @@ -0,0 +1,18 @@ +///Project imports +import 'package:hmssdk_flutter/src/enum/hms_poll_enum.dart'; +import 'package:hmssdk_flutter/src/model/polls/hms_poll.dart'; + +///100ms HMSPollListener +/// +///[HMSPollListener] provides callback related to poll state changes. If the application uses polls and quizzes then it is required +///to implement [HMSPollListener] to get updates related to polls. +abstract class HMSPollListener { + ///This is called whenever there are any changes related to the poll + ///i.e whether a poll is started or stopped or someone answered the poll + /// + /// - Parameters: + /// - poll: the poll object for which upgrade is triggered. + /// - pollUpdateType: pollUpdateType is an enum of [HMSPollUpdateType] + void onPollUpdate( + {required HMSPoll poll, required HMSPollUpdateType pollUpdateType}); +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question.dart new file mode 100644 index 000000000..884abe105 --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question.dart @@ -0,0 +1,74 @@ +///Project imports +import 'package:hmssdk_flutter/src/enum/hms_poll_enum.dart'; +import 'package:hmssdk_flutter/src/model/polls/hms_poll_answer.dart'; +import 'package:hmssdk_flutter/src/model/polls/hms_poll_question_answer.dart'; +import 'package:hmssdk_flutter/src/model/polls/hms_poll_question_option.dart'; + +///[HMSPollQuestion] class represents poll question +class HMSPollQuestion { + final int questionId; + final bool canSkip; + final HMSPollQuestionAnswer? correctAnswer; + final Duration duration; + final List myResponses; + final List options; + final bool canChangeResponse; + final String text; + final HMSPollQuestionType type; + final int weight; + final bool voted; + + HMSPollQuestion( + {required this.questionId, + required this.options, + required this.text, + required this.type, + required this.weight, + required this.voted, + required this.canSkip, + this.correctAnswer, + required this.duration, + required this.myResponses, + required this.canChangeResponse}); + + factory HMSPollQuestion.fromMap(Map map) { + return HMSPollQuestion( + questionId: map["question_id"], + canSkip: map['can_skip'], + correctAnswer: map['correct_answer'] != null + ? HMSPollQuestionAnswer.fromMap(map['correct_answer']) + : null, + duration: Duration(milliseconds: map['duration']), + myResponses: map['my_responses'] != null + ? (map['my_responses'] as List) + .map((e) => HMSPollAnswer.fromMap(e)) + .toList() + : [], + options: map['options'] != null + ? (map['options'] as List) + .map((e) => HMSPollQuestionOption.fromMap(e)) + .toList() + : [], + text: map['text'], + type: HMSPollQuestionTypeValues.getHMSPollQuestionTypeFromString( + map['type']), + weight: map['weight'], + voted: map['voted'], + canChangeResponse: map['can_change_response'], + ); + } + + Map toMap() { + return { + 'can_skip': canSkip, + 'correct_answer': correctAnswer?.toMap(), + 'duration': duration.inMilliseconds, + 'my_responses': myResponses.map((e) => e.toMap()).toList(), + 'options': options.map((e) => e.toMap()).toList(), + 'text': text, + 'type': HMSPollQuestionTypeValues.getStringFromHMSPollQuestionType(type), + 'weight': weight, + 'voted': voted, + }; + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_answer.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_answer.dart new file mode 100644 index 000000000..85891cdcd --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_answer.dart @@ -0,0 +1,28 @@ +///[HMSPollQuestionAnswer] class represents the answer to poll questions +class HMSPollQuestionAnswer { + final bool hidden; + final int? option; + final List? options; + + HMSPollQuestionAnswer({ + required this.hidden, + this.option, + this.options, + }); + + factory HMSPollQuestionAnswer.fromMap(Map map) { + return HMSPollQuestionAnswer( + hidden: map['hidden'] ?? false, + option: map['option'], + options: map['options']?.cast(), + ); + } + + Map toMap() { + return { + 'hidden': this.hidden, + 'option': this.option, + 'options': this.options, + }; + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_builder.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_builder.dart new file mode 100644 index 000000000..a899f5c23 --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_builder.dart @@ -0,0 +1,106 @@ +///Project imports +import 'package:hmssdk_flutter/src/enum/hms_poll_enum.dart'; + +///[HMSPollQuestionBuilder] is used to create questions for polls +///It contains getters and setters for poll question builder properties +class HMSPollQuestionBuilder { + bool? _canSkip; + bool? _canChangeResponse; + String? _title; + Duration? _duration; + List _options = []; + List _pollOptions = []; + String? _text; + HMSPollQuestionType _type = HMSPollQuestionType.singleChoice; + int? _weight; + bool? _answerHidden; + int? _maxLength; + int? _minLength; + + set withCanSkip(bool canSkip) { + _canSkip = canSkip; + } + + set withText(String text) { + _text = text; + } + + set withTitle(String title) { + _title = title; + } + + set withDuration(Duration duration) { + _duration = duration; + } + + set withType(HMSPollQuestionType type) { + _type = type; + } + + set withWeight(int weight) { + _weight = weight; + } + + set withMaxLength(int maxLength) { + _maxLength = maxLength; + } + + set withMinLength(int minLength) { + _minLength = minLength; + } + + set withAnswerHidden(bool answerHidden) { + _answerHidden = answerHidden; + } + + set withCanChangeResponse(bool canChangeResponse) { + _canChangeResponse = canChangeResponse; + } + + set withOption(List options) { + _pollOptions = options; + } + + set addQuizOption(List options) { + _options.addAll(options); + } + + List get pollOptions => _pollOptions; + + String? get title => _title; + + String? get text => _text; + + HMSPollQuestionType get type => _type; + + Map toMap() { + return { + 'can_skip': _canSkip, + 'title': _title, + 'duration': _duration?.inMilliseconds, + 'options': _options, + 'poll_options': _pollOptions, + 'text': _text, + 'type': HMSPollQuestionTypeValues.getStringFromHMSPollQuestionType(_type), + 'weight': _weight, + 'answer_hidden': _answerHidden, + 'max_length': _maxLength, + 'min_length': _minLength, + 'can_change_response': _canChangeResponse, + }; + } +} + +class HMSPollQuizOption { + final String text; + final bool isCorrect; + + HMSPollQuizOption({required this.text, required this.isCorrect}); + + Map toMap() { + return { + 'text': text, + 'is_correct': isCorrect, + }; + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_option.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_option.dart new file mode 100644 index 000000000..f0db1d47d --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_question_option.dart @@ -0,0 +1,31 @@ +///[HMSPollQuestionOption] represents options for poll questions +class HMSPollQuestionOption { + final int index; + final String? text; + final int voteCount; + final int? weight; + + HMSPollQuestionOption( + {required this.index, + this.text, + required this.voteCount, + required this.weight}); + + factory HMSPollQuestionOption.fromMap(Map map) { + return HMSPollQuestionOption( + index: map['index'], + text: map['text'], + voteCount: map['vote_count'], + weight: map['weight'], + ); + } + + Map toMap() { + return { + 'index': index, + 'text': text, + 'vote_count': voteCount, + 'weight': weight, + }; + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_result_display.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_result_display.dart new file mode 100644 index 000000000..76d2ec2fd --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_result_display.dart @@ -0,0 +1,30 @@ +///Project imports +import 'package:hmssdk_flutter/src/model/polls/hms_poll_stats_question.dart'; + +///[HMSPollResultDisplay] class represents the poll results +class HMSPollResultDisplay { + final List? questions; + final int? totalDistinctUsers; + final int? totalResponses; + final int? votingUsers; + + HMSPollResultDisplay({ + this.questions, + this.totalDistinctUsers, + this.totalResponses, + this.votingUsers, + }); + + factory HMSPollResultDisplay.fromMap(Map map) { + return HMSPollResultDisplay( + questions: map['questions'] != null + ? (map['questions'] as List) + .map((e) => HMSPollStatsQuestion.fromMap(e)) + .toList() + : [], + totalDistinctUsers: map['total_distinct_users'], + totalResponses: map['total_responses'], + votingUsers: map['voting_users'], + ); + } +} diff --git a/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_stats_question.dart b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_stats_question.dart new file mode 100644 index 000000000..f84594044 --- /dev/null +++ b/packages/hmssdk_flutter/lib/src/model/polls/hms_poll_stats_question.dart @@ -0,0 +1,32 @@ +///Project imports +import 'package:hmssdk_flutter/src/enum/hms_poll_enum.dart'; + +///The [HMSPollStatsQuestion] class represents statistics for a single question in a poll. +class HMSPollStatsQuestion { + final int attemptedTimes; + final int? correct; + final List options; + final HMSPollQuestionType type; + final int skipped; + + HMSPollStatsQuestion({ + required this.attemptedTimes, + this.correct, + required this.options, + required this.type, + required this.skipped, + }); + + factory HMSPollStatsQuestion.fromMap(Map map) { + return HMSPollStatsQuestion( + attemptedTimes: map['attempted_times'], + correct: map['correct'], + options: map['options'] != null + ? (map['options'] as List).map((e) => e as int).toList() + : [], + type: HMSPollQuestionTypeValues.getHMSPollQuestionTypeFromString( + map['question_type']), + skipped: map['skipped'], + ); + } +} diff --git a/packages/hmssdk_flutter/lib/src/service/platform_service.dart b/packages/hmssdk_flutter/lib/src/service/platform_service.dart index 509cf3fb3..340f446f2 100644 --- a/packages/hmssdk_flutter/lib/src/service/platform_service.dart +++ b/packages/hmssdk_flutter/lib/src/service/platform_service.dart @@ -18,7 +18,7 @@ import 'package:hmssdk_flutter/src/enum/hms_key_change_listener_method.dart'; import 'package:hmssdk_flutter/src/enum/hms_logs_update_listener.dart'; import 'package:hmssdk_flutter/src/model/hms_key_change_observer.dart'; -class PlatformService { +abstract class PlatformService { ///used to pass data to platform using methods static const MethodChannel _channel = const MethodChannel('hmssdk_flutter'); @@ -45,6 +45,9 @@ class PlatformService { static const EventChannel _hlsPlayerChannel = const EventChannel("hls_player_channel"); + static const EventChannel _pollsEventChannel = + const EventChannel("polls_event_channel"); + ///add meeting listeners. static List updateListeners = []; @@ -57,6 +60,8 @@ class PlatformService { static List hlsPlaybackEventListener = []; + static HMSPollListener? _pollListener; + ///List for event Listener static List statsListeners = []; static bool isStartedListening = false; @@ -97,6 +102,16 @@ class PlatformService { } } + static void addPollUpdateListener(HMSPollListener listener) { + _pollListener = listener; + PlatformService.invokeMethod(PlatformMethod.addPollUpdateListener); + } + + static void removePollUpdateListener() { + _pollListener = null; + PlatformService.invokeMethod(PlatformMethod.removePollUpdateListener); + } + static void addLogsListener( HMSLogListener hmsLogListener, ) { @@ -526,6 +541,26 @@ class PlatformService { }).listen((event) { notifyHLSPlaybackEventListeners(event.method, event.data); }); + + _pollsEventChannel.receiveBroadcastStream({'name': 'polls'}).map((event) { + HMSPollListenerMethod method = + HMSPollListenerMethodValues.getMethodFromName(event['event_name']); + Map data = event['data']; + return HMSPollListenerMethodResponse(method: method, data: data); + }).listen((event) { + HMSPollListenerMethod method = event.method; + switch (method) { + case HMSPollListenerMethod.onPollUpdate: + _pollListener?.onPollUpdate( + poll: HMSPoll.fromMap(event.data["poll"]), + pollUpdateType: + HMSPollUpdateTypeValues.getHMSPollUpdateTypeFromString( + event.data["poll_update_type"])); + return; + case HMSPollListenerMethod.unknown: + break; + } + }); } static void notifyLogsUpdateListeners( diff --git a/packages/hmssdk_flutter/pubspec.yaml b/packages/hmssdk_flutter/pubspec.yaml index 4684fe9b1..45d0130d6 100644 --- a/packages/hmssdk_flutter/pubspec.yaml +++ b/packages/hmssdk_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: hmssdk_flutter description: Add Real Time Audio & Video calls, Interactive Live Streaming & Recording, Chat, HLS, RTMP, PiP, CallKit, VoIP, Video conferencing, Stream Player & WebRTC-based communications API -version: 1.9.8 +version: 1.9.9 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues