From 12e5817a913c899e53f04a036dabd54df929cb01 Mon Sep 17 00:00:00 2001 From: azduda Date: Wed, 11 Jan 2023 09:13:37 +0100 Subject: [PATCH] Add UI for file presentation service --- .../module/FilePresentationServiceModule.kt | 2 +- .../conference_controls.dart | 104 +++++++- .../participant_screen.dart | 55 +++- ...ile_presentation_service_test_buttons.dart | 245 ------------------ .../screens/test_buttons/test_buttons.dart | 5 - .../models/conference_model.dart | 16 +- test_app/lib/widgets/file_container.dart | 28 ++ .../lib/widgets/file_presentation_ui.dart | 167 ++++++++++++ 8 files changed, 357 insertions(+), 265 deletions(-) delete mode 100644 test_app/lib/screens/test_buttons/file_presentation_service_test_buttons.dart create mode 100644 test_app/lib/widgets/file_container.dart create mode 100644 test_app/lib/widgets/file_presentation_ui.dart diff --git a/android/src/main/kotlin/io/dolby/comms/sdk/flutter/module/FilePresentationServiceModule.kt b/android/src/main/kotlin/io/dolby/comms/sdk/flutter/module/FilePresentationServiceModule.kt index 65edcbde..ab4820dd 100644 --- a/android/src/main/kotlin/io/dolby/comms/sdk/flutter/module/FilePresentationServiceModule.kt +++ b/android/src/main/kotlin/io/dolby/comms/sdk/flutter/module/FilePresentationServiceModule.kt @@ -117,7 +117,7 @@ class FilePresentationServiceModule( onError = result::error, onSuccess = { getCurrentFileId() - .let { VoxeetSDK.filePresentation().update(it, call.argumentOrThrow("page")) } + .let { VoxeetSDK.filePresentation().update(it, call.argumentOrThrow("page")).await() } .let { result.success(null) } } ) diff --git a/test_app/lib/screens/participant_screen/conference_controls.dart b/test_app/lib/screens/participant_screen/conference_controls.dart index bfb1ba0c..721d0548 100644 --- a/test_app/lib/screens/participant_screen/conference_controls.dart +++ b/test_app/lib/screens/participant_screen/conference_controls.dart @@ -1,5 +1,6 @@ import 'package:dolbyio_comms_sdk_flutter_example/conference_ext.dart'; import 'package:dolbyio_comms_sdk_flutter_example/widgets/bottom_tool_bar.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:dolbyio_comms_sdk_flutter/dolbyio_comms_sdk_flutter.dart'; import 'package:provider/provider.dart'; @@ -14,7 +15,6 @@ typedef ParticipantConferenceStatus = void Function( class ConferenceControls extends StatefulWidget { final ParticipantConferenceStatus updateCloseSessionFlag; - const ConferenceControls({Key? key, required this.updateCloseSessionFlag}) : super(key: key); @@ -24,7 +24,10 @@ class ConferenceControls extends StatefulWidget { class _ConferenceControlsState extends State { final _dolbyioCommsSdkFlutterPlugin = DolbyioCommsSdk.instance; - bool isMicOff = false, isVideoOff = false, isScreenShareOff = true; + bool isMicOff = false; + bool isVideoOff = false; + bool isScreenShareOff = true; + FileConverted? _fileConverted; @override Widget build(BuildContext context) { @@ -52,16 +55,49 @@ class _ConferenceControlsState extends State { ? const Icon(Icons.videocam_off) : const Icon(Icons.videocam), ), - ConferenceActionIconButton( - iconWidget: isScreenShareOff - ? const Icon(Icons.screen_share) - : const Icon(Icons.stop_screen_share_sharp), - backgroundIconColor: Colors.deepPurple, - onPressedIcon: () { - if (isScreenShareOff) { - startScreenShare(); - } else { - stopScreenShare(); + PopupMenuButton( + position: PopupMenuPosition.under, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Container( + width: 50, + height: 50, + decoration: const BoxDecoration( + color: Colors.deepPurple, + shape: BoxShape.circle, + ), + child: const Icon(Icons.ios_share_rounded, color: Colors.white), + ), + ), + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + value: 0, + child: isScreenShareOff + ? const Text('Share screen') + : const Text('Stop sharing'), + ), + const PopupMenuItem( + value: 1, + child: Text('Share file'), + ), + ]; + }, + onSelected: (value) async { + switch (value) { + case 0: + if (isScreenShareOff) { + startScreenShare(); + } else { + stopScreenShare(); + } + break; + case 1: + await convertFile(); + if (_fileConverted != null) { + startFilePresentation(); + } + break; } }, ), @@ -195,6 +231,50 @@ class _ConferenceControlsState extends State { } } + Future convertFile() async { + try { + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['doc', 'docx', 'ppt', 'pptx', 'pdf'], + ); + String? path = result?.files.single.path; + + if (path == null) { + if (!mounted) return; + showResultDialog(context, 'File not selected', ''); + return; + } + + var fileConverted = await _dolbyioCommsSdkFlutterPlugin.filePresentation + .convert(File(path)); + _fileConverted = fileConverted; + if (!mounted) return; + showResultDialog(context, 'Success', 'File converted.'); + } catch (error) { + if (!mounted) return; + showResultDialog(context, 'Error: ', error.toString()); + } + } + + Future startFilePresentation() async { + try { + if (_fileConverted == null) { + if (!mounted) return; + showResultDialog(context, 'You must convert file first!', ''); + return; + } + + await _dolbyioCommsSdkFlutterPlugin.filePresentation + .start(_fileConverted!); + if (!mounted) return; + showResultDialog( + context, 'Success', 'File presentation has been started.'); + } catch (error) { + if (!mounted) return; + showResultDialog(context, 'Error: ', error.toString()); + } + } + Future isSomeoneScreenSharing() async { final conference = await _dolbyioCommsSdkFlutterPlugin.conference.current(); final participants = await _dolbyioCommsSdkFlutterPlugin.conference diff --git a/test_app/lib/screens/participant_screen/participant_screen.dart b/test_app/lib/screens/participant_screen/participant_screen.dart index 7a48de9c..bdaebcb4 100644 --- a/test_app/lib/screens/participant_screen/participant_screen.dart +++ b/test_app/lib/screens/participant_screen/participant_screen.dart @@ -1,7 +1,9 @@ import 'package:dolbyio_comms_sdk_flutter_example/state_management/models/conference_model.dart'; +import 'package:dolbyio_comms_sdk_flutter_example/widgets/file_presentation_ui.dart'; import 'package:dolbyio_comms_sdk_flutter_example/widgets/spatial_extensions/participant_spatial_values.dart'; import 'package:provider/provider.dart'; import '../../conference_ext.dart'; +import '../../widgets/file_container.dart'; import '../../widgets/spatial_extensions/spatial_values_model.dart'; import '../../widgets/status_snackbar.dart'; import '../test_buttons/test_buttons.dart'; @@ -77,10 +79,16 @@ class _ParticipantScreenContentState extends State { StreamSubscription>? _onRecordingChangeSubscription; + StreamSubscription< + Event>? + _onFilePresentationChangeSubscription; + Participant? _localParticipant; bool shouldCloseSessionOnLeave = false; List participants = []; bool _isScreenSharing = false; + bool isFilePresenting = false; + bool isLocalPresentingFile = false; @override void initState() { @@ -120,6 +128,42 @@ class _ParticipantScreenContentState extends State { "Recording status: ${event.body.recordingStatus} for conference: ${event.body.conferenceId}", const Duration(seconds: 2)); }); + + _onFilePresentationChangeSubscription = _dolbyioCommsSdkFlutterPlugin + .filePresentation + .onFilePresentationChange() + .listen((event) async { + if (event.type == + FilePresentationServiceEventNames.filePresentationStarted) { + Provider.of(context, listen: false).imageSource = + await _dolbyioCommsSdkFlutterPlugin.filePresentation + .getImage(event.body.position); + var localParticipant = await _dolbyioCommsSdkFlutterPlugin.conference + .getLocalParticipant(); + setState(() { + isFilePresenting = true; + if (event.body.owner.id == localParticipant.id) { + isLocalPresentingFile = true; + Provider.of(context, listen: false) + .amountOfPagesInDocument = event.body.imageCount - 1; + } + }); + } else if (event.type == + FilePresentationServiceEventNames.filePresentationUpdated) { + var imageSource = await _dolbyioCommsSdkFlutterPlugin.filePresentation + .getImage(event.body.position); + setState(() { + Provider.of(context, listen: false).imageSource = + imageSource; + }); + } else if (event.type == + FilePresentationServiceEventNames.filePresentationStopped) { + setState(() { + isFilePresenting = false; + isLocalPresentingFile = false; + }); + } + }); } @override @@ -130,12 +174,14 @@ class _ParticipantScreenContentState extends State { _streamsChangeSubscription?.cancel(); _onPermissionsChangeSubsription?.cancel(); _onRecordingChangeSubscription?.cancel(); + _onFilePresentationChangeSubscription?.cancel(); super.deactivate(); } @override Widget build(BuildContext context) { - Provider.of(context).setSpatialConferenceState(widget.isSpatialAudio); + Provider.of(context) + .setSpatialConferenceState(widget.isSpatialAudio); Provider.of(context).copyList(participants); Widget videoView = const FlutterLogo(); @@ -159,6 +205,13 @@ class _ParticipantScreenContentState extends State { child: Column( children: [ const ConferenceTitle(), + isFilePresenting + ? isLocalPresentingFile + ? const FilePresentationUI() + : const SingleChildScrollView( + child: FileContainer(), + ) + : const SizedBox.shrink(), Expanded( child: Stack( children: [ diff --git a/test_app/lib/screens/test_buttons/file_presentation_service_test_buttons.dart b/test_app/lib/screens/test_buttons/file_presentation_service_test_buttons.dart deleted file mode 100644 index 3bd00161..00000000 --- a/test_app/lib/screens/test_buttons/file_presentation_service_test_buttons.dart +++ /dev/null @@ -1,245 +0,0 @@ -import 'dart:async'; -import 'package:dolbyio_comms_sdk_flutter/dolbyio_comms_sdk_flutter.dart'; -import 'package:dolbyio_comms_sdk_flutter_example/widgets/secondary_button.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import '/widgets/dialogs.dart'; - -class FilePresentationServiceTestButtons extends StatefulWidget { - const FilePresentationServiceTestButtons({Key? key}) : super(key: key); - - @override - State createState() => - _FilePresentationServiceTestButtonsState(); -} - -class _FilePresentationServiceTestButtonsState - extends State { - final _dolbyioCommsSdkFlutterPlugin = DolbyioCommsSdk.instance; - FileConverted? _fileConverted; - int selectedPage = 0; - - final _eventListeners = >{}; - - @override - void initState() { - super.initState(); - - _eventListeners.add(_dolbyioCommsSdkFlutterPlugin.filePresentation - .onFileConverted() - .listen((event) { - switch (event.type) { - case FilePresentationServiceEventNames.fileConverted: - showDialog(context, 'File presentation evet: fileConverted', - 'On Event Change'); - break; - case FilePresentationServiceEventNames.filePresentationStarted: - case FilePresentationServiceEventNames.filePresentationStopped: - case FilePresentationServiceEventNames.filePresentationUpdated: - break; - } - })); - - _eventListeners.add(_dolbyioCommsSdkFlutterPlugin.filePresentation - .onFilePresentationChange() - .listen((event) { - switch (event.type) { - case FilePresentationServiceEventNames.fileConverted: - break; - case FilePresentationServiceEventNames.filePresentationStarted: - showDialog( - context, - 'File presentation event: filePresentationStarted', - 'On Event Change'); - break; - case FilePresentationServiceEventNames.filePresentationStopped: - showDialog( - context, - 'File presentation event: filePresentationStopped', - 'On Event Change'); - break; - case FilePresentationServiceEventNames.filePresentationUpdated: - showDialog( - context, - 'File presentation event: filePresentationUpdated', - 'On Event Change'); - break; - } - })); - } - - @override - void dispose() { - for (final element in _eventListeners) { - element.cancel(); - } - _eventListeners.clear(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Wrap( - spacing: 8.0, - runSpacing: 4.0, - children: [ - SecondaryButton( - text: 'Convert', onPressed: () => convert(), fillWidth: false), - SecondaryButton( - text: 'Start', - onPressed: () => startFilePresentation(), - fillWidth: false), - SecondaryButton( - text: 'Stop', - onPressed: () => stopFilePresentation(), - fillWidth: false), - SecondaryButton( - text: 'getCurrent', - onPressed: () => getCurrent(), - fillWidth: false), - SecondaryButton( - text: 'setPage', onPressed: () => setPage(), fillWidth: false), - SecondaryButton( - text: 'getImage', onPressed: () => getImage(), fillWidth: false), - SecondaryButton( - text: 'getThumbnail', - onPressed: () => getThumbnail(), - fillWidth: false), - ], - ); - } - - Future showDialog( - BuildContext context, String title, String text) async { - await ViewDialogs.dialog( - context: context, - title: title, - body: text, - ); - } - - Future convert() async { - try { - FilePickerResult? result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['doc', 'docx', 'ppt', 'pptx', 'pdf'], - ); - String? path = result?.files.single.path; - - if (path == null) { - if (!mounted) return; - showDialog(context, 'File not selected', ''); - return; - } - - var fileConverted = await _dolbyioCommsSdkFlutterPlugin.filePresentation - .convert(File(path)); - _fileConverted = fileConverted; - if (!mounted) return; - showDialog(context, 'Success', 'OK'); - } catch (error) { - if (!mounted) return; - showDialog(context, 'Error: ', error.toString()); - } - } - - Future startFilePresentation() async { - try { - if (_fileConverted == null) { - if (!mounted) return; - showDialog(context, 'You must convert file first!', ''); - return; - } - - await _dolbyioCommsSdkFlutterPlugin.filePresentation - .start(_fileConverted!); - if (!mounted) return; - showDialog(context, 'Success', 'OK'); - } catch (error) { - if (!mounted) return; - showDialog(context, 'Error: ', error.toString()); - } - } - - Future stopFilePresentation() async { - try { - await _dolbyioCommsSdkFlutterPlugin.filePresentation.stop(); - if (!mounted) return; - showDialog(context, 'Success', 'OK'); - } catch (error) { - if (!mounted) return; - showDialog(context, 'Error: ', error.toString()); - } - } - - Future getCurrent() async { - try { - if (_fileConverted == null) { - if (!mounted) return; - showDialog(context, 'You must start presentation first!', ''); - return; - } - - var filePresentation = - await _dolbyioCommsSdkFlutterPlugin.filePresentation.getCurrent(); - if (!mounted) return; - showDialog(context, 'Success', filePresentation.toJson().toString()); - } catch (error) { - if (!mounted) return; - showDialog(context, 'Error: ', error.toString()); - } - } - - Future setPage() async { - try { - if (_fileConverted == null) { - if (!mounted) return; - showDialog(context, 'You must start presentation first!', ''); - return; - } - - await _dolbyioCommsSdkFlutterPlugin.filePresentation.setPage(0); - if (!mounted) return; - showDialog(context, 'Success', 'OK'); - } catch (error) { - if (!mounted) return; - showDialog(context, 'Error: ', error.toString()); - } - } - - Future getImage() async { - try { - if (_fileConverted == null) { - if (!mounted) return; - showDialog(context, 'You must start presentation first!', ''); - return; - } - - var value = - await _dolbyioCommsSdkFlutterPlugin.filePresentation.getImage(0); - if (!mounted) return; - showDialog(context, 'Success', value); - } catch (error) { - if (!mounted) return; - showDialog(context, 'Error: ', error.toString()); - } - } - - Future getThumbnail() async { - try { - if (_fileConverted == null) { - if (!mounted) return; - showDialog(context, 'You must start presentation first!', ''); - return; - } - - var value = - await _dolbyioCommsSdkFlutterPlugin.filePresentation.getThumbnail(0); - if (!mounted) return; - showDialog(context, 'Success', value); - } catch (error) { - if (!mounted) return; - showDialog(context, 'Error: ', error.toString()); - } - } -} diff --git a/test_app/lib/screens/test_buttons/test_buttons.dart b/test_app/lib/screens/test_buttons/test_buttons.dart index c997a6f7..5405917f 100644 --- a/test_app/lib/screens/test_buttons/test_buttons.dart +++ b/test_app/lib/screens/test_buttons/test_buttons.dart @@ -3,7 +3,6 @@ import 'package:dolbyio_comms_sdk_flutter_example/screens/test_buttons/video_ser import 'package:flutter/material.dart'; import 'command_service_test_buttons.dart'; import 'conference_service_test_buttons.dart'; -import 'file_presentation_service_test_buttons.dart'; import 'media_device_service_test_buttons.dart'; import 'notification_service_test_buttons.dart'; import 'recording_service_test_buttons.dart'; @@ -44,10 +43,6 @@ class TestButtons extends StatelessWidget { SizedBox(height: 10), CommandServiceTestButtons(), SizedBox(height: 10), - Text("File presentation service"), - SizedBox(height: 10), - FilePresentationServiceTestButtons(), - SizedBox(height: 10), Text("Video presentation service"), SizedBox(height: 10), VideoPresentationServiceTestButtons(), diff --git a/test_app/lib/state_management/models/conference_model.dart b/test_app/lib/state_management/models/conference_model.dart index d37a3802..0a704811 100644 --- a/test_app/lib/state_management/models/conference_model.dart +++ b/test_app/lib/state_management/models/conference_model.dart @@ -7,12 +7,16 @@ class ConferenceModel extends ChangeNotifier { String _username = ""; String? _externalId = ""; bool _isReplayConference = false; + String _imageSource = ''; + int _amountOfPagesInDocument = 0; Conference get conference => _conference; Conference get replayedConference => _replayedConference; String get username => _username; String? get externalId => _externalId; bool get isReplayConference => _isReplayConference; + String get imageSource => _imageSource; + int get amountOfPagesInDocument => _amountOfPagesInDocument; set conference(Conference value) { _conference = value; @@ -39,8 +43,18 @@ class ConferenceModel extends ChangeNotifier { notifyListeners(); } + set imageSource(String value) { + _imageSource = value; + notifyListeners(); + } + + set amountOfPagesInDocument(int value) { + _amountOfPagesInDocument = value; + notifyListeners(); + } + void updateConference() async { final dolbyioCommsSdkFlutterPlugin = DolbyioCommsSdk.instance; _conference = await dolbyioCommsSdkFlutterPlugin.conference.current(); } -} \ No newline at end of file +} diff --git a/test_app/lib/widgets/file_container.dart b/test_app/lib/widgets/file_container.dart new file mode 100644 index 00000000..43f0bfee --- /dev/null +++ b/test_app/lib/widgets/file_container.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../state_management/models/conference_model.dart'; + +class FileContainer extends StatefulWidget { + const FileContainer({Key? key}) : super(key: key); + + @override + State createState() => _FileContainerState(); +} + +class _FileContainerState extends State { + @override + Widget build(BuildContext context) { + String imageSource = Provider.of(context).imageSource; + + return Container( + width: 200, + decoration: BoxDecoration( + border: Border.all(color: Colors.black), + borderRadius: const BorderRadius.all( + Radius.circular(5.0), + ), + ), + child: Image.network(imageSource), + ); + } +} diff --git a/test_app/lib/widgets/file_presentation_ui.dart b/test_app/lib/widgets/file_presentation_ui.dart new file mode 100644 index 00000000..e3f70dc0 --- /dev/null +++ b/test_app/lib/widgets/file_presentation_ui.dart @@ -0,0 +1,167 @@ +import 'package:dolbyio_comms_sdk_flutter/dolbyio_comms_sdk_flutter.dart'; +import 'package:dolbyio_comms_sdk_flutter_example/state_management/models/conference_model.dart'; +import 'package:dolbyio_comms_sdk_flutter_example/widgets/file_container.dart'; +import 'package:dolbyio_comms_sdk_flutter_example/widgets/secondary_button.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'dialogs.dart'; + +class FilePresentationUI extends StatefulWidget { + const FilePresentationUI({Key? key}) : super(key: key); + + @override + State createState() => _FilePresentationUIState(); +} + +class _FilePresentationUIState extends State { + final _dolbyioCommsSdkFlutterPlugin = DolbyioCommsSdk.instance; + int pageNumber = 0; + + @override + Widget build(BuildContext context) { + int amountOfPagesInDocument = + Provider.of(context, listen: false) + .amountOfPagesInDocument; + + return Center( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.4, + child: SingleChildScrollView( + child: Column( + children: [ + Text("Page: $pageNumber/$amountOfPagesInDocument"), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + onPressed: () async { + if (pageNumber > 0) { + setState(() => pageNumber--); + await _dolbyioCommsSdkFlutterPlugin.filePresentation + .setPage(pageNumber); + + String imageSource = + await _dolbyioCommsSdkFlutterPlugin + .filePresentation + .getImage(pageNumber); + setState(() { + Provider.of(context, listen: false) + .imageSource = imageSource; + }); + } + }, + icon: const Icon(Icons.arrow_circle_left_sharp), + color: pageNumber > 0 ? Colors.deepPurple : Colors.grey), + const FileContainer(), + IconButton( + onPressed: () async { + if (pageNumber < amountOfPagesInDocument) { + setState(() => pageNumber++); + await _dolbyioCommsSdkFlutterPlugin.filePresentation + .setPage(pageNumber); + + String imageSource = + await _dolbyioCommsSdkFlutterPlugin + .filePresentation + .getImage(pageNumber); + setState(() { + Provider.of(context, listen: false) + .imageSource = imageSource; + }); + } + }, + icon: const Icon(Icons.arrow_circle_right_sharp), + color: pageNumber < amountOfPagesInDocument + ? Colors.deepPurple + : Colors.grey), + ], + ), + const SizedBox(height: 8), + Wrap( + runSpacing: 4, + spacing: 8, + alignment: WrapAlignment.center, + children: [ + SecondaryButton( + text: 'getCurrent', + onPressed: () => getCurrent(), + fillWidth: false), + SecondaryButton( + text: 'getImage', + onPressed: () => getImage(), + fillWidth: false), + SecondaryButton( + text: 'getThumbnail', + onPressed: () => getThumbnail(), + fillWidth: false), + SecondaryButton( + text: 'stopFilePresentation', + onPressed: () => stopFilePresentation(), + color: Colors.red), + ], + ), + ], + ), + ), + ), + ); + } + + Future showDialog( + BuildContext context, String title, String text) async { + await ViewDialogs.dialog( + context: context, + title: title, + body: text, + ); + } + + Future getCurrent() async { + try { + var filePresentation = + await _dolbyioCommsSdkFlutterPlugin.filePresentation.getCurrent(); + if (!mounted) return; + showDialog(context, 'Success', filePresentation.toJson().toString()); + } catch (error) { + if (!mounted) return; + showDialog(context, 'Error: ', error.toString()); + } + } + + Future getImage() async { + try { + var value = await _dolbyioCommsSdkFlutterPlugin.filePresentation + .getImage(pageNumber); + if (!mounted) return; + showDialog(context, 'Success', value); + } catch (error) { + if (!mounted) return; + showDialog(context, 'Error: ', error.toString()); + } + } + + Future getThumbnail() async { + try { + var value = await _dolbyioCommsSdkFlutterPlugin.filePresentation + .getThumbnail(pageNumber); + if (!mounted) return; + showDialog(context, 'Success', value); + } catch (error) { + if (!mounted) return; + showDialog(context, 'Error: ', error.toString()); + } + } + + Future stopFilePresentation() async { + try { + await _dolbyioCommsSdkFlutterPlugin.filePresentation.stop(); + if (!mounted) return; + showDialog(context, 'Success', 'ok'); + } catch (error) { + if (!mounted) return; + showDialog(context, 'Error: ', error.toString()); + } + } +}