diff --git a/.pubignore b/.pubignore index facedd980..cafadcf4f 100644 --- a/.pubignore +++ b/.pubignore @@ -20,3 +20,8 @@ example/ios/Runner/GoogleService-Info.plist *.p8 release-apps.sh example/lib/firebase_options.dart + +mobx-example-app +example_riverpod +demo_app_with_100ms_and_bloc +demo_with_getx_and_100ms \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d197a048..c932242f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.7.3 - 2022-06-23 +- Added support for iOS Screenshare +- Added `HMSHLSRecordingConfig` to perform recording while HLS Streaming +- Updated error callback in `HMSUpdateListener` to `onHMSError` +- Updated to Native Android SDK 2.4.2 & Native iOS SDK 0.3.1 + ## 0.7.2 - 2022-06-02 - Segregated RTC Stats update notifications from `HMSUpdateListener` into `HMSStatsListener` diff --git a/README.md b/README.md index 95d4d4c52..5971058f9 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ abstract class HMSUpdateListener { /// This will be called when there is an error in the system /// and SDK has already retried to fix the error /// [error]: the error that occurred - void onError({required HMSException error}); + void onHMSError({required HMSException error}); /// This is called when there is a new broadcast message from any other peer in the room /// @@ -221,25 +221,26 @@ abstract class HMSUpdateListener { HMSPeerUpdate HMSPeerUpdate.peerJoined: A new peer joins the room HMSPeerUpdate.peerLeft: An existing peer leaves the room - HMSPeerUpdate.roleUpdated - HMSPeerUpdate.metadataChanged - HMSPeerUpdate.nameChanged + HMSPeerUpdate.roleUpdated: When peer role is changed + HMSPeerUpdate.metadataChanged: When peer metadata changed + HMSPeerUpdate.nameChanged: When peer name change HMSTrackUpdate HMSTrackUpdate.trackAdded: A new track is added by a remote peer HMSTrackUpdate.trackRemoved: An existing track is removed from a remote peer HMSTrackUpdate.trackMuted: An existing track of a remote peer is muted HMSTrackUpdate.trackUnMuted: An existing track of a remote peer is unmuted - HMSTrackUpdate.trackDegraded - HMSTrackUpdate.trackRestored + HMSTrackUpdate.trackDegraded: When track is degraded + HMSTrackUpdate.trackRestored: When track is restored HMSRoomUpdate - HMSRoomUpdate.roomMuted - HMSRoomUpdate.roomUnmuted - HMSRoomUpdate.serverRecordingStateUpdated - HMSRoomUpdate.browserRecordingStateUpdated - HMSRoomUpdate.rtmpStreamingStateUpdated - HMSRoomUpdate.hlsStreamingStateUpdated + HMSRoomUpdate.roomMuted: When room is muted + HMSRoomUpdate.roomUnmuted: When room is unmuted + HMSRoomUpdate.serverRecordingStateUpdated: When server recording state is updated + HMSRoomUpdate.browserRecordingStateUpdated: When browser recording state is changed + HMSRoomUpdate.rtmpStreamingStateUpdated: When RTMP is started or stopped + HMSRoomUpdate.hlsStreamingStateUpdated: When HLS is started or stopped + HMSRoomUpdate.hlsRecordingStateUpdated: When hls recording state is updated ``` ## 🛤 How to know the type and source of Track? @@ -372,7 +373,7 @@ void onMessage({required HMSMessage message}){ abstract class HMSPreviewListener { //you will get all error updates here - void onError({required HMSException error}); + void onHMSError({required HMSException error}); //here you will get a room instance where you are going to join and your own local tracks to render the video by checking the type of trackKind and then using the //above mentioned VideoView widget diff --git a/android/build.gradle b/android/build.gradle index 44702cdcf..8073f3cc9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 32 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -37,7 +37,7 @@ android { } dependencies { - implementation 'com.github.100mslive.android-sdk:lib:2.3.9' + implementation 'com.github.100mslive.android-sdk:lib:2.4.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0' implementation "org.jetbrains.kotlin:kotlin-script-runtime:1.5.0" diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomExtension.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomExtension.kt index c31e7fb50..327c6396c 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomExtension.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRoomExtension.kt @@ -24,6 +24,7 @@ class HMSRoomExtension { hashMap["browser_recording_state"] = HMSStreamingState.toDictionary(room.browserRecordingState) hashMap["server_recording_state"] = HMSStreamingState.toDictionary(room.serverRecordingState) hashMap["hls_streaming_state"] = HMSStreamingState.toDictionary(room.hlsStreamingState) + hashMap["hls_recording_state"] = HMSStreamingState.toDictionary(room.hlsRecordingState) hashMap["peer_count"] = room.peerCount hashMap["started_at"] = room.startedAt?:-1 hashMap["session_id"] = room.sessionId @@ -40,6 +41,7 @@ class HMSRoomExtension { HMSRoomUpdate.RTMP_STREAMING_STATE_UPDATED-> "rtmp_streaming_state_updated" HMSRoomUpdate.HLS_STREAMING_STATE_UPDATED-> "hls_streaming_state_updated" HMSRoomUpdate.BROWSER_RECORDING_STATE_UPDATED-> "browser_recording_state_updated" + HMSRoomUpdate.HLS_RECORDING_STATE_UPDATED-> "hls_recording_state_updated" HMSRoomUpdate.ROOM_NAME_UPDATED->"room_name_updated" HMSRoomUpdate.ROOM_PEER_COUNT_UPDATED->"room_peer_count_updated" else-> "defaultUpdate" diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt index e3664cde5..aa4a0d9f9 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HMSRtmpStreamingState.kt @@ -1,10 +1,8 @@ import live.hms.hmssdk_flutter.HMSExceptionExtension import live.hms.hmssdk_flutter.HMSHLSVariantExtension -import live.hms.video.sdk.models.HMSBrowserRecordingState -import live.hms.video.sdk.models.HMSHLSStreamingState -import live.hms.video.sdk.models.HMSRtmpStreamingState -import live.hms.video.sdk.models.HMSServerRecordingState +import live.hms.video.sdk.models.* +import java.text.SimpleDateFormat class HMSStreamingState { companion object{ @@ -13,6 +11,8 @@ class HMSStreamingState { if(hmsRtmpStreamingState == null)return null map["running"] = hmsRtmpStreamingState.running map["error"] = HMSExceptionExtension.toDictionary(hmsRtmpStreamingState.error) + if(hmsRtmpStreamingState.running) + map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsRtmpStreamingState.startedAt).toString() return map } @@ -21,6 +21,8 @@ class HMSStreamingState { if(hmsServerRecordingState == null)return null map["running"] = hmsServerRecordingState.running map["error"] = HMSExceptionExtension.toDictionary(hmsServerRecordingState.error) + if(hmsServerRecordingState.running) + map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsServerRecordingState.startedAt).toString() return map } @@ -29,6 +31,8 @@ class HMSStreamingState { if(hmsBrowserRecordingState == null)return null map["running"] = hmsBrowserRecordingState.running map["error"] = HMSExceptionExtension.toDictionary(hmsBrowserRecordingState.error) + if(hmsBrowserRecordingState.running) + map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsBrowserRecordingState.startedAt).toString() return map } @@ -44,6 +48,15 @@ class HMSStreamingState { return map } + fun toDictionary(hmsHlsRecordingState: HmsHlsRecordingState?):HashMap?{ + val map = HashMap() + if(hmsHlsRecordingState == null)return null + map["running"] = hmsHlsRecordingState.running + if(hmsHlsRecordingState.running == true) + map["started_at"] = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(hmsHlsRecordingState.startedAt).toString() + return map + } + } } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt index 2f7a755bf..cb88d1bb7 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/HmssdkFlutterPlugin.kt @@ -58,7 +58,6 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, lateinit var hmssdk: HMSSDK private lateinit var hmsVideoFactory: HMSVideoViewFactory private var requestChange: HMSRoleChangeRequest? = null - private var isStatsActive: Boolean = false companion object { var hmssdkFlutterPlugin: HmssdkFlutterPlugin? = null @@ -275,20 +274,14 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } } - private var isRTCStatsListenerInit = false - private fun statsListenerAction(call: MethodCall, result: Result){ when (call.method) { "start_stats_listener" -> { - if (!isRTCStatsListenerInit) { - hmssdk.addRtcStatsObserver(this.hmsStatsListener) - isRTCStatsListenerInit = true - } - isStatsActive = true + hmssdk.addRtcStatsObserver(this.hmsStatsListener) } "remove_stats_listener" -> { - isStatsActive = false + hmssdk.removeRtcStatsObserver() } else -> { @@ -1012,19 +1005,17 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, hmsTrack: HMSTrack?, hmsPeer: HMSPeer? ) { - if(isStatsActive) { - val args = HashMap() - args["event_name"] = "on_remote_video_stats" - args["data"] = HMSRtcStatsExtension.toDictionary( - hmsRemoteVideoStats = videoStats, - peer = hmsPeer, - track = hmsTrack - ) - - if (args["data"] != null) - CoroutineScope(Dispatchers.Main).launch { - rtcSink?.success(args) - } + val args = HashMap() + args["event_name"] = "on_remote_video_stats" + args["data"] = HMSRtcStatsExtension.toDictionary( + hmsRemoteVideoStats = videoStats, + peer = hmsPeer, + track = hmsTrack + ) + + if (args["data"] != null) + CoroutineScope(Dispatchers.Main).launch { + rtcSink?.success(args) } } @@ -1033,20 +1024,17 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, hmsTrack: HMSTrack?, hmsPeer: HMSPeer? ) { - if(isStatsActive) { - val args = HashMap() - args["event_name"] = "on_remote_audio_stats" - args["data"] = HMSRtcStatsExtension.toDictionary( - hmsRemoteAudioStats = audioStats, - peer = hmsPeer, - track = hmsTrack - ) - - - if (args["data"] != null) - CoroutineScope(Dispatchers.Main).launch { - rtcSink?.success(args) - } + val args = HashMap() + args["event_name"] = "on_remote_audio_stats" + args["data"] = HMSRtcStatsExtension.toDictionary( + hmsRemoteAudioStats = audioStats, + peer = hmsPeer, + track = hmsTrack + ) + + if (args["data"] != null) + CoroutineScope(Dispatchers.Main).launch { + rtcSink?.success(args) } } @@ -1055,20 +1043,17 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, hmsTrack: HMSTrack?, hmsPeer: HMSPeer? ) { - if(isStatsActive) { - val args = HashMap() - args["event_name"] = "on_local_video_stats" - args["data"] = HMSRtcStatsExtension.toDictionary( - hmsLocalVideoStats = videoStats, - peer = getLocalPeer(), - track = hmsTrack - ) - + val args = HashMap() + args["event_name"] = "on_local_video_stats" + args["data"] = HMSRtcStatsExtension.toDictionary( + hmsLocalVideoStats = videoStats, + peer = getLocalPeer(), + track = hmsTrack + ) - if (args["data"] != null) - CoroutineScope(Dispatchers.Main).launch { - rtcSink?.success(args) - } + if (args["data"] != null) + CoroutineScope(Dispatchers.Main).launch { + rtcSink?.success(args) } } @@ -1077,41 +1062,38 @@ class HmssdkFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, hmsTrack: HMSTrack?, hmsPeer: HMSPeer? ) { - if(isStatsActive) { - val args = HashMap() - args["event_name"] = "on_local_audio_stats" - args["data"] = HMSRtcStatsExtension.toDictionary( - hmsLocalAudioStats = audioStats, - peer = getLocalPeer(), - track = hmsTrack - ) + val args = HashMap() + args["event_name"] = "on_local_audio_stats" + args["data"] = HMSRtcStatsExtension.toDictionary( + hmsLocalAudioStats = audioStats, + peer = getLocalPeer(), + track = hmsTrack + ) - if (args["data"] != null) - CoroutineScope(Dispatchers.Main).launch { - rtcSink?.success(args) - } + if (args["data"] != null) + CoroutineScope(Dispatchers.Main).launch { + rtcSink?.success(args) } } override fun onRTCStats(rtcStats: HMSRTCStatsReport) { - if(isStatsActive) { - val args = HashMap() - args["event_name"] = "on_rtc_stats" - val dict = HashMap() - dict["bytes_sent"] = rtcStats.combined.bytesSent - dict["bytes_received"] = rtcStats.combined.bitrateReceived - dict["bitrate_sent"] = rtcStats.combined.bitrateSent - dict["packets_received"] = rtcStats.combined.packetsReceived - dict["packets_lost"] = rtcStats.combined.packetsLost - dict["bitrate_received"] = rtcStats.combined.bitrateReceived - dict["round_trip_time"] = rtcStats.combined.roundTripTime - - args["data"] = dict - if (args["data"] != null) - CoroutineScope(Dispatchers.Main).launch { - rtcSink?.success(args) - } + val args = HashMap() + args["event_name"] = "on_rtc_stats" + val dict = HashMap() + dict["bytes_sent"] = rtcStats.combined.bytesSent + dict["bytes_received"] = rtcStats.combined.bitrateReceived + dict["bitrate_sent"] = rtcStats.combined.bitrateSent + dict["packets_received"] = rtcStats.combined.packetsReceived + dict["packets_lost"] = rtcStats.combined.packetsLost + dict["bitrate_received"] = rtcStats.combined.bitrateReceived + dict["round_trip_time"] = rtcStats.combined.roundTripTime + + args["data"] = dict + if (args["data"] != null) + CoroutineScope(Dispatchers.Main).launch { + rtcSink?.success(args) } + } } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSHLSAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSHLSAction.kt index a65d3520a..a70feda11 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSHLSAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSHLSAction.kt @@ -6,6 +6,7 @@ import live.hms.video.sdk.models.HMSHLSConfig import live.hms.video.sdk.models.HMSHLSMeetingURLVariant import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel.Result +import live.hms.video.sdk.models.HMSHlsRecordingConfig class HMSHLSAction { @@ -28,10 +29,11 @@ class HMSHLSAction { private fun hlsStreaming(call: MethodCall, result: Result,hmssdk:HMSSDK) { val meetingUrlVariantsList = call.argument>>("meeting_url_variants") - val meetingUrlVariant1 : ArrayList = ArrayList() + val recordingConfig = call.argument>("recording_config") ?: null + val meetingUrlVariant : ArrayList = ArrayList() meetingUrlVariantsList?.forEach { - meetingUrlVariant1.add( + meetingUrlVariant.add( HMSHLSMeetingURLVariant( meetingUrl = it["meeting_url"]!!, metadata = it["meta_data"]!! @@ -39,8 +41,15 @@ class HMSHLSAction { ) } - val hlsConfig = HMSHLSConfig(meetingUrlVariant1) - + val hlsConfig = if(recordingConfig!=null) { + val hmsHLSRecordingConfig = HMSHlsRecordingConfig( + singleFilePerLayer = recordingConfig?.get("single_file_per_layer")!!, + videoOnDemand = recordingConfig?.get("video_on_demand")!! + ) + HMSHLSConfig(meetingUrlVariant, hmsHLSRecordingConfig) + }else{ + HMSHLSConfig(meetingUrlVariant) + } hmssdk.startHLSStreaming(config = hlsConfig, hmsActionResultListener = HMSCommonAction.getActionListener(result)) } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRecordingAction.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRecordingAction.kt index 8c6db9efc..2ba564493 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRecordingAction.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/methods/HMSRecordingAction.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import live.hms.video.error.HMSException +import live.hms.video.media.settings.HMSRtmpVideoResolution import live.hms.video.sdk.HMSActionResultListener import live.hms.video.sdk.models.HMSRecordingConfig @@ -29,9 +30,19 @@ class HMSRecordingAction { val meetingUrl = call.argument("meeting_url") val toRecord = call.argument("to_record") val listOfRtmpUrls: List = call.argument>("rtmp_urls") ?: listOf() + val resolutionMap = call.argument>("resolution") + val hmsRecordingConfig = if(resolutionMap!=null) { + val resolution = HMSRtmpVideoResolution( + width = resolutionMap["width"]!!, + height = resolutionMap["height"]!! + ) + HMSRecordingConfig(meetingUrl!!, listOfRtmpUrls, toRecord!!,resolution) + }else{ + HMSRecordingConfig(meetingUrl!!, listOfRtmpUrls, toRecord!!) + } hmssdk.startRtmpOrRecording( - HMSRecordingConfig(meetingUrl!!, listOfRtmpUrls, toRecord!!), - hmsActionResultListener = HMSCommonAction.getActionListener(result) + hmsRecordingConfig, + hmsActionResultListener = HMSCommonAction.getActionListener(result) ) } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt index d9fb03150..a355865e0 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoView.kt @@ -13,16 +13,14 @@ import org.webrtc.SurfaceViewRenderer class HMSVideoView( - context: Context, - setMirror: Boolean, - scaleType: Int? = RendererCommon.ScalingType.SCALE_ASPECT_FIT.ordinal, - track:HMSVideoTrack + context: Context, + setMirror: Boolean, + scaleType: Int? = RendererCommon.ScalingType.SCALE_ASPECT_FIT.ordinal, + private val track:HMSVideoTrack ) : FrameLayout(context, null) { private val surfaceViewRenderer: SurfaceViewRenderer - private val myTrack: HMSVideoTrack = track - init { val inflater = getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater @@ -41,18 +39,14 @@ class HMSVideoView( override fun onAttachedToWindow() { super.onAttachedToWindow() surfaceViewRenderer.init(SharedEglContext.context, null) - myTrack.addSink(surfaceViewRenderer) + track.addSink(surfaceViewRenderer) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - myTrack.removeSink(surfaceViewRenderer) + track.removeSink(surfaceViewRenderer) surfaceViewRenderer.release() } - - override fun onWindowVisibilityChanged(visibility: Int) { - super.onWindowVisibilityChanged(visibility) - } } diff --git a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt index cf3b2bc14..68eeb34db 100644 --- a/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt +++ b/android/src/main/kotlin/live/hms/hmssdk_flutter/views/HMSVideoViewFactory.kt @@ -13,18 +13,15 @@ import live.hms.video.utils.HmsUtilities -class HMSVideoViewWidget(context: Context, id: Int, creationParams: Map?, private val track: HMSVideoTrack, private val setMirror:Boolean, +class HMSVideoViewWidget(private val context: Context, id: Int, creationParams: Map?, private val track: HMSVideoTrack, private val setMirror:Boolean, private val scaleType : Int?,private val matchParent: Boolean? = true ) : PlatformView { - private val viewContext: Context = context - private val myTrack: HMSVideoTrack = track - private var hmsVideoView: HMSVideoView? = null override fun getView(): View { if (hmsVideoView == null) { - hmsVideoView = HMSVideoView(viewContext, setMirror, scaleType, myTrack) + hmsVideoView = HMSVideoView(context, setMirror, scaleType, track) } return hmsVideoView!! } @@ -57,8 +54,7 @@ class HMSVideoViewWidget(context: Context, id: Int, creationParams: Map? @@ -73,6 +69,6 @@ class HMSVideoViewFactory(private val plugin: HmssdkFlutterPlugin) : val track = HmsUtilities.getVideoTrack(trackId!!, room!!) - return HMSVideoViewWidget(context, viewId, creationParams, track!!, setMirror!!, scaleType, matchParent) + return HMSVideoViewWidget(requireNotNull(context), viewId, creationParams, track!!, setMirror!!, scaleType, matchParent) } } \ No newline at end of file diff --git a/demo_app_with_100ms_and_bloc/.gitignore b/demo_app_with_100ms_and_bloc/.gitignore new file mode 100644 index 000000000..0fa6b675c --- /dev/null +++ b/demo_app_with_100ms_and_bloc/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/demo_app_with_100ms_and_bloc/.metadata b/demo_app_with_100ms_and_bloc/.metadata new file mode 100644 index 000000000..5a023280a --- /dev/null +++ b/demo_app_with_100ms_and_bloc/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 7e9793dee1b85a243edd0e06cb1658e98b077561 + channel: stable + +project_type: app diff --git a/demo_app_with_100ms_and_bloc/README.md b/demo_app_with_100ms_and_bloc/README.md new file mode 100644 index 000000000..3805a1b43 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/README.md @@ -0,0 +1,201 @@ +# demo_with_100ms_and_bloc + +A demo app using Bloc and 100ms. + +## Getting Started + +A few resources to get you started if this is your first Flutter project: + +- [100ms flutter documentation](https://www.100ms.live/docs/flutter/v2/foundation/basics) +- [bloc](https://bloclibrary.dev/#/) + +## Now we will see steps to use bloc and 100ms + In this example we used both Cubit and Bloc for better understanding on using bloc package + + ### Preview + +1. for preview create Cubit and initialzing HMSSDK object. + + ```dart + class PreviewCubit extends Cubit{} + ``` + + +2. create PreviewState.that has properties like micOn,cameraOn and localVideoTracks + + + ```dart + class PreviewState extends Equatable { + const PreviewState({ + this.isMicOff = true, + this.isVideoOff = true, + this.tracks = const [], + }); + + final bool isMicOff; + final bool isVideoOff; + final List tracks; + + PreviewState copyWith( + {bool? isMicOff, bool? isVideoOff, List? tracks}) { + return PreviewState( + isMicOff: isMicOff ?? this.isMicOff, + isVideoOff: isVideoOff ?? this.isVideoOff, + tracks: tracks ?? this.tracks); + } + + @override + List get props => [isMicOff, isVideoOff, tracks]; +} + ``` + +3. props are used for state changes. + +4. After this create PreviewObserver that will act as PreviewEvent class + ```dart + class PreviewObserver implements HMSPreviewListener{} + ``` + +5. as you can see we have to refernce of PreviewCubit we can give it the localTracks on onPreview() callback + ```dart + PreviewCubit previewCubit; + @override + void onPreview({required HMSRoom room, required List localTracks}) { + List videoTracks = []; + for (var track in localTracks) { + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + videoTracks.add(track as HMSVideoTrack); + } + } + this.localTracks.clear(); + this.localTracks.addAll(videoTracks); + previewCubit.updateTracks(this.localTracks); + } + ``` + + ### Room + For room we will be using Bloc for better understanding and as room interactions are litte complex than preview. + + 1. Create RoomOverviewEvent which has all different events associated with room. + + ```dart + abstract class RoomOverviewEvent extends Equatable { + const RoomOverviewEvent(); + @override + List get props => []; + } + ``` + 2. Create RoomOverviewState which consist of all state variables on which our Ui sis dependent. + + + ```dart + class RoomOverviewState extends Equatable { + final RoomOverviewStatus status; + final List peerTrackNodes; + final bool isVideoMute; + final bool isAudioMute; + final bool leaveMeeting; + + + @override + List get props => [status,peerTrackNodes,isAudioMute,isVideoMute,leaveMeeting]; + } + ``` + + 3. In RoomOverviewState you can see List of PeerTrackNode which take cares of video tracks in room. + + + 4. On Different events we will have different states for listening to states we have BlocBuilder or BlocConsumer. + + + ```dart + + BlocConsumer( + listener: (ctx, state) { + if (state.leaveMeeting) { + Navigator.of(context).pushReplacement(HomePage.route()); + } + }, + builder: (ctx, state) { + return ListView.builder( + itemCount: state.peerTrackNodes.length, + itemBuilder: (ctx, index) { + return Card( + key: Key( + state.peerTrackNodes[index].peer!.peerId.toString()), + child: SizedBox(height: 250.0, child: VideoWidget(index))); + }, + ); + }, + ) + ``` + + +## How to use PeerTrackNode model class + +This model class is used for some changes in a Peer.So that getx can only rebuild that particular changes. + +```dart + +class PeerTrackNode extends Equatable { + + + final HMSVideoTrack? hmsVideoTrack; + final bool? isMute; + final HMSPeer? peer; + final bool isOffScreen; + + const PeerTrackNode( + this.hmsVideoTrack, this.isMute, this.peer, this.isOffScreen); + + + @override + List get props => [hmsVideoTrack, isMute, peer, isOffScreen]; +} +``` + +We need to first listen to changes in List of PeerTrackNode. +For that we need to do this. +```dart + final _peerNodeStreamController = + BehaviorSubject>.seeded(const []); + + Stream> getTracks() => + _peerNodeStreamController.asBroadcastStream(); +``` + +Now in On Subscription Event you listen to tracks change. + ```dart + Future _onSubscription(RoomOverviewSubscriptionRequested event, + Emitter emit) async { + await emit.forEach>( + roomObserver.getTracks(), + onData: (tracks) { + return state.copyWith( + status: RoomOverviewStatus.success, peerTrackNodes: tracks); + }, + onError: (_, __) => state.copyWith( + status: RoomOverviewStatus.failure, + ), + ); + } + ``` + +So now, for changing some properties in PeerTrackNode you need to do something like this. + +```dart + Future addPeer(HMSVideoTrack hmsVideoTrack, HMSPeer peer) async{ + final tracks = [..._peerNodeStreamController.value]; + final trackIndex = tracks.indexWhere((t) => t.peer?.peerId == peer.peerId); + if (trackIndex >= 0) { + tracks[trackIndex] = + PeerTrackNode(hmsVideoTrack, hmsVideoTrack.isMute, peer, false); + } else { + tracks + .add(PeerTrackNode(hmsVideoTrack, hmsVideoTrack.isMute, peer, false)); + } + + _peerNodeStreamController.add(tracks); + } +``` +This is how you can rebuild widgets on change. diff --git a/demo_app_with_100ms_and_bloc/analysis_options.yaml b/demo_app_with_100ms_and_bloc/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/demo_app_with_100ms_and_bloc/android/.gitignore b/demo_app_with_100ms_and_bloc/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/demo_app_with_100ms_and_bloc/android/app/build.gradle b/demo_app_with_100ms_and_bloc/android/app/build.gradle new file mode 100644 index 000000000..0a017cb5d --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 32 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.demo_app_with_100ms_and_bloc" + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/demo_app_with_100ms_and_bloc/android/app/proguard-rules.pro b/demo_app_with_100ms_and_bloc/android/app/proguard-rules.pro new file mode 100644 index 000000000..5f6821f1a --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/proguard-rules.pro @@ -0,0 +1,32 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android +-keepattributes SourceFile,LineNumberTable # Keep file names and line numbers. +#-keep public class * extends java.lang.Exception # Optional: Keep custom exceptions. + +# https://developer.android.com/guide/navigation/navigation-pass-data#proguard_considerations + + +# Video libs +-keep class org.webrtc.** { *; } +-keep class live.hms.video.** { *; } \ No newline at end of file diff --git a/demo_app_with_100ms_and_bloc/android/app/src/debug/AndroidManifest.xml b/demo_app_with_100ms_and_bloc/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..7de2e1f88 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/AndroidManifest.xml b/demo_app_with_100ms_and_bloc/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..4e9bb6fab --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/kotlin/com/example/demo_app_with_100ms_and_bloc/MainActivity.kt b/demo_app_with_100ms_and_bloc/android/app/src/main/kotlin/com/example/demo_app_with_100ms_and_bloc/MainActivity.kt new file mode 100644 index 000000000..b76cedf0a --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/src/main/kotlin/com/example/demo_app_with_100ms_and_bloc/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.demo_app_with_100ms_and_bloc + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/drawable-v21/launch_background.xml b/demo_app_with_100ms_and_bloc/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/drawable/launch_background.xml b/demo_app_with_100ms_and_bloc/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/demo_app_with_100ms_and_bloc/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/values-night/styles.xml b/demo_app_with_100ms_and_bloc/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..3db14bb53 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/demo_app_with_100ms_and_bloc/android/app/src/main/res/values/styles.xml b/demo_app_with_100ms_and_bloc/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..d460d1e92 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/demo_app_with_100ms_and_bloc/android/app/src/profile/AndroidManifest.xml b/demo_app_with_100ms_and_bloc/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..7de2e1f88 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/demo_app_with_100ms_and_bloc/android/build.gradle b/demo_app_with_100ms_and_bloc/android/build.gradle new file mode 100644 index 000000000..4256f9173 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/demo_app_with_100ms_and_bloc/android/gradle.properties b/demo_app_with_100ms_and_bloc/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/demo_app_with_100ms_and_bloc/android/gradle/wrapper/gradle-wrapper.properties b/demo_app_with_100ms_and_bloc/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..bc6a58afd --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/demo_app_with_100ms_and_bloc/android/settings.gradle b/demo_app_with_100ms_and_bloc/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/demo_app_with_100ms_and_bloc/assets/icons/bloc_logo.png b/demo_app_with_100ms_and_bloc/assets/icons/bloc_logo.png new file mode 100644 index 000000000..1267277ba Binary files /dev/null and b/demo_app_with_100ms_and_bloc/assets/icons/bloc_logo.png differ diff --git a/demo_app_with_100ms_and_bloc/assets/icons/hms_icon_1024.png b/demo_app_with_100ms_and_bloc/assets/icons/hms_icon_1024.png new file mode 100644 index 000000000..df281e975 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/assets/icons/hms_icon_1024.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/.gitignore b/demo_app_with_100ms_and_bloc/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/demo_app_with_100ms_and_bloc/ios/Flutter/AppFrameworkInfo.plist b/demo_app_with_100ms_and_bloc/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..8d4492f97 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/demo_app_with_100ms_and_bloc/ios/Flutter/Debug.xcconfig b/demo_app_with_100ms_and_bloc/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/demo_app_with_100ms_and_bloc/ios/Flutter/Release.xcconfig b/demo_app_with_100ms_and_bloc/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.pbxproj b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..f20745679 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoAppWith100msAndBloc; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoAppWith100msAndBloc; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoAppWith100msAndBloc; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..c87d15a33 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/contents.xcworkspacedata b/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/AppDelegate.swift b/demo_app_with_100ms_and_bloc/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Base.lproj/LaunchScreen.storyboard b/demo_app_with_100ms_and_bloc/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Base.lproj/Main.storyboard b/demo_app_with_100ms_and_bloc/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Info.plist b/demo_app_with_100ms_and_bloc/ios/Runner/Info.plist new file mode 100644 index 000000000..a7cc3cbd8 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Demo App With 100ms And Bloc + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + demo_app_with_100ms_and_bloc + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/demo_app_with_100ms_and_bloc/ios/Runner/Runner-Bridging-Header.h b/demo_app_with_100ms_and_bloc/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/demo_app_with_100ms_and_bloc/lib/bloc/preview/preview_cubit.dart b/demo_app_with_100ms_and_bloc/lib/bloc/preview/preview_cubit.dart new file mode 100644 index 000000000..107e6c28b --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/bloc/preview/preview_cubit.dart @@ -0,0 +1,32 @@ +import 'package:bloc/bloc.dart'; +import 'package:demo_app_with_100ms_and_bloc/observers/preview_observer.dart'; +import 'package:equatable/equatable.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +part 'preview_state.dart'; + +class PreviewCubit extends Cubit { + HMSSDK hmsSdk = HMSSDK(); + String name; + String url; + + PreviewCubit(this.name, this.url) + : super(const PreviewState(isMicOff: false, isVideoOff: false)) { + PreviewObserver(this); + } + + void toggleVideo() { + hmsSdk.switchVideo(isOn: !state.isVideoOff); + + emit(state.copyWith(isVideoOff: !state.isVideoOff)); + } + + void toggleAudio() { + hmsSdk.switchAudio(isOn: !state.isMicOff); + emit(state.copyWith(isMicOff: !state.isMicOff)); + } + + void updateTracks(List localTracks) { + emit(state.copyWith(tracks: localTracks)); + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/bloc/preview/preview_state.dart b/demo_app_with_100ms_and_bloc/lib/bloc/preview/preview_state.dart new file mode 100644 index 000000000..dbb28a323 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/bloc/preview/preview_state.dart @@ -0,0 +1,29 @@ +part of 'preview_cubit.dart'; + +class PreviewState extends Equatable { + const PreviewState({ + this.isMicOff = true, + this.isVideoOff = true, + this.tracks = const [], + }); + + final bool isMicOff; + final bool isVideoOff; + final List tracks; + + PreviewState copyWith( + {bool? isMicOff, bool? isVideoOff, List? tracks}) { + return PreviewState( + isMicOff: isMicOff ?? this.isMicOff, + isVideoOff: isVideoOff ?? this.isVideoOff, + tracks: tracks ?? this.tracks); + } + + @override + String toString() { + return '''PreviewState { isMicOff: $isMicOff, isVideoOff: $isVideoOff}'''; + } + + @override + List get props => [isMicOff, isVideoOff, tracks]; +} diff --git a/demo_app_with_100ms_and_bloc/lib/bloc/room/peer_track_node.dart b/demo_app_with_100ms_and_bloc/lib/bloc/room/peer_track_node.dart new file mode 100644 index 000000000..3571417b3 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/bloc/room/peer_track_node.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class PeerTrackNode extends Equatable { + final HMSVideoTrack? hmsVideoTrack; + final bool? isMute; + final HMSPeer? peer; + final bool isOffScreen; + + const PeerTrackNode( + this.hmsVideoTrack, this.isMute, this.peer, this.isOffScreen); + + @override + List get props => [hmsVideoTrack, isMute, peer, isOffScreen]; + + PeerTrackNode copyWith({ + HMSVideoTrack? hmsVideoTrack, + bool? isMute, + HMSPeer? peer, + bool? isOffScreen, + }) { + return PeerTrackNode( + hmsVideoTrack ?? this.hmsVideoTrack, + isMute ?? this.isMute, + peer ?? this.peer, + isOffScreen ?? this.isOffScreen, + ); + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_bloc.dart b/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_bloc.dart new file mode 100644 index 000000000..174e0079c --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_bloc.dart @@ -0,0 +1,87 @@ +import 'package:bloc/bloc.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/peer_track_node.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_event.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_state.dart'; +import 'package:demo_app_with_100ms_and_bloc/observers/room_observer.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class RoomOverviewBloc extends Bloc { + final bool isVideoMute; + final bool isAudioMute; + HMSSDK hmsSdk = HMSSDK(); + String name; + String url; + late RoomObserver roomObserver; + + RoomOverviewBloc(this.isVideoMute, this.isAudioMute, this.name, this.url) + : super(RoomOverviewState( + isAudioMute: isAudioMute, isVideoMute: isVideoMute)) { + roomObserver = RoomObserver(this); + on(_onSubscription); + on(_onLocalAudioToggled); + on(_onLocalVideoToggled); + on(_onJoinSuccess); + on(_onPeerLeave); + on(_onPeerJoin); + on(_leaveRequested); + on(_setOffScreen); + } + + Future _onSubscription(RoomOverviewSubscriptionRequested event, + Emitter emit) async { + await emit.forEach>( + roomObserver.getTracks(), + onData: (tracks) { + return state.copyWith( + status: RoomOverviewStatus.success, peerTrackNodes: tracks); + }, + onError: (_, __) => state.copyWith( + status: RoomOverviewStatus.failure, + ), + ); + } + + Future _onLocalVideoToggled(RoomOverviewLocalPeerVideoToggled event, + Emitter emit) async { + hmsSdk.switchVideo(isOn: !state.isVideoMute); + emit(state.copyWith(isVideoMute: !state.isVideoMute)); + } + + Future _onLocalAudioToggled(RoomOverviewLocalPeerAudioToggled event, + Emitter emit) async { + hmsSdk.switchAudio(isOn: !state.isAudioMute); + emit(state.copyWith(isAudioMute: !state.isAudioMute)); + } + + Future _onJoinSuccess( + RoomOverviewOnJoinSuccess event, Emitter emit) async { + if (state.isAudioMute) { + hmsSdk.switchAudio(isOn: state.isAudioMute); + } + + if (state.isVideoMute) { + hmsSdk.switchVideo(isOn: state.isVideoMute); + } + } + + Future _onPeerLeave( + RoomOverviewOnPeerLeave event, Emitter emit) async { + await roomObserver.deletePeer(event.hmsPeer.peerId); + } + + Future _onPeerJoin( + RoomOverviewOnPeerJoin event, Emitter emit) async { + await roomObserver.addPeer(event.hmsVideoTrack, event.hmsPeer); + } + + Future _leaveRequested( + RoomOverviewLeaveRequested event, Emitter emit) async { + await roomObserver.leaveMeeting(); + emit(state.copyWith(leaveMeeting: true)); + } + + Future _setOffScreen( + RoomOverviewSetOffScreen event, Emitter emit) async { + await roomObserver.setOffScreen(event.index, event.setOffScreen); + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_event.dart b/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_event.dart new file mode 100644 index 000000000..7d80119c0 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_event.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +abstract class RoomOverviewEvent extends Equatable { + const RoomOverviewEvent(); + + @override + List get props => []; +} + +class RoomOverviewSubscriptionRequested extends RoomOverviewEvent { + const RoomOverviewSubscriptionRequested(); +} + +class RoomOverviewLocalPeerVideoToggled extends RoomOverviewEvent { + const RoomOverviewLocalPeerVideoToggled(); +} + +class RoomOverviewLocalPeerAudioToggled extends RoomOverviewEvent { + const RoomOverviewLocalPeerAudioToggled(); +} + +class RoomOverviewLeaveRequested extends RoomOverviewEvent { + const RoomOverviewLeaveRequested(); +} + +class RoomOverviewSetOffScreen extends RoomOverviewEvent { + final int index; + final bool setOffScreen; + const RoomOverviewSetOffScreen(this.setOffScreen, this.index); +} + +class RoomOverviewOnJoinSuccess extends RoomOverviewEvent { + final HMSRoom hmsRoom; + const RoomOverviewOnJoinSuccess(this.hmsRoom); +} + +class RoomOverviewOnPeerLeave extends RoomOverviewEvent { + final HMSPeer hmsPeer; + final HMSVideoTrack hmsVideoTrack; + const RoomOverviewOnPeerLeave(this.hmsVideoTrack, this.hmsPeer); +} + +class RoomOverviewOnPeerJoin extends RoomOverviewEvent { + final HMSPeer hmsPeer; + final HMSVideoTrack hmsVideoTrack; + const RoomOverviewOnPeerJoin(this.hmsVideoTrack, this.hmsPeer); +} diff --git a/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_state.dart b/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_state.dart new file mode 100644 index 000000000..aeb7d0130 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/bloc/room/room_overview_state.dart @@ -0,0 +1,37 @@ +import 'package:demo_app_with_100ms_and_bloc/bloc/room/peer_track_node.dart'; +import 'package:equatable/equatable.dart'; + +enum RoomOverviewStatus { initial, loading, success, failure } + +class RoomOverviewState extends Equatable { + final RoomOverviewStatus status; + final List peerTrackNodes; + final bool isVideoMute; + final bool isAudioMute; + final bool leaveMeeting; + + const RoomOverviewState( + {this.status = RoomOverviewStatus.initial, + this.peerTrackNodes = const [], + this.isVideoMute = false, + this.isAudioMute = false, + this.leaveMeeting = false}); + + @override + List get props => + [status, peerTrackNodes, isAudioMute, isVideoMute, leaveMeeting]; + + RoomOverviewState copyWith( + {RoomOverviewStatus? status, + List? peerTrackNodes, + bool? isVideoMute, + bool? isAudioMute, + bool? leaveMeeting}) { + return RoomOverviewState( + status: status ?? this.status, + peerTrackNodes: peerTrackNodes ?? this.peerTrackNodes, + isVideoMute: isVideoMute ?? this.isVideoMute, + isAudioMute: isAudioMute ?? this.isAudioMute, + leaveMeeting: leaveMeeting ?? this.leaveMeeting); + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/home_page.dart b/demo_app_with_100ms_and_bloc/lib/home_page.dart new file mode 100644 index 000000000..c226347ae --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/home_page.dart @@ -0,0 +1,134 @@ +import 'dart:io'; + +import 'package:demo_app_with_100ms_and_bloc/views/preview_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class HomePage extends StatelessWidget { + static Route route() { + return MaterialPageRoute(builder: (_) => HomePage()); + } + + const HomePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final meetingTextController = TextEditingController( + text: "https://shaik.app.100ms.live/meeting/ajk-stp-ebs"); + final nameTextController = TextEditingController(); + + return SafeArea( + child: Scaffold( + appBar: AppBar( + leading: Image.asset("assets/icons/hms_icon_1024.png"), + title: const Text("100ms and Bloc Demo App"), + actions: [ + Image.asset("assets/icons/bloc_logo.png"), + ], + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 300.0, + child: TextField( + controller: meetingTextController, + autofocus: true, + keyboardType: TextInputType.url, + decoration: InputDecoration( + hintText: 'Enter Room URL', + suffixIcon: IconButton( + onPressed: meetingTextController.clear, + icon: const Icon(Icons.clear), + ), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)))), + ), + ), + const SizedBox( + height: 30.0, + ), + SizedBox( + width: 300.0, + child: TextField( + controller: nameTextController, + autofocus: true, + keyboardType: TextInputType.url, + decoration: InputDecoration( + hintText: 'Enter Name', + suffixIcon: IconButton( + onPressed: nameTextController.clear, + icon: const Icon(Icons.clear), + ), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)))), + ), + ), + const SizedBox( + height: 30.0, + ), + SizedBox( + width: 300.0, + child: ElevatedButton( + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ))), + onPressed: () async { + if (meetingTextController.text.isNotEmpty && + nameTextController.text.isNotEmpty) { + bool res = await getPermissions(); + if (res) { + Navigator.of(context).pushReplacement(Preview.route( + meetingTextController.text, + nameTextController.text, + )); + } + } + }, + child: Container( + padding: const EdgeInsets.all(4.0), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(16))), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(Icons.video_call_outlined, size: 48), + SizedBox( + width: 8, + ), + Text('Join Meeting', + style: TextStyle(height: 1, fontSize: 24)) + ], + ), + ), + ), + ) + ], + )), + ), + ); + } + + Future getPermissions() async { + if (Platform.isIOS) return true; + await Permission.bluetoothConnect.request(); + await Permission.microphone.request(); + await Permission.camera.request(); + + while ((await Permission.camera.isDenied)) { + await Permission.camera.request(); + } + + while ((await Permission.microphone.isDenied)) { + await Permission.microphone.request(); + } + + while ((await Permission.bluetoothConnect.isDenied)) { + await Permission.bluetoothConnect.request(); + } + return true; + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/main.dart b/demo_app_with_100ms_and_bloc/lib/main.dart new file mode 100644 index 000000000..d1fd048a2 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/main.dart @@ -0,0 +1,21 @@ +import 'package:demo_app_with_100ms_and_bloc/home_page.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const HomePage(), + ); + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/observers/preview_observer.dart b/demo_app_with_100ms_and_bloc/lib/observers/preview_observer.dart new file mode 100644 index 000000000..da7d6018e --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/observers/preview_observer.dart @@ -0,0 +1,59 @@ +import 'package:demo_app_with_100ms_and_bloc/bloc/preview/preview_cubit.dart'; +import 'package:demo_app_with_100ms_and_bloc/services/RoomService.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class PreviewObserver implements HMSPreviewListener { + PreviewCubit previewCubit; + + List localTracks = []; + + PreviewObserver(this.previewCubit) { + previewCubit.hmsSdk.addPreviewListener(listener: this); + + previewCubit.hmsSdk.build(); + RoomService() + .getToken(user: previewCubit.name, room: previewCubit.url) + .then((token) { + if (token == null) return; + if (token[0] == null) return; + + HMSConfig config = HMSConfig( + authToken: token[0]!, + userName: previewCubit.name, + ); + + previewCubit.hmsSdk.preview(config: config); + }); + } + + @override + void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) { + // TODO: implement onPeerUpdate + } + + @override + void onPreview({required HMSRoom room, required List localTracks}) { + List videoTracks = []; + for (var track in localTracks) { + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + videoTracks.add(track as HMSVideoTrack); + } + } + this.localTracks.clear(); + this.localTracks.addAll(videoTracks); + previewCubit.updateTracks(this.localTracks); + } + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) { + // TODO: implement onRoomUpdate + } + + @override + void onHMSError({required HMSException error}) { + if (kDebugMode) { + print("OnError ${error.message}"); + } + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/observers/room_observer.dart b/demo_app_with_100ms_and_bloc/lib/observers/room_observer.dart new file mode 100644 index 000000000..a0dd1b3d7 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/observers/room_observer.dart @@ -0,0 +1,163 @@ +import 'package:demo_app_with_100ms_and_bloc/bloc/room/peer_track_node.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_bloc.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_event.dart'; +import 'package:demo_app_with_100ms_and_bloc/services/RoomService.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:rxdart/subjects.dart'; + +class RoomObserver implements HMSUpdateListener, HMSActionResultListener { + RoomOverviewBloc roomOverviewBloc; + + RoomObserver(this.roomOverviewBloc) { + roomOverviewBloc.hmsSdk.addUpdateListener(listener: this); + + roomOverviewBloc.hmsSdk.build(); + RoomService() + .getToken(user: roomOverviewBloc.name, room: roomOverviewBloc.url) + .then((token) { + if (token == null) return; + if (token[0] == null) return; + + HMSConfig config = HMSConfig( + authToken: token[0]!, + userName: roomOverviewBloc.name, + ); + + roomOverviewBloc.hmsSdk.join(config: config); + }); + } + + final _peerNodeStreamController = + BehaviorSubject>.seeded(const []); + + Stream> getTracks() => + _peerNodeStreamController.asBroadcastStream(); + + Future addPeer(HMSVideoTrack hmsVideoTrack, HMSPeer peer) async { + final tracks = [..._peerNodeStreamController.value]; + final todoIndex = tracks.indexWhere((t) => t.peer?.peerId == peer.peerId); + if (todoIndex >= 0) { + print("onTrackUpdate ${peer.name} ${hmsVideoTrack.isMute}"); + tracks[todoIndex] = + PeerTrackNode(hmsVideoTrack, hmsVideoTrack.isMute, peer, false); + } else { + tracks + .add(PeerTrackNode(hmsVideoTrack, hmsVideoTrack.isMute, peer, false)); + } + + _peerNodeStreamController.add(tracks); + } + + Future deletePeer(String id) async { + final tracks = [..._peerNodeStreamController.value]; + final todoIndex = tracks.indexWhere((t) => t.peer?.peerId == id); + if (todoIndex >= 0) { + tracks.removeAt(todoIndex); + } + _peerNodeStreamController.add(tracks); + } + + @override + void onChangeTrackStateRequest( + {required HMSTrackChangeRequest hmsTrackChangeRequest}) { + // TODO: implement onChangeTrackStateRequest + } + + @override + void onHMSError({required HMSException error}) { + // TODO: implement onError + } + + @override + void onJoin({required HMSRoom room}) { + // TODO: implement onJoin + roomOverviewBloc.add(RoomOverviewOnJoinSuccess(room)); + } + + @override + void onMessage({required HMSMessage message}) { + // TODO: implement onMessage + } + + @override + void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) { + // TODO: implement onPeerUpdate + } + + @override + void onReconnected() { + // TODO: implement onReconnected + } + + @override + void onReconnecting() { + // TODO: implement onReconnecting + } + + @override + void onRemovedFromRoom( + {required HMSPeerRemovedFromPeer hmsPeerRemovedFromPeer}) { + // TODO: implement onRemovedFromRoom + } + + @override + void onRoleChangeRequest({required HMSRoleChangeRequest roleChangeRequest}) { + // TODO: implement onRoleChangeRequest + } + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) { + // TODO: implement onRoomUpdate + } + + @override + void onTrackUpdate( + {required HMSTrack track, + required HMSTrackUpdate trackUpdate, + required HMSPeer peer}) { + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + if (trackUpdate == HMSTrackUpdate.trackRemoved) { + roomOverviewBloc + .add(RoomOverviewOnPeerLeave(track as HMSVideoTrack, peer)); + } else if (trackUpdate == HMSTrackUpdate.trackAdded || + trackUpdate == HMSTrackUpdate.trackMuted || + trackUpdate == HMSTrackUpdate.trackUnMuted) { + roomOverviewBloc + .add(RoomOverviewOnPeerJoin(track as HMSVideoTrack, peer)); + } + } + } + + Future leaveMeeting() async { + roomOverviewBloc.hmsSdk.leave(hmsActionResultListener: this); + } + + Future setOffScreen(int index, bool setOffScreen) async { + final tracks = [..._peerNodeStreamController.value]; + + if (index >= 0) { + tracks[index] = tracks[index].copyWith(isOffScreen: setOffScreen); + } + _peerNodeStreamController.add(tracks); + } + + @override + void onUpdateSpeakers({required List updateSpeakers}) { + // TODO: implement onUpdateSpeakers + } + + @override + void onException( + {HMSActionResultListenerMethod? methodType, + Map? arguments, + required HMSException hmsException}) { + // TODO: implement onException + } + + @override + void onSuccess( + {HMSActionResultListenerMethod? methodType, + Map? arguments}) { + _peerNodeStreamController.add([]); + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/services/Constants.dart b/demo_app_with_100ms_and_bloc/lib/services/Constants.dart new file mode 100644 index 000000000..3fc8e2c69 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/services/Constants.dart @@ -0,0 +1,23 @@ +class Constant { + static String prodTokenEndpoint = + "https://prod-in.100ms.live/hmsapi/get-token"; + + static String qaTokenEndPoint = "https://qa-in.100ms.live/hmsapi/get-token"; + + static String tokenQuery = "api/token"; + + static String defaultRoomID = + "https://yogi.app.100ms.live/meeting/ssz-eqr-eaa"; + + static String tokenKey = "token"; + + static String idKey = "id"; + + static String roomIDKey = "roomID"; + + static String hostKey = "host"; + static String defaultRole = 'host'; + static String meetingUrl = defaultRoomID; + static String meetingCode = ""; + static String rtmpUrl = ""; +} diff --git a/demo_app_with_100ms_and_bloc/lib/services/RoomService.dart b/demo_app_with_100ms_and_bloc/lib/services/RoomService.dart new file mode 100644 index 000000000..989c92744 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/services/RoomService.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; + +import 'Constants.dart'; +import 'package:http/http.dart' as http; + +class RoomService { + Future?> getToken( + {required String user, required String room}) async { + Constant.meetingUrl = room; + List codeAndDomain = getCode(room) ?? []; + if (codeAndDomain.isEmpty) { + return null; + } + Constant.meetingCode = codeAndDomain[1] ?? ''; + Uri endPoint = codeAndDomain[2] == "true" + ? Uri.parse(Constant.prodTokenEndpoint) + : Uri.parse(Constant.qaTokenEndPoint); + http.Response response = await http.post(endPoint, body: { + 'code': (codeAndDomain[1] ?? "").trim(), + 'user_id': user, + }, headers: { + 'subdomain': (codeAndDomain[0] ?? "").trim() + }); + + var body = json.decode(response.body); + return [body['token'], codeAndDomain[2]!.trim()]; + } + + List? getCode(String roomUrl) { + String url = roomUrl; + if (url == "") return []; + url = url.trim(); + bool isProdM = url.contains(".app.100ms.live/meeting/"); + bool isProdP = url.contains(".app.100ms.live/preview/"); + bool isQaM = url.contains(".qa-app.100ms.live/meeting/"); + bool isQaP = url.contains(".qa-app.100ms.live/preview/"); + + if (!isProdM && !isQaM && isQaP && isProdP) return []; + + List codeAndDomain = []; + String code = ""; + String subDomain = ""; + if (isProdM || isProdP) { + codeAndDomain = isProdM + ? url.split(".app.100ms.live/meeting/") + : url.split(".app.100ms.live/preview/"); + code = codeAndDomain[1]; + subDomain = codeAndDomain[0].split("https://")[1] + ".app.100ms.live"; + } else if (isQaM || isQaP) { + codeAndDomain = isQaM + ? url.split(".qa-app.100ms.live/meeting/") + : url.split(".qa-app.100ms.live/preview/"); + code = codeAndDomain[1]; + subDomain = codeAndDomain[0].split("https://")[1] + ".qa-app.100ms.live"; + } + return [subDomain, code, isProdM || isProdP ? "true" : "false"]; + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/views/preview_widget.dart b/demo_app_with_100ms_and_bloc/lib/views/preview_widget.dart new file mode 100644 index 000000000..00f3bc8d5 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/views/preview_widget.dart @@ -0,0 +1,114 @@ +import 'package:demo_app_with_100ms_and_bloc/bloc/preview/preview_cubit.dart'; +import 'package:demo_app_with_100ms_and_bloc/views/room_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class Preview extends StatelessWidget { + final String meetingUrl; + final String userName; + + const Preview(this.meetingUrl, this.userName, {Key? key}) : super(key: key); + + static Route route(String url, String name) { + return MaterialPageRoute(builder: (_) => Preview(url, name)); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => PreviewCubit(userName, meetingUrl), + child: PreviewWidget(meetingUrl, userName)); + } +} + +class PreviewWidget extends StatelessWidget { + final String meetingUrl; + final String userName; + + const PreviewWidget(this.meetingUrl, this.userName, {Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + final double itemHeight = size.height; + final double itemWidth = size.width; + + return Scaffold( + body: Column( + children: [ + Expanded( + child: BlocBuilder( + builder: (context, state) { + return state.tracks.isEmpty + ? SizedBox( + height: itemHeight / 1.3, + child: const Center( + child: CircularProgressIndicator(), + ), + ) + : SizedBox( + height: itemHeight, + width: itemWidth, + child: Stack( + children: [ + HMSVideoView( + track: state.tracks[0], matchParent: true), + Positioned( + bottom: 20.0, + left: itemWidth / 2 - 50.0, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + primary: Colors.blue, + padding: const EdgeInsets.all(14)), + onPressed: () { + Navigator.of(context).pushReplacement( + Room.route(meetingUrl, userName, + state.isVideoOff, state.isMicOff)); + }, + child: const Text( + "Join Now", + style: TextStyle(height: 1, fontSize: 18), + ), + ), + ), + Positioned( + bottom: 20.0, + right: 50.0, + child: IconButton( + onPressed: () { + context.read().toggleAudio(); + }, + icon: Icon( + state.isMicOff ? Icons.mic_off : Icons.mic, + size: 30.0, + color: Colors.blue, + )), + ), + Positioned( + bottom: 20.0, + left: 50.0, + child: IconButton( + onPressed: () { + context.read().toggleVideo(); + }, + icon: Icon( + state.isVideoOff + ? Icons.videocam_off + : Icons.videocam, + size: 30.0, + color: Colors.blueAccent, + )), + ), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/views/room_widget.dart b/demo_app_with_100ms_and_bloc/lib/views/room_widget.dart new file mode 100644 index 000000000..888a3e8b3 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/views/room_widget.dart @@ -0,0 +1,108 @@ +import 'package:demo_app_with_100ms_and_bloc/bloc/preview/preview_cubit.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_bloc.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_event.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_state.dart'; +import 'package:demo_app_with_100ms_and_bloc/home_page.dart'; +import 'package:demo_app_with_100ms_and_bloc/views/video_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class Room extends StatelessWidget { + final String meetingUrl; + final String userName; + final bool isVideoOff; + final bool isAudioOff; + + static Route route(String url, String name, bool v, bool a) { + return MaterialPageRoute(builder: (_) => Room(url, name, v, a)); + } + + const Room(this.meetingUrl, this.userName, this.isVideoOff, this.isAudioOff, + {Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => + RoomOverviewBloc(isVideoOff, isAudioOff, userName, meetingUrl) + ..add(const RoomOverviewSubscriptionRequested()), + child: RoomWidget(meetingUrl, userName), + ); + } +} + +class RoomWidget extends StatelessWidget { + final String meetingUrl; + final String userName; + + const RoomWidget(this.meetingUrl, this.userName, {Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: BlocConsumer( + listener: (ctx, state) { + if (state.leaveMeeting) { + Navigator.of(context).pushReplacement(HomePage.route()); + } + }, + builder: (ctx, state) { + return ListView.builder( + itemCount: state.peerTrackNodes.length, + itemBuilder: (ctx, index) { + return Card( + key: Key( + state.peerTrackNodes[index].peer!.peerId.toString()), + child: SizedBox(height: 250.0, child: VideoWidget(index))); + }, + ); + }, + ), + ), + bottomNavigationBar: BlocBuilder( + builder: (ctx, state) { + return BottomNavigationBar( + type: BottomNavigationBarType.fixed, + backgroundColor: Colors.black, + selectedItemColor: Colors.greenAccent, + unselectedItemColor: Colors.grey, + items: [ + const BottomNavigationBarItem( + icon: Icon(Icons.cancel), + label: 'Leave Meeting', + ), + BottomNavigationBarItem( + icon: Icon(state.isAudioMute ? Icons.mic_off : Icons.mic), + label: 'Mic', + ), + BottomNavigationBarItem( + icon: Icon( + state.isVideoMute ? Icons.videocam_off : Icons.videocam), + label: 'Camera', + ), + ], + + //New + onTap: (index) => _onItemTapped(index, context)); + }, + ), + ); + } + + void _onItemTapped(int index, BuildContext context) { + if (index == 0) { + context.read().add(const RoomOverviewLeaveRequested()); + } else if (index == 1) { + context + .read() + .add(const RoomOverviewLocalPeerAudioToggled()); + } else { + context + .read() + .add(const RoomOverviewLocalPeerVideoToggled()); + } + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/views/video_view.dart b/demo_app_with_100ms_and_bloc/lib/views/video_view.dart new file mode 100644 index 000000000..d414c2719 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/views/video_view.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class VideoView extends StatefulWidget { + final HMSVideoTrack track; + + const VideoView(this.track, {Key? key}) : super(key: key); + + @override + State createState() => _VideoViewState(); +} + +class _VideoViewState extends State { + @override + Widget build(BuildContext context) { + return HMSVideoView(track: widget.track, matchParent: true); + } +} diff --git a/demo_app_with_100ms_and_bloc/lib/views/video_widget.dart b/demo_app_with_100ms_and_bloc/lib/views/video_widget.dart new file mode 100644 index 000000000..427bfd2de --- /dev/null +++ b/demo_app_with_100ms_and_bloc/lib/views/video_widget.dart @@ -0,0 +1,85 @@ +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_bloc.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_event.dart'; +import 'package:demo_app_with_100ms_and_bloc/bloc/room/room_overview_state.dart'; +import 'package:demo_app_with_100ms_and_bloc/views/video_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:focus_detector/focus_detector.dart'; + +class VideoWidget extends StatefulWidget { + final int index; + + const VideoWidget(this.index, {Key? key}) : super(key: key); + + @override + State createState() => _VideoWidgetState(); +} + +class _VideoWidgetState extends State { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (ctx, state) => FocusDetector( + onFocusGained: () { + if (state.leaveMeeting && !mounted) { + return; + } + context + .read() + .add(RoomOverviewSetOffScreen(false, widget.index)); + }, + onFocusLost: () { + if (state.leaveMeeting && !mounted) { + return; + } + context + .read() + .add(RoomOverviewSetOffScreen(true, widget.index)); + }, + child: (state.peerTrackNodes[widget.index].peer!.isLocal + ? !state.isVideoMute + : !state.peerTrackNodes[widget.index].isMute!) && + !(state.peerTrackNodes[widget.index].isOffScreen) + ? ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: Column( + children: [ + SizedBox( + height: 200.0, + width: 400.0, + child: VideoView( + state.peerTrackNodes[widget.index].hmsVideoTrack!), + ), + Text( + state.peerTrackNodes[widget.index].peer!.name, + ) + ], + ), + ) + : Container( + height: 200.0, + width: 400.0, + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + backgroundColor: Colors.green, + radius: 36, + child: Text( + state.peerTrackNodes[widget.index].peer!.name[0], + style: const TextStyle( + fontSize: 36, color: Colors.white), + )), + const SizedBox( + height: 20.0, + ), + Text( + state.peerTrackNodes[widget.index].peer!.name, + ) + ], + )), + ), + ); + } +} diff --git a/demo_app_with_100ms_and_bloc/pubspec.lock b/demo_app_with_100ms_and_bloc/pubspec.lock new file mode 100644 index 000000000..990edd5cc --- /dev/null +++ b/demo_app_with_100ms_and_bloc/pubspec.lock @@ -0,0 +1,294 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.2" + bloc: + dependency: "direct main" + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.3" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.1" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + focus_detector: + dependency: "direct main" + description: + name: focus_detector + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + hmssdk_flutter: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.7.3" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "9.2.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.2+1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.4" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" + rxdart: + dependency: "direct main" + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.27.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.8" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.2" +sdks: + dart: ">=2.16.1 <3.0.0" + flutter: ">=2.8.0" diff --git a/demo_app_with_100ms_and_bloc/pubspec.yaml b/demo_app_with_100ms_and_bloc/pubspec.yaml new file mode 100644 index 000000000..509cb403a --- /dev/null +++ b/demo_app_with_100ms_and_bloc/pubspec.yaml @@ -0,0 +1,100 @@ +name: demo_app_with_100ms_and_bloc +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.16.1 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_bloc: ^8.0.1 + equatable: + bloc: + permission_handler: + focus_detector: + http: + rxdart: ^0.27.2 + hmssdk_flutter: + path: ../ #branch name + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + assets: + - assets/icons/ + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/demo_app_with_100ms_and_bloc/test/widget_test.dart b/demo_app_with_100ms_and_bloc/test/widget_test.dart new file mode 100644 index 000000000..22d3a0455 --- /dev/null +++ b/demo_app_with_100ms_and_bloc/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:demo_app_with_100ms_and_bloc/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/demo_with_getx_and_100ms/.gitignore b/demo_with_getx_and_100ms/.gitignore new file mode 100644 index 000000000..0fa6b675c --- /dev/null +++ b/demo_with_getx_and_100ms/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/demo_with_getx_and_100ms/.metadata b/demo_with_getx_and_100ms/.metadata new file mode 100644 index 000000000..5a023280a --- /dev/null +++ b/demo_with_getx_and_100ms/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 7e9793dee1b85a243edd0e06cb1658e98b077561 + channel: stable + +project_type: app diff --git a/demo_with_getx_and_100ms/README.md b/demo_with_getx_and_100ms/README.md new file mode 100644 index 000000000..45ea8bed7 --- /dev/null +++ b/demo_with_getx_and_100ms/README.md @@ -0,0 +1,119 @@ +# demo_with_getx_and_100ms + +A demo app using Getx and 100ms. + +## Getting Started + +A few resources to get you started if this is your first Flutter project: + +- [100ms flutter documentation](https://www.100ms.live/docs/flutter/v2/foundation/basics) +- [getx](https://pub.dev/packages/get) + +## Now we will see steps to use getx and 100ms + +1. Create a controller class extending GetxController. + + ```dart + class RoomController extends GetxController{} + ``` + +3. In onInit() initialize all the necessary objects like hmssdk and creating tokens. + + ```dart + void onInit() async { + hmsSdk.addUpdateListener(listener: this); + + hmsSdk.build(); + List? token = await RoomService().getToken(user: name, room: url); + + if (token == null) return; + if (token[0] == null) return; + + HMSConfig config = HMSConfig( + authToken: token[0]!, + userName: name, + ); + + hmsSdk.join(config: config); + super.onInit(); + } + + ``` + +4. As you get onJoin() or onPreview() callback it means join success. create a list of PeerTrackNode on onTrackUpdate() callback. + + + ```dart + RxList> peerTrackList = + >[].obs; + + @override + void onTrackUpdate( + {required HMSTrack track, + required HMSTrackUpdate trackUpdate, + required HMSPeer peer}) { + + + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + + if (trackUpdate == HMSTrackUpdate.trackRemoved) { + removeUserFromList(peer); + } else if(trackUpdate == HMSTrackUpdate.trackAdded) { + int index = peerTrackList.indexWhere((element) => element.value.peer.peerId == peer.peerId); + if(index > -1){ + peerTrackList[index](PeerTrackNode(track as HMSVideoTrack, track.isMute, peer)); + } + else{ + peerTrackList.add(PeerTrackNode(track as HMSVideoTrack, track.isMute, peer).obs); + } + } + + } + } + ``` + +5. you can add or delete peerTrackNode from list according to onPeerUpdate callback or onTrackUpdate as well. + +7. on UI side use GetXBuilder for rebuilding on observable changes. + + + ```dart + GetX(builder: (controller) { + return ListView.builder( + itemCount: controller.peerTrackList.length, + itemBuilder: (ctx, index) { + return Card( + key: Key(controller.peerTrackList[index].value.peer.peerId + .toString()), + child: SizedBox( + height: 250.0, child: VideoWidget(index, roomController))); + }, + ); + }) + ``` + +7. you can also use obx for listening to changes + +## How to use PeerTrackNode model class + +This model class is used for some changes in a Peer.So that getx can only rebuild that particular changes. + +```dart + +class PeerTrackNode{ + HMSVideoTrack hmsVideoTrack; + bool isMute = true; + HMSPeer peer; + bool isOffScreen = true; + + PeerTrackNode(this.hmsVideoTrack, this.isMute, this.peer , {this.isOffScreen = false}); + +} +``` + +So now, for changing some properties in PeerTrackNode you need to do something like this. + +```dart + peerTrackList[index](PeerTrackNode(track as HMSVideoTrack, track.isMute, peer)); +``` +This is the way you need to do so that Obx can listen to changes and can rebuild the widget. diff --git a/demo_with_getx_and_100ms/analysis_options.yaml b/demo_with_getx_and_100ms/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/demo_with_getx_and_100ms/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/demo_with_getx_and_100ms/android/.gitignore b/demo_with_getx_and_100ms/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/demo_with_getx_and_100ms/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/demo_with_getx_and_100ms/android/app/build.gradle b/demo_with_getx_and_100ms/android/app/build.gradle new file mode 100644 index 000000000..a73647d5c --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 32 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.demo_with_getx_and_100ms" + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/demo_with_getx_and_100ms/android/app/proguard-rules.pro b/demo_with_getx_and_100ms/android/app/proguard-rules.pro new file mode 100644 index 000000000..5f6821f1a --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/proguard-rules.pro @@ -0,0 +1,32 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android +-keepattributes SourceFile,LineNumberTable # Keep file names and line numbers. +#-keep public class * extends java.lang.Exception # Optional: Keep custom exceptions. + +# https://developer.android.com/guide/navigation/navigation-pass-data#proguard_considerations + + +# Video libs +-keep class org.webrtc.** { *; } +-keep class live.hms.video.** { *; } \ No newline at end of file diff --git a/demo_with_getx_and_100ms/android/app/src/debug/AndroidManifest.xml b/demo_with_getx_and_100ms/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..607c4718b --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/demo_with_getx_and_100ms/android/app/src/main/AndroidManifest.xml b/demo_with_getx_and_100ms/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..f5b8febb2 --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo_with_getx_and_100ms/android/app/src/main/kotlin/com/example/demo_with_getx_and_100ms/MainActivity.kt b/demo_with_getx_and_100ms/android/app/src/main/kotlin/com/example/demo_with_getx_and_100ms/MainActivity.kt new file mode 100644 index 000000000..57acf123b --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/src/main/kotlin/com/example/demo_with_getx_and_100ms/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.demo_with_getx_and_100ms + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/drawable-v21/launch_background.xml b/demo_with_getx_and_100ms/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/drawable/launch_background.xml b/demo_with_getx_and_100ms/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/demo_with_getx_and_100ms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/values-night/styles.xml b/demo_with_getx_and_100ms/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..3db14bb53 --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/demo_with_getx_and_100ms/android/app/src/main/res/values/styles.xml b/demo_with_getx_and_100ms/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..d460d1e92 --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/demo_with_getx_and_100ms/android/app/src/profile/AndroidManifest.xml b/demo_with_getx_and_100ms/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..607c4718b --- /dev/null +++ b/demo_with_getx_and_100ms/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/demo_with_getx_and_100ms/android/build.gradle b/demo_with_getx_and_100ms/android/build.gradle new file mode 100644 index 000000000..4256f9173 --- /dev/null +++ b/demo_with_getx_and_100ms/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/demo_with_getx_and_100ms/android/gradle.properties b/demo_with_getx_and_100ms/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/demo_with_getx_and_100ms/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/demo_with_getx_and_100ms/android/gradle/wrapper/gradle-wrapper.properties b/demo_with_getx_and_100ms/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..bc6a58afd --- /dev/null +++ b/demo_with_getx_and_100ms/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/demo_with_getx_and_100ms/android/settings.gradle b/demo_with_getx_and_100ms/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/demo_with_getx_and_100ms/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/demo_with_getx_and_100ms/assets/icons/getx_logo.webp b/demo_with_getx_and_100ms/assets/icons/getx_logo.webp new file mode 100644 index 000000000..a5279c197 Binary files /dev/null and b/demo_with_getx_and_100ms/assets/icons/getx_logo.webp differ diff --git a/demo_with_getx_and_100ms/assets/icons/hms_icon_1024.png b/demo_with_getx_and_100ms/assets/icons/hms_icon_1024.png new file mode 100644 index 000000000..df281e975 Binary files /dev/null and b/demo_with_getx_and_100ms/assets/icons/hms_icon_1024.png differ diff --git a/demo_with_getx_and_100ms/ios/.gitignore b/demo_with_getx_and_100ms/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/demo_with_getx_and_100ms/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/demo_with_getx_and_100ms/ios/Flutter/AppFrameworkInfo.plist b/demo_with_getx_and_100ms/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..8d4492f97 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/demo_with_getx_and_100ms/ios/Flutter/Debug.xcconfig b/demo_with_getx_and_100ms/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/demo_with_getx_and_100ms/ios/Flutter/Release.xcconfig b/demo_with_getx_and_100ms/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.pbxproj b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..8f88f430d --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoWithGetxAnd100ms; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoWithGetxAnd100ms; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoWithGetxAnd100ms; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/demo_with_getx_and_100ms/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..c87d15a33 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo_with_getx_and_100ms/ios/Runner.xcworkspace/contents.xcworkspacedata b/demo_with_getx_and_100ms/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/demo_with_getx_and_100ms/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/demo_with_getx_and_100ms/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/demo_with_getx_and_100ms/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/demo_with_getx_and_100ms/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/demo_with_getx_and_100ms/ios/Runner/AppDelegate.swift b/demo_with_getx_and_100ms/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/demo_with_getx_and_100ms/ios/Runner/Base.lproj/LaunchScreen.storyboard b/demo_with_getx_and_100ms/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo_with_getx_and_100ms/ios/Runner/Base.lproj/Main.storyboard b/demo_with_getx_and_100ms/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo_with_getx_and_100ms/ios/Runner/Info.plist b/demo_with_getx_and_100ms/ios/Runner/Info.plist new file mode 100644 index 000000000..596e80ec1 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Demo With Getx And 100ms + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + demo_with_getx_and_100ms + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/demo_with_getx_and_100ms/ios/Runner/Runner-Bridging-Header.h b/demo_with_getx_and_100ms/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/demo_with_getx_and_100ms/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/demo_with_getx_and_100ms/lib/controllers/PreviewController.dart b/demo_with_getx_and_100ms/lib/controllers/PreviewController.dart new file mode 100644 index 000000000..99b21cc70 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/controllers/PreviewController.dart @@ -0,0 +1,114 @@ +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +import '../services/RoomService.dart'; + +class PreviewController extends GetxController + implements HMSPreviewListener, HMSActionResultListener { + RxBool isLocalVideoOn = Get.put(false.obs, tag: "isLocalVideoOn"); + RxBool isLocalAudioOn = Get.put(false.obs, tag: "isLocalAudioOn"); + + List localTracks = [].obs; + + String url; + String name; + + PreviewController(this.url, this.name); + + HMSSDK hmsSdk = Get.put(HMSSDK()); + + @override + void onInit() async { + hmsSdk.addPreviewListener(listener: this); + + hmsSdk.build(); + List? token = await RoomService().getToken(user: name, room: url); + + //print("Success ${token?.length} ${token?[0]}"); + + if (token == null) return; + if (token[0] == null) return; + + HMSConfig config = Get.put( + HMSConfig( + authToken: token[0]!, + userName: name, + ), + tag: ""); + + hmsSdk.preview(config: config); + + super.onInit(); + } + + void leaveMeeting() async { + hmsSdk.leave(hmsActionResultListener: this); + } + + void toggleAudio() async { + var result = await hmsSdk.switchAudio(isOn: isLocalAudioOn.value); + if (result == null) { + isLocalAudioOn.toggle(); + } + } + + void toggleVideo() async { + var result = await hmsSdk.switchVideo(isOn: isLocalVideoOn.value); + + if (result == null) { + isLocalVideoOn.toggle(); + + if (kDebugMode) { + print("RESULT VIDEO ${isLocalVideoOn.value}"); + } + } + } + + @override + void onException( + {HMSActionResultListenerMethod? methodType, + Map? arguments, + required HMSException hmsException}) { + Get.snackbar("Error", hmsException.message); + } + + @override + void onSuccess( + {HMSActionResultListenerMethod? methodType, + Map? arguments}) { + Get.back(); + } + + @override + void onPreview({required HMSRoom room, required List localTracks}) { + List videoTracks = []; + for (var track in localTracks) { + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + isLocalVideoOn.value = !track.isMute; + isLocalVideoOn.refresh(); + videoTracks.add(track as HMSVideoTrack); + } else { + isLocalAudioOn.value = !track.isMute; + isLocalAudioOn.refresh(); + } + } + this.localTracks.clear(); + this.localTracks.addAll(videoTracks); + } + + @override + void onHMSError({required HMSException error}) { + Get.snackbar("Error", error.message); + } + + @override + void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) { + // TODO: implement onPeerUpdate + } + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) { + // TODO: implement onRoomUpdate + } +} diff --git a/demo_with_getx_and_100ms/lib/controllers/RoomController.dart b/demo_with_getx_and_100ms/lib/controllers/RoomController.dart new file mode 100644 index 000000000..42acb7f37 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/controllers/RoomController.dart @@ -0,0 +1,208 @@ +import 'package:demo_with_getx_and_100ms/models/PeerTrackNode.dart'; +import 'package:demo_with_getx_and_100ms/views/HomePage.dart'; +import 'package:get/get.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +import '../services/RoomService.dart'; + +class RoomController extends GetxController + implements HMSUpdateListener, HMSActionResultListener { + RxList> peerTrackList = >[].obs; + RxBool isLocalVideoOn = false.obs; + RxBool isLocalAudioOn = false.obs; + + String url; + String name; + + RoomController(this.url, this.name); + + HMSSDK hmsSdk = Get.find(); + + RxBool isVideoOnPreview = false.obs; + RxBool isAudioOnPreview = false.obs; + + @override + void onInit() async { + hmsSdk.addUpdateListener(listener: this); + + hmsSdk.build(); + List? token = await RoomService().getToken(user: name, room: url); + + if (token == null) return; + if (token[0] == null) return; + + HMSConfig config = HMSConfig( + authToken: token[0]!, + userName: name, + ); + + hmsSdk.join(config: config); + + isVideoOnPreview = Get.find(tag: "isLocalVideoOn"); + isAudioOnPreview = Get.find(tag: "isLocalAudioOn"); + + super.onInit(); + } + + @override + void onChangeTrackStateRequest( + {required HMSTrackChangeRequest hmsTrackChangeRequest}) { + // TODO: implement onChangeTrackStateRequest + } + + @override + void onHMSError({required HMSException error}) { + Get.snackbar("Error", error.message); + } + + @override + void onJoin({required HMSRoom room}) { + peerTrackList.clear(); + isLocalAudioOn.value = isAudioOnPreview.value; + isLocalAudioOn.refresh(); + + isLocalVideoOn.value = isVideoOnPreview.value; + isLocalVideoOn.refresh(); + + hmsSdk.switchAudio(isOn: !isLocalAudioOn.value); + hmsSdk.switchVideo(isOn: !isLocalVideoOn.value); + } + + @override + void onMessage({required HMSMessage message}) { + // TODO: implement onMessage + } + + @override + void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) {} + + @override + void onReconnected() { + // TODO: implement onReconnected + } + + @override + void onReconnecting() { + // TODO: implement onReconnecting + } + + @override + void onRemovedFromRoom( + {required HMSPeerRemovedFromPeer hmsPeerRemovedFromPeer}) { + // TODO: implement onRemovedFromRoom + } + + @override + void onRoleChangeRequest({required HMSRoleChangeRequest roleChangeRequest}) { + // TODO: implement onRoleChangeRequest + } + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) { + // TODO: implement onRoomUpdate + } + + @override + void onTrackUpdate( + {required HMSTrack track, + required HMSTrackUpdate trackUpdate, + required HMSPeer peer}) { + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + if (trackUpdate == HMSTrackUpdate.trackRemoved) { + removeUserFromList(peer); + } else if (trackUpdate == HMSTrackUpdate.trackAdded) { + int index = peerTrackList + .indexWhere((element) => element.value.peer.peerId == peer.peerId); + if (index > -1) { + peerTrackList[index]( + PeerTrackNode(track as HMSVideoTrack, track.isMute, peer)); + } else { + peerTrackList.add( + PeerTrackNode(track as HMSVideoTrack, track.isMute, peer).obs); + } + } + } + } + + @override + void onUpdateSpeakers({required List updateSpeakers}) { + // TODO: implement onUpdateSpeakers + } + + void leaveMeeting() async { + hmsSdk.leave(hmsActionResultListener: this); + } + + void toggleAudio() async { + var result = await hmsSdk.switchAudio(isOn: isLocalAudioOn.value); + if (result == null) { + isLocalAudioOn.toggle(); + } + } + + void toggleVideo() async { + var result = await hmsSdk.switchVideo(isOn: isLocalVideoOn.value); + + if (result == null) { + isLocalVideoOn.toggle(); + } + } + + @override + void onException( + {HMSActionResultListenerMethod? methodType, + Map? arguments, + required HMSException hmsException}) { + Get.snackbar("Error", hmsException.message); + } + + @override + void onSuccess( + {HMSActionResultListenerMethod? methodType, + Map? arguments}) { + Get.back(); + Get.off(() => const HomePage()); + } + + void removeUserFromList(HMSPeer peer) { + peerTrackList + .removeWhere((element) => peer.peerId == element.value.peer.peerId); + } + + @override + void onLocalAudioStats( + {required HMSLocalAudioStats hmsLocalAudioStats, + required HMSLocalAudioTrack track, + required HMSPeer peer}) { + // TODO: implement onLocalAudioStats + } + + @override + void onLocalVideoStats( + {required HMSLocalVideoStats hmsLocalVideoStats, + required HMSLocalVideoTrack track, + required HMSPeer peer}) { + // TODO: implement onLocalVideoStats + } + + @override + void onRTCStats({required HMSRTCStatsReport hmsrtcStatsReport}) { + // TODO: implement onRTCStats + } + + @override + void onRemoteAudioStats( + {required HMSRemoteAudioStats hmsRemoteAudioStats, + required HMSRemoteAudioTrack track, + required HMSPeer peer}) { + // TODO: implement onRemoteAudioStats + } + + @override + void onRemoteVideoStats( + {required HMSRemoteVideoStats hmsRemoteVideoStats, + required HMSRemoteVideoTrack track, + required HMSPeer peer}) { + // TODO: implement onRemoteVideoStats + } +} diff --git a/demo_with_getx_and_100ms/lib/main.dart b/demo_with_getx_and_100ms/lib/main.dart new file mode 100644 index 000000000..ed35375ca --- /dev/null +++ b/demo_with_getx_and_100ms/lib/main.dart @@ -0,0 +1,23 @@ +import 'package:demo_with_getx_and_100ms/views/HomePage.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return GetMaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const HomePage(), + ); + } +} diff --git a/demo_with_getx_and_100ms/lib/models/PeerTrackNode.dart b/demo_with_getx_and_100ms/lib/models/PeerTrackNode.dart new file mode 100644 index 000000000..de4c81d38 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/models/PeerTrackNode.dart @@ -0,0 +1,23 @@ +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class PeerTrackNode { + HMSVideoTrack hmsVideoTrack; + bool isMute = true; + HMSPeer peer; + bool isOffScreen = true; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PeerTrackNode && + runtimeType == other.runtimeType && + isMute == other.isMute && + peer == other.peer && + isOffScreen == other.isOffScreen; + + @override + int get hashCode => isMute.hashCode ^ peer.hashCode ^ isOffScreen.hashCode; + + PeerTrackNode(this.hmsVideoTrack, this.isMute, this.peer, + {this.isOffScreen = false}); +} diff --git a/demo_with_getx_and_100ms/lib/services/Constants.dart b/demo_with_getx_and_100ms/lib/services/Constants.dart new file mode 100644 index 000000000..3fc8e2c69 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/services/Constants.dart @@ -0,0 +1,23 @@ +class Constant { + static String prodTokenEndpoint = + "https://prod-in.100ms.live/hmsapi/get-token"; + + static String qaTokenEndPoint = "https://qa-in.100ms.live/hmsapi/get-token"; + + static String tokenQuery = "api/token"; + + static String defaultRoomID = + "https://yogi.app.100ms.live/meeting/ssz-eqr-eaa"; + + static String tokenKey = "token"; + + static String idKey = "id"; + + static String roomIDKey = "roomID"; + + static String hostKey = "host"; + static String defaultRole = 'host'; + static String meetingUrl = defaultRoomID; + static String meetingCode = ""; + static String rtmpUrl = ""; +} diff --git a/demo_with_getx_and_100ms/lib/services/RoomService.dart b/demo_with_getx_and_100ms/lib/services/RoomService.dart new file mode 100644 index 000000000..81ffb0527 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/services/RoomService.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; + +import 'Constants.dart'; +import 'package:http/http.dart' as http; + +class RoomService { + Future?> getToken( + {required String user, required String room}) async { + Constant.meetingUrl = room; + List codeAndDomain = getCode(room) ?? []; + if (codeAndDomain.length == 0) { + return null; + } + Constant.meetingCode = codeAndDomain[1] ?? ''; + Uri endPoint = codeAndDomain[2] == "true" + ? Uri.parse(Constant.prodTokenEndpoint) + : Uri.parse(Constant.qaTokenEndPoint); + http.Response response = await http.post(endPoint, body: { + 'code': (codeAndDomain[1] ?? "").trim(), + 'user_id': user, + }, headers: { + 'subdomain': (codeAndDomain[0] ?? "").trim() + }); + + var body = json.decode(response.body); + return [body['token'], codeAndDomain[2]!.trim()]; + } + + List? getCode(String roomUrl) { + String url = roomUrl; + if (url == "") return []; + url = url.trim(); + bool isProdM = url.contains(".app.100ms.live/meeting/"); + bool isProdP = url.contains(".app.100ms.live/preview/"); + bool isQaM = url.contains(".qa-app.100ms.live/meeting/"); + bool isQaP = url.contains(".qa-app.100ms.live/preview/"); + + if (!isProdM && !isQaM && isQaP && isProdP) return []; + + List codeAndDomain = []; + String code = ""; + String subDomain = ""; + if (isProdM || isProdP) { + codeAndDomain = isProdM + ? url.split(".app.100ms.live/meeting/") + : url.split(".app.100ms.live/preview/"); + code = codeAndDomain[1]; + subDomain = codeAndDomain[0].split("https://")[1] + ".app.100ms.live"; + } else if (isQaM || isQaP) { + codeAndDomain = isQaM + ? url.split(".qa-app.100ms.live/meeting/") + : url.split(".qa-app.100ms.live/preview/"); + code = codeAndDomain[1]; + subDomain = codeAndDomain[0].split("https://")[1] + ".qa-app.100ms.live"; + } + return [subDomain, code, isProdM || isProdP ? "true" : "false"]; + } +} diff --git a/demo_with_getx_and_100ms/lib/views/HomePage.dart b/demo_with_getx_and_100ms/lib/views/HomePage.dart new file mode 100644 index 000000000..497e710ee --- /dev/null +++ b/demo_with_getx_and_100ms/lib/views/HomePage.dart @@ -0,0 +1,133 @@ +import 'dart:io'; + +import 'package:demo_with_getx_and_100ms/views/PreviewWidget.dart'; +import 'package:demo_with_getx_and_100ms/views/RoomWidget.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class HomePage extends StatelessWidget { + const HomePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final meetingTextController = TextEditingController( + text: "https://shaik.app.100ms.live/meeting/ajk-stp-ebs"); + final nameTextController = TextEditingController(); + + return SafeArea( + child: Scaffold( + appBar: AppBar( + leading: Image.asset("assets/icons/hms_icon_1024.png"), + title: const Text("100ms and Getx Demo App"), + actions: [ + Image.asset("assets/icons/getx_logo.webp"), + ], + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 300.0, + child: TextField( + controller: meetingTextController, + autofocus: true, + keyboardType: TextInputType.url, + decoration: InputDecoration( + hintText: 'Enter Room URL', + suffixIcon: IconButton( + onPressed: meetingTextController.clear, + icon: const Icon(Icons.clear), + ), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)))), + ), + ), + const SizedBox( + height: 30.0, + ), + SizedBox( + width: 300.0, + child: TextField( + controller: nameTextController, + autofocus: true, + keyboardType: TextInputType.url, + decoration: InputDecoration( + hintText: 'Enter Name', + suffixIcon: IconButton( + onPressed: nameTextController.clear, + icon: const Icon(Icons.clear), + ), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)))), + ), + ), + const SizedBox( + height: 30.0, + ), + SizedBox( + width: 300.0, + child: ElevatedButton( + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ))), + onPressed: () async { + if (!(GetUtils.isBlank(meetingTextController.text) ?? true) && + !(GetUtils.isBlank(nameTextController.text) ?? true)) { + if (kDebugMode) { + print( + "RaisedButton ${meetingTextController.text} ${nameTextController.text}"); + } + bool res = await getPermissions(); + if (res) { + Get.to(() => PreviewWidget( + meetingTextController.text, nameTextController.text)); + } + } + }, + child: Container( + padding: const EdgeInsets.all(4.0), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(16))), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(Icons.video_call_outlined, size: 48), + SizedBox( + width: 8, + ), + Text('Join Meeting', + style: TextStyle(height: 1, fontSize: 24)) + ], + ), + ), + ), + ) + ], + )), + ), + ); + } + + Future getPermissions() async { + if (Platform.isIOS) return true; + await Permission.camera.request(); + await Permission.microphone.request(); + + while ((await Permission.camera.isDenied)) { + await Permission.camera.request(); + } + while ((await Permission.microphone.isDenied)) { + await Permission.microphone.request(); + } + + while ((await Permission.bluetoothConnect.isDenied)) { + await Permission.bluetoothConnect.request(); + } + return true; + } +} diff --git a/demo_with_getx_and_100ms/lib/views/PreviewWidget.dart b/demo_with_getx_and_100ms/lib/views/PreviewWidget.dart new file mode 100644 index 000000000..940bfde45 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/views/PreviewWidget.dart @@ -0,0 +1,108 @@ +import 'package:demo_with_getx_and_100ms/controllers/PreviewController.dart'; +import 'package:flutter/material.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:get/get.dart'; + +import 'RoomWidget.dart'; + +class PreviewWidget extends StatelessWidget { + String meetingUrl; + String userName; + + late PreviewController previewController; + + PreviewWidget(this.meetingUrl, this.userName, {Key? key}) : super(key: key) { + previewController = Get.put(PreviewController(meetingUrl, userName)); + } + + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + final double itemHeight = size.height; + final double itemWidth = size.width; + + return Scaffold( + body: Column( + children: [ + Expanded( + child: GetX(builder: (controller) { + return Container( + child: controller.localTracks.length > 0 + ? SizedBox( + height: itemHeight, + width: itemWidth, + child: Stack( + children: [ + HMSVideoView( + track: controller.localTracks[0], + matchParent: true), + Positioned( + bottom: 20.0, + left: itemWidth / 2 - 50.0, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + primary: Colors.blue, + padding: const EdgeInsets.all(14)), + onPressed: () { + Get.off( + () => RoomWidget(meetingUrl, userName)); + }, + child: const Text( + "Join Now", + style: TextStyle(height: 1, fontSize: 18), + ), + ), + ), + Positioned( + bottom: 20.0, + right: 50.0, + child: GetX( + builder: (controller) { + return IconButton( + onPressed: () { + controller.toggleAudio(); + }, + icon: Icon( + controller.isLocalAudioOn.value + ? Icons.mic + : Icons.mic_off, + size: 30.0, + color: Colors.blue, + )); + }), + ), + Positioned( + bottom: 20.0, + left: 50.0, + child: GetX( + builder: (controller) { + return IconButton( + onPressed: () { + controller.toggleVideo(); + }, + icon: Icon( + controller.isLocalVideoOn.value + ? Icons.videocam + : Icons.videocam_off, + size: 30.0, + color: Colors.blueAccent, + )); + }), + ), + ], + ), + ) + : SizedBox( + height: itemHeight / 1.3, + child: const Center( + child: CircularProgressIndicator(), + ), + ), + ); + }), + ), + ], + ), + ); + } +} diff --git a/demo_with_getx_and_100ms/lib/views/RoomWidget.dart b/demo_with_getx_and_100ms/lib/views/RoomWidget.dart new file mode 100644 index 000000000..e9e0f47b2 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/views/RoomWidget.dart @@ -0,0 +1,71 @@ +import 'package:demo_with_getx_and_100ms/controllers/RoomController.dart'; +import 'package:demo_with_getx_and_100ms/views/VideoWidget.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class RoomWidget extends StatelessWidget { + final String meetingUrl; + final String userName; + + late final RoomController roomController; + + RoomWidget(this.meetingUrl, this.userName, {Key? key}) : super(key: key) { + roomController = Get.put(RoomController(meetingUrl, userName)); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: GetX(builder: (controller) { + return ListView.builder( + itemCount: controller.peerTrackList.length, + itemBuilder: (ctx, index) { + return Card( + key: Key(controller.peerTrackList[index].value.peer.peerId + .toString()), + child: SizedBox( + height: 250.0, child: VideoWidget(index, roomController))); + }, + ); + }), + bottomNavigationBar: GetX(builder: (controller) { + return BottomNavigationBar( + type: BottomNavigationBarType.fixed, + backgroundColor: Colors.black, + selectedItemColor: Colors.greenAccent, + unselectedItemColor: Colors.grey, + items: [ + const BottomNavigationBarItem( + icon: Icon(Icons.cancel), + label: 'Leave Meeting', + ), + BottomNavigationBarItem( + icon: Icon(controller.isLocalAudioOn.value + ? Icons.mic + : Icons.mic_off), + label: 'Mic', + ), + BottomNavigationBarItem( + icon: Icon(controller.isLocalVideoOn.value + ? Icons.videocam + : Icons.videocam_off), + label: 'Camera', + ), + ], + + //New + onTap: _onItemTapped); + }), + ); + } + + void _onItemTapped(int index) { + if (index == 0) { + roomController.leaveMeeting(); + } else if (index == 1) { + roomController.toggleAudio(); + } else { + roomController.toggleVideo(); + } + } +} diff --git a/demo_with_getx_and_100ms/lib/views/VideoView.dart b/demo_with_getx_and_100ms/lib/views/VideoView.dart new file mode 100644 index 000000000..cb8a0a260 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/views/VideoView.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class VideoView extends StatefulWidget { + final HMSVideoTrack track; + final String name; + const VideoView(this.track, this.name, {Key? key}) : super(key: key); + + @override + State createState() => _VideoViewState(); +} + +class _VideoViewState extends State { + @override + Widget build(BuildContext context) { + return HMSVideoView( + track: widget.track, + matchParent: true, + ); + } +} diff --git a/demo_with_getx_and_100ms/lib/views/VideoWidget.dart b/demo_with_getx_and_100ms/lib/views/VideoWidget.dart new file mode 100644 index 000000000..7d7395de5 --- /dev/null +++ b/demo_with_getx_and_100ms/lib/views/VideoWidget.dart @@ -0,0 +1,74 @@ +import 'package:demo_with_getx_and_100ms/controllers/RoomController.dart'; +import 'package:demo_with_getx_and_100ms/views/VideoView.dart'; +import 'package:flutter/material.dart'; +import 'package:focus_detector/focus_detector.dart'; +import 'package:get/get.dart'; + +class VideoWidget extends StatelessWidget { + final int index; + final RoomController roomController; + + const VideoWidget(this.index, this.roomController, {Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return FocusDetector( + onFocusGained: () { + roomController.peerTrackList[index].update((val) { + val?.isOffScreen = false; + }); + }, + onFocusLost: () { + roomController.peerTrackList[index].update((val) { + val?.isOffScreen = true; + }); + }, + child: Obx(() { + var user = roomController.peerTrackList[index]; + return (user.value.peer.isLocal + ? roomController.isLocalVideoOn.value + : !user.value.isMute) + ? ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: Column( + children: [ + SizedBox( + height: 200.0, + width: 400.0, + child: VideoView( + user.value.hmsVideoTrack, user.value.peer.name), + ), + Text( + user.value.peer.name, + ) + ], + ), + ) + : Container( + height: 200.0, + width: 400.0, + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CircleAvatar( + backgroundColor: Colors.green, + radius: 36, + child: Text( + user.value.peer.name[0], + style: const TextStyle( + fontSize: 36, color: Colors.white), + )), + const SizedBox( + height: 20.0, + ), + Text( + user.value.peer.name, + ) + ], + )); + }), + ); + } +} diff --git a/demo_with_getx_and_100ms/pubspec.lock b/demo_with_getx_and_100ms/pubspec.lock new file mode 100644 index 000000000..2aeb85be0 --- /dev/null +++ b/demo_with_getx_and_100ms/pubspec.lock @@ -0,0 +1,259 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + focus_detector: + dependency: "direct main" + description: + name: focus_detector + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + get: + dependency: "direct main" + description: + name: get + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.1" + hmssdk_flutter: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.7.3" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "9.2.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.2+1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.4" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.8" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.2" +sdks: + dart: ">=2.16.1 <3.0.0" + flutter: ">=2.8.0" diff --git a/demo_with_getx_and_100ms/pubspec.yaml b/demo_with_getx_and_100ms/pubspec.yaml new file mode 100644 index 000000000..9d323df9f --- /dev/null +++ b/demo_with_getx_and_100ms/pubspec.yaml @@ -0,0 +1,94 @@ +name: demo_with_getx_and_100ms +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.16.1 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + + flutter: + sdk: flutter + + + hmssdk_flutter: + path: ../ + + cupertino_icons: + get: + permission_handler: + focus_detector: + http: + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/icons/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/demo_with_getx_and_100ms/test/widget_test.dart b/demo_with_getx_and_100ms/test/widget_test.dart new file mode 100644 index 000000000..8fa75cdbf --- /dev/null +++ b/demo_with_getx_and_100ms/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:demo_with_getx_and_100ms/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/example/android/Gemfile.lock b/example/android/Gemfile.lock index 8c89dd116..7c132899c 100644 --- a/example/android/Gemfile.lock +++ b/example/android/Gemfile.lock @@ -8,8 +8,8 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.595.0) - aws-sdk-core (3.131.1) + aws-partitions (1.600.0) + aws-sdk-core (3.131.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -56,8 +56,8 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -107,9 +107,9 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-firebase_app_distribution (0.3.4) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.21.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-core (0.5.0) + google-apis-androidpublisher_v3 (0.22.0) + google-apis-core (>= 0.5, < 2.a) + google-apis-core (0.6.0) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -118,12 +118,12 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.10.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-playcustomapp_v1 (0.7.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.14.0) - google-apis-core (>= 0.4, < 2.a) + google-apis-iamcredentials_v1 (0.12.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-playcustomapp_v1 (0.9.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-storage_v1 (0.15.0) + google-apis-core (>= 0.5, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) @@ -151,7 +151,7 @@ GEM httpclient (2.8.3) jmespath (1.6.1) json (2.6.2) - jwt (2.3.0) + jwt (2.4.1) memoist (0.16.2) mini_magick (4.11.0) mini_mime (1.1.2) @@ -197,7 +197,7 @@ GEM unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.21.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 6b41a014e..81eab273e 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -21,7 +21,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 32 + compileSdkVersion 33 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -31,9 +31,9 @@ android { defaultConfig { applicationId "live.hms.flutter" minSdkVersion 21 - targetSdkVersion 32 - versionCode 72 - versionName "1.1.22" + targetSdkVersion 33 + versionCode 82 + versionName "1.1.32" } signingConfigs { @@ -63,8 +63,8 @@ flutter { } dependencies { - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'com.google.firebase:firebase-crashlytics:18.2.10' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'com.google.firebase:firebase-crashlytics:18.2.11' } apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.firebase-perf' diff --git a/example/assets/100ms.gif b/example/assets/100ms.gif deleted file mode 100644 index 25f30b1c6..000000000 Binary files a/example/assets/100ms.gif and /dev/null differ diff --git a/example/assets/icons/rotate.svg b/example/assets/icons/rotate.svg new file mode 100644 index 000000000..c65a3184d --- /dev/null +++ b/example/assets/icons/rotate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/assets/welcome.svg b/example/assets/welcome.svg new file mode 100644 index 000000000..54d547624 --- /dev/null +++ b/example/assets/welcome.svg @@ -0,0 +1,1437 @@ + + + + diff --git a/example/ios/FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements b/example/ios/FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements new file mode 100644 index 000000000..3850fed2d --- /dev/null +++ b/example/ios/FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.flutterhms + + + diff --git a/example/ios/FlutterBroadcastUploadExtension/Info.plist b/example/ios/FlutterBroadcastUploadExtension/Info.plist new file mode 100644 index 000000000..e9367904d --- /dev/null +++ b/example/ios/FlutterBroadcastUploadExtension/Info.plist @@ -0,0 +1,15 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.broadcast-services-upload + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).SampleHandler + RPBroadcastProcessMode + RPBroadcastProcessModeSampleBuffer + + + diff --git a/example/ios/FlutterBroadcastUploadExtension/SampleHandler.swift b/example/ios/FlutterBroadcastUploadExtension/SampleHandler.swift new file mode 100644 index 000000000..79269e9dc --- /dev/null +++ b/example/ios/FlutterBroadcastUploadExtension/SampleHandler.swift @@ -0,0 +1,56 @@ +// +// SampleHandler.swift +// FlutterBroadcastUploadExtension +// +// Created by Yogesh Singh on 14/06/22. +// Copyright © 2022 100ms. All rights reserved. +// + +import ReplayKit +import HMSBroadcastExtensionSDK + +class SampleHandler: RPBroadcastSampleHandler { + + // TODO: add option in Dart to pass the app group + let screenRenderer = HMSScreenRenderer(appGroup: "group.flutterhms") + + + override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) { + // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. + } + + override func broadcastPaused() { + // User has requested to pause the broadcast. Samples will stop being delivered. + } + + override func broadcastResumed() { + // User has requested to resume the broadcast. Samples delivery will resume. + } + + override func broadcastFinished() { + // User has requested to finish the broadcast. + screenRenderer.invalidate() + } + + override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) { + switch sampleBufferType { + case RPSampleBufferType.video: + // Handle video sample buffer + if let error = screenRenderer.process(sampleBuffer) { + if error.code == .noActiveMeeting { + finishBroadcastWithError(NSError(domain: "ScreenShare", code: error.code.rawValue, userInfo: [NSLocalizedFailureReasonErrorKey : "You are not in a meeting."])) + } + } + break + case RPSampleBufferType.audioApp: + // Handle audio sample buffer for app audio + break + case RPSampleBufferType.audioMic: + // Handle audio sample buffer for mic audio + break + @unknown default: + // Handle other sample buffer types + fatalError("Unknown type of sample buffer") + } + } +} diff --git a/example/ios/Gemfile.lock b/example/ios/Gemfile.lock index 8c89dd116..91d8fdab6 100644 --- a/example/ios/Gemfile.lock +++ b/example/ios/Gemfile.lock @@ -8,8 +8,8 @@ GEM artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.595.0) - aws-sdk-core (3.131.1) + aws-partitions (1.600.0) + aws-sdk-core (3.131.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -56,8 +56,8 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -106,10 +106,11 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-firebase_app_distribution (0.3.4) + fastlane-plugin-versioning (0.5.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.21.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-core (0.5.0) + google-apis-androidpublisher_v3 (0.22.0) + google-apis-core (>= 0.5, < 2.a) + google-apis-core (0.6.0) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -118,12 +119,12 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.10.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-playcustomapp_v1 (0.7.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.14.0) - google-apis-core (>= 0.4, < 2.a) + google-apis-iamcredentials_v1 (0.12.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-playcustomapp_v1 (0.9.0) + google-apis-core (>= 0.6, < 2.a) + google-apis-storage_v1 (0.15.0) + google-apis-core (>= 0.5, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) @@ -151,7 +152,7 @@ GEM httpclient (2.8.3) jmespath (1.6.1) json (2.6.2) - jwt (2.3.0) + jwt (2.4.1) memoist (0.16.2) mini_magick (4.11.0) mini_mime (1.1.2) @@ -197,7 +198,7 @@ GEM unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.21.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -216,6 +217,7 @@ PLATFORMS DEPENDENCIES fastlane fastlane-plugin-firebase_app_distribution + fastlane-plugin-versioning BUNDLED WITH 2.3.11 diff --git a/example/ios/Podfile b/example/ios/Podfile index 717fff83e..ef7c2cd67 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -33,6 +33,11 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end +target 'FlutterBroadcastUploadExtension' do + use_modular_headers! + pod 'HMSBroadcastExtensionSDK' +end + post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 9f00338af..5244e1ba6 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -12,18 +12,18 @@ PODS: - Firebase/Performance (8.15.0): - Firebase/CoreOnly - FirebasePerformance (~> 8.15.0) - - firebase_analytics (9.1.9): + - firebase_analytics (9.1.10): - Firebase/Analytics (= 8.15.0) - firebase_core - Flutter - - firebase_core (1.17.1): + - firebase_core (1.18.0): - Firebase/CoreOnly (= 8.15.0) - Flutter - - firebase_crashlytics (2.8.1): + - firebase_crashlytics (2.8.2): - Firebase/Crashlytics (= 8.15.0) - firebase_core - Flutter - - firebase_performance (0.8.0-13): + - firebase_performance (0.8.0-14): - Firebase/Performance (= 8.15.0) - firebase_core - Flutter @@ -131,12 +131,15 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (7.7.0): - GoogleUtilities/Logger + - HMSBroadcastExtensionSDK (0.0.1) - HMSSDK (0.3.1): - HMSWebRTC (= 1.0.4897) - - hmssdk_flutter (0.7.2): + - hmssdk_flutter (0.7.3): - Flutter + - HMSBroadcastExtensionSDK (= 0.0.1) - HMSSDK (= 0.3.1) - HMSWebRTC (1.0.4897) + - MTBBarcodeScanner (5.0.11) - nanopb (2.30908.0): - nanopb/decode (= 2.30908.0) - nanopb/encode (= 2.30908.0) @@ -149,6 +152,9 @@ PODS: - permission_handler_apple (9.0.4): - Flutter - PromisesObjC (2.1.0) + - qr_code_scanner (0.2.0): + - Flutter + - MTBBarcodeScanner - Toast (4.0.0) - video_player_avfoundation (0.0.1): - Flutter @@ -162,10 +168,12 @@ DEPENDENCIES: - firebase_performance (from `.symlinks/plugins/firebase_performance/ios`) - Flutter (from `Flutter`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - HMSBroadcastExtensionSDK - hmssdk_flutter (from `.symlinks/plugins/hmssdk_flutter/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) - wakelock (from `.symlinks/plugins/wakelock/ios`) @@ -183,8 +191,10 @@ SPEC REPOS: - GoogleAppMeasurement - GoogleDataTransport - GoogleUtilities + - HMSBroadcastExtensionSDK - HMSSDK - HMSWebRTC + - MTBBarcodeScanner - nanopb - PromisesObjC - Toast @@ -210,6 +220,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_ios/ios" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" + qr_code_scanner: + :path: ".symlinks/plugins/qr_code_scanner/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/ios" wakelock: @@ -217,10 +229,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d - firebase_analytics: 99eefffcacf3ab694db7926dd1291833e1300853 - firebase_core: 318de541b0e61d3f24262982a3f0b54afe72439b - firebase_crashlytics: 3ccd7b4f26a7fc546774b1074d2d10b0c6f92ab6 - firebase_performance: 41f6ab0cb84134c550a0624ec91e513faf667684 + firebase_analytics: 9029d8bd4cd488a3dd7ff32ec4fccd3160bf7200 + firebase_core: b14c1cfa29b9eb316345f74d1489328283825d6d + firebase_crashlytics: 248f810b9b21421d3e4d102cc1871ca4296e6753 + firebase_performance: 58de0f0a1029b115c30ddf2108d75e36ca951355 FirebaseABTesting: 10cbce8db9985ae2e3847ea44e9947dd18f94e10 FirebaseAnalytics: 7761cbadb00a717d8d0939363eb46041526474fa FirebaseCore: 5743c5785c074a794d35f2fff7ecc254a91e08b1 @@ -234,18 +246,21 @@ SPEC CHECKSUMS: GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e GoogleDataTransport: 5fffe35792f8b96ec8d6775f5eccd83c998d5a3b GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 + HMSBroadcastExtensionSDK: a19186ebbf8e381f447f382448038b38a9985be0 HMSSDK: 9780df180a4c8bd3446fd2981b338856fd02b3a4 - hmssdk_flutter: 3fed63723ebebd88699728f9a6fd93984bf51c74 + hmssdk_flutter: ea5a52cb937dd6f442e40555bb546e6a39277deb HMSWebRTC: aa317dbbf56a191463a90491e5e37f298f8fa29c + MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce PromisesObjC: 99b6f43f9e1044bd87a95a60beff28c2c44ddb72 + qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f -PODFILE CHECKSUM: e57a6b233c1c6fff78fd48a8b8a1b5b9fcc1e3a5 +PODFILE CHECKSUM: 3b22f8e4f0aeb192e1241ece38f59bf2a052864a COCOAPODS: 1.11.3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 10443ecde..07c65f7bc 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -13,10 +13,24 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + A6291DF792801F9A6A7EACA8 /* libPods-FlutterBroadcastUploadExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC902E842ECD88C50127D92B /* libPods-FlutterBroadcastUploadExtension.a */; }; + EC1052B22858A77D005EAB9E /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC1052B12858A77D005EAB9E /* ReplayKit.framework */; }; + EC1052B52858A77D005EAB9E /* SampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1052B42858A77D005EAB9E /* SampleHandler.swift */; }; + EC1052B92858A77D005EAB9E /* FlutterBroadcastUploadExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = EC1052B02858A77D005EAB9E /* FlutterBroadcastUploadExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; EC9BCD2B26B1CFDB00D378A0 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = EC9BCD2A26B1CFDB00D378A0 /* GoogleService-Info.plist */; }; EE34C22A67AA1F5AB40D3D81 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 63ECF97ABDF354FEEEDB85D5 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + EC1052B72858A77D005EAB9E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = EC1052AF2858A77D005EAB9E; + remoteInfo = FlutterBroadcastUploadExtension; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -28,6 +42,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + EC1052BA2858A77D005EAB9E /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + EC1052B92858A77D005EAB9E /* FlutterBroadcastUploadExtension.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -48,8 +73,17 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B0623F8FBD08D7EF79FF5844 /* Pods-FlutterBroadcastUploadExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlutterBroadcastUploadExtension.profile.xcconfig"; path = "Target Support Files/Pods-FlutterBroadcastUploadExtension/Pods-FlutterBroadcastUploadExtension.profile.xcconfig"; sourceTree = ""; }; + CD3C8BA7497CF5A349499EE7 /* Pods-FlutterBroadcastUploadExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlutterBroadcastUploadExtension.release.xcconfig"; path = "Target Support Files/Pods-FlutterBroadcastUploadExtension/Pods-FlutterBroadcastUploadExtension.release.xcconfig"; sourceTree = ""; }; + DC902E842ECD88C50127D92B /* libPods-FlutterBroadcastUploadExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FlutterBroadcastUploadExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + EC1052B02858A77D005EAB9E /* FlutterBroadcastUploadExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FlutterBroadcastUploadExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + EC1052B12858A77D005EAB9E /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; }; + EC1052B42858A77D005EAB9E /* SampleHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleHandler.swift; sourceTree = ""; }; + EC1052B62858A77D005EAB9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + EC638D182858ABE8005D0AF2 /* FlutterBroadcastUploadExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FlutterBroadcastUploadExtension.entitlements; sourceTree = ""; }; EC9BCD2A26B1CFDB00D378A0 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; ECFB42B6269F435A00A7BDB6 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + FA1DA48EB088E64EABD2ADFE /* Pods-FlutterBroadcastUploadExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlutterBroadcastUploadExtension.debug.xcconfig"; path = "Target Support Files/Pods-FlutterBroadcastUploadExtension/Pods-FlutterBroadcastUploadExtension.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -61,6 +95,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EC1052AD2858A77D005EAB9E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EC1052B22858A77D005EAB9E /* ReplayKit.framework in Frameworks */, + A6291DF792801F9A6A7EACA8 /* libPods-FlutterBroadcastUploadExtension.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -68,6 +111,8 @@ isa = PBXGroup; children = ( 63ECF97ABDF354FEEEDB85D5 /* libPods-Runner.a */, + EC1052B12858A77D005EAB9E /* ReplayKit.framework */, + DC902E842ECD88C50127D92B /* libPods-FlutterBroadcastUploadExtension.a */, ); name = Frameworks; sourceTree = ""; @@ -88,6 +133,7 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + EC1052B32858A77D005EAB9E /* FlutterBroadcastUploadExtension */, 97C146EF1CF9000F007C117D /* Products */, B2F80193CC408B94D74DE224 /* Pods */, 6B91F8981D56ACC93535B1B2 /* Frameworks */, @@ -98,6 +144,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + EC1052B02858A77D005EAB9E /* FlutterBroadcastUploadExtension.appex */, ); name = Products; sourceTree = ""; @@ -125,10 +172,23 @@ 89EFF6EE4AAF69984055D76C /* Pods-Runner.debug.xcconfig */, 41AA5D92A2CC1BB27F1A83DE /* Pods-Runner.release.xcconfig */, 238323F347131537C23069F5 /* Pods-Runner.profile.xcconfig */, + FA1DA48EB088E64EABD2ADFE /* Pods-FlutterBroadcastUploadExtension.debug.xcconfig */, + CD3C8BA7497CF5A349499EE7 /* Pods-FlutterBroadcastUploadExtension.release.xcconfig */, + B0623F8FBD08D7EF79FF5844 /* Pods-FlutterBroadcastUploadExtension.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; + EC1052B32858A77D005EAB9E /* FlutterBroadcastUploadExtension */ = { + isa = PBXGroup; + children = ( + EC638D182858ABE8005D0AF2 /* FlutterBroadcastUploadExtension.entitlements */, + EC1052B42858A77D005EAB9E /* SampleHandler.swift */, + EC1052B62858A77D005EAB9E /* Info.plist */, + ); + path = FlutterBroadcastUploadExtension; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -145,22 +205,43 @@ 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 7B56423DDF1948A38AE2EA10 /* [CP] Embed Pods Frameworks */, CC19BBB77EA3E7F699588CF7 /* [firebase_crashlytics] Crashlytics Upload Symbols */, + EC1052BA2858A77D005EAB9E /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + EC1052B82858A77D005EAB9E /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + EC1052AF2858A77D005EAB9E /* FlutterBroadcastUploadExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = EC1052BE2858A77D005EAB9E /* Build configuration list for PBXNativeTarget "FlutterBroadcastUploadExtension" */; + buildPhases = ( + 402164D3899CD572C6304156 /* [CP] Check Pods Manifest.lock */, + EC1052AC2858A77D005EAB9E /* Sources */, + EC1052AD2858A77D005EAB9E /* Frameworks */, + EC1052AE2858A77D005EAB9E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FlutterBroadcastUploadExtension; + productName = FlutterBroadcastUploadExtension; + productReference = EC1052B02858A77D005EAB9E /* FlutterBroadcastUploadExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1340; LastUpgradeCheck = 1300; ORGANIZATIONNAME = 100ms; TargetAttributes = { @@ -168,6 +249,9 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; + EC1052AF2858A77D005EAB9E = { + CreatedOnToolsVersion = 13.4; + }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -184,6 +268,7 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + EC1052AF2858A77D005EAB9E /* FlutterBroadcastUploadExtension */, ); }; /* End PBXProject section */ @@ -201,6 +286,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EC1052AE2858A77D005EAB9E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -218,6 +310,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 402164D3899CD572C6304156 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FlutterBroadcastUploadExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 7B56423DDF1948A38AE2EA10 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -303,8 +417,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EC1052AC2858A77D005EAB9E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EC1052B52858A77D005EAB9E /* SampleHandler.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + EC1052B82858A77D005EAB9E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = EC1052AF2858A77D005EAB9E /* FlutterBroadcastUploadExtension */; + targetProxy = EC1052B72858A77D005EAB9E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -379,12 +509,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 77; DEVELOPMENT_TEAM = 5N85PP82A9; ENABLE_BITCODE = YES; INFOPLIST_FILE = Runner/Info.plist; @@ -393,11 +524,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.1.27; PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = FlutterAdHocDistribution; SUPPORTS_MACCATALYST = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -517,12 +648,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CURRENT_PROJECT_VERSION = 77; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 5N85PP82A9; ENABLE_BITCODE = YES; @@ -532,7 +664,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.1.27; PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -550,12 +682,13 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 72; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 77; DEVELOPMENT_TEAM = 5N85PP82A9; ENABLE_BITCODE = YES; INFOPLIST_FILE = Runner/Info.plist; @@ -564,11 +697,11 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.1; + MARKETING_VERSION = 1.1.27; PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter; "PRODUCT_BUNDLE_IDENTIFIER[sdk=macosx*]" = ""; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; + PROVISIONING_PROFILE_SPECIFIER = FlutterAdHocDistribution; SUPPORTS_MACCATALYST = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -577,6 +710,127 @@ }; name = Release; }; + EC1052BB2858A77D005EAB9E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FA1DA48EB088E64EABD2ADFE /* Pods-FlutterBroadcastUploadExtension.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 79; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 5N85PP82A9; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = FlutterBroadcastUploadExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = FlutterBroadcastUploadExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 100ms. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.1.29; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter.FlutterBroadcastUploadExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + EC1052BC2858A77D005EAB9E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CD3C8BA7497CF5A349499EE7 /* Pods-FlutterBroadcastUploadExtension.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 79; + DEVELOPMENT_TEAM = 5N85PP82A9; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = FlutterBroadcastUploadExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = FlutterBroadcastUploadExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 100ms. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.1.29; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter.FlutterBroadcastUploadExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = FlutterAdhocBroadcastUploadExtension; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + EC1052BD2858A77D005EAB9E /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B0623F8FBD08D7EF79FF5844 /* Pods-FlutterBroadcastUploadExtension.profile.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = FlutterBroadcastUploadExtension/FlutterBroadcastUploadExtension.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 79; + DEVELOPMENT_TEAM = 5N85PP82A9; + GCC_C_LANGUAGE_STANDARD = gnu11; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = FlutterBroadcastUploadExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = FlutterBroadcastUploadExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 100ms. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.1.29; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = live.100ms.flutter.FlutterBroadcastUploadExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = FlutterAdhocBroadcastUploadExtension; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -600,6 +854,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + EC1052BE2858A77D005EAB9E /* Build configuration list for PBXNativeTarget "FlutterBroadcastUploadExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EC1052BB2858A77D005EAB9E /* Debug */, + EC1052BC2858A77D005EAB9E /* Release */, + EC1052BD2858A77D005EAB9E /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/FlutterBroadcastUploadExtension.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/FlutterBroadcastUploadExtension.xcscheme new file mode 100644 index 000000000..5fda27e0a --- /dev/null +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/FlutterBroadcastUploadExtension.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 784a84e0d..e2b218874 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.22 + 1.1.32 CFBundleSignature ???? CFBundleURLTypes @@ -34,7 +34,7 @@ CFBundleVersion - 72 + 82 ITSAppUsesNonExemptEncryption LSApplicationCategoryType diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements index a0a3b02b8..0288f4085 100644 --- a/example/ios/Runner/Runner.entitlements +++ b/example/ios/Runner/Runner.entitlements @@ -2,14 +2,18 @@ + aps-environment + development com.apple.developer.associated-domains applinks:*.100ms.live - aps-environment - development com.apple.security.app-sandbox + com.apple.security.application-groups + + group.flutterhms + com.apple.security.device.audio-input com.apple.security.device.camera diff --git a/example/lib/common/ui/organisms/audio_mute_status.dart b/example/lib/common/ui/organisms/audio_mute_status.dart index b94400470..aa1f77e28 100644 --- a/example/lib/common/ui/organisms/audio_mute_status.dart +++ b/example/lib/common/ui/organisms/audio_mute_status.dart @@ -1,4 +1,5 @@ //Package imports +import 'package:flutter_svg/flutter_svg.dart'; import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; @@ -18,18 +19,15 @@ class _AudioMuteStatusState extends State { peerTrackNode.audioTrack?.isMute ?? true, builder: (_, data, __) { return Positioned( - bottom: 20, - left: 0, - right: 0, + top: 5, + right: 5, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 3.0), + padding: const EdgeInsets.fromLTRB(0, 5, 5, 0), child: data - ? CircleAvatar( - backgroundColor: Colors.transparent.withOpacity(0.2), - child: Icon( - Icons.mic_off, - color: Colors.red, - )) + ? SvgPicture.asset( + 'assets/icons/mic_state_off.svg', + color: Colors.red, + ) : Container()), ); }); diff --git a/example/lib/common/ui/organisms/audio_tile.dart b/example/lib/common/ui/organisms/audio_tile.dart index a67c8af65..27d9a2403 100644 --- a/example/lib/common/ui/organisms/audio_tile.dart +++ b/example/lib/common/ui/organisms/audio_tile.dart @@ -36,7 +36,7 @@ class AudioTile extends StatelessWidget { bool removePeerPermission = _meetingStore.localPeer?.role.permissions.removeOthers ?? false; bool changeRolePermission = - _meetingStore.localPeer!.role.permissions.changeRole ?? false; + _meetingStore.localPeer?.role.permissions.changeRole ?? false; return InkWell( onLongPress: () { diff --git a/example/lib/common/ui/organisms/brb_tag.dart b/example/lib/common/ui/organisms/brb_tag.dart index 029493e75..8f65428a5 100644 --- a/example/lib/common/ui/organisms/brb_tag.dart +++ b/example/lib/common/ui/organisms/brb_tag.dart @@ -21,8 +21,9 @@ class BRBTag extends StatelessWidget { width: 35, ), ), - top: 10.0, - right: 5.0, + top: 30.0, + right: 0, + left: 0, ) : Container(); }, diff --git a/example/lib/common/ui/organisms/chat_bottom_sheet.dart b/example/lib/common/ui/organisms/chat_bottom_sheet.dart index b9027ee1b..5b762c17f 100644 --- a/example/lib/common/ui/organisms/chat_bottom_sheet.dart +++ b/example/lib/common/ui/organisms/chat_bottom_sheet.dart @@ -2,7 +2,10 @@ import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; +import 'package:flutter/scheduler.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:hmssdk_flutter_example/common/ui/organisms/receive_message.dart'; +import 'package:hmssdk_flutter_example/common/ui/organisms/send_message.dart'; import 'package:hmssdk_flutter_example/common/util/app_color.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; @@ -25,16 +28,45 @@ class _ChatWidgetState extends State { late double widthOfScreen; TextEditingController messageTextController = TextEditingController(); String valueChoose = "Everyone"; + final ScrollController _scrollController = ScrollController(); @override void initState() { super.initState(); } + void _scrollDown() { + SchedulerBinding.instance!.addPostFrameCallback((_) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent + 20, + duration: Duration(seconds: 1), + curve: Curves.fastOutSlowIn, + ); + }); + } + + @override + void dispose() { + messageTextController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + String sender(HMSMessageRecipient hmsMessageRecipient) { + if ((hmsMessageRecipient.recipientPeer != null) && + (hmsMessageRecipient.recipientRoles == null)) { + return hmsMessageRecipient.recipientPeer?.name ?? ""; + } else if ((hmsMessageRecipient.recipientPeer == null) && + (hmsMessageRecipient.recipientRoles != null)) { + return hmsMessageRecipient.recipientRoles![0].name; + } + return ""; + } + @override Widget build(BuildContext context) { widthOfScreen = MediaQuery.of(context).size.width; - final DateFormat formatter = DateFormat('yyyy-MM-dd hh:mm a'); + final DateFormat formatter = DateFormat('hh:mm a'); return FractionallySizedBox( heightFactor: 0.8, child: Container( @@ -43,120 +75,109 @@ class _ChatWidgetState extends State { child: Center( child: Container( child: Column( + crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Container( - padding: - EdgeInsets.symmetric(vertical: 5.0, horizontal: 10), - color: Colors.blue, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Row( - children: [ - Icon(Icons.people_alt_outlined), - SizedBox( - width: 5, - ), - Selector, List>>( - selector: (_, meetingStore) => Tuple2( - meetingStore.roles, meetingStore.peers), - builder: (context, data, _) { - List roles = data.item1; - if (roles.length > 0) { - return DropdownButtonHideUnderline( - child: DropdownButton2( - buttonWidth: 120, - value: valueChoose, - iconEnabledColor: iconColor, - onChanged: (newvalue) { - setState(() { - this.valueChoose = - newvalue as String; - }); - }, - items: [ - DropdownMenuItem( - child: Container( - width: 90, - child: Text( - "Everyone", - style: GoogleFonts.inter(), - overflow: TextOverflow.clip, - )), - value: "Everyone", - ), - ...data.item2 - .map((peer) { - return !peer.isLocal - ? DropdownMenuItem( - child: Container( - width: 90, - child: Text( - "${peer.name} ${peer.isLocal ? "(You)" : ""}", - style: GoogleFonts - .inter( - color: - iconColor), - overflow: - TextOverflow - .clip, - ), - ), - value: peer.peerId, - ) - : null; - }) - .whereNotNull() - .toList(), - ...roles - .map((role) => - DropdownMenuItem( - child: Container( - width: 90, - child: Text( - "${role.name}", - overflow: - TextOverflow.clip, - style: - GoogleFonts.inter( - color: - iconColor), - )), - value: role.name, - )) - .toList() - ], - ), - ); - } else - return CircularProgressIndicator( - color: Colors.white, - ); - }), - ], + Icon(Icons.people_alt_outlined), + SizedBox( + width: 5, ), - GestureDetector( - onTap: () { - Navigator.of(context).pop(); - }, - child: Icon( - Icons.clear, - size: 25.0, - ), - ) + Selector, List>>( + selector: (_, meetingStore) => + Tuple2(meetingStore.roles, meetingStore.peers), + builder: (context, data, _) { + List roles = data.item1; + if (roles.length > 0) { + return DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + dropdownWidth: + MediaQuery.of(context).size.width * 0.6, + buttonWidth: 120, + value: valueChoose, + offset: Offset( + -1 * + (MediaQuery.of(context).size.width * + 0.2), + 0), + iconEnabledColor: iconColor, + selectedItemHighlightColor: Colors.blue, + onChanged: (newvalue) { + setState(() { + this.valueChoose = newvalue as String; + }); + }, + items: [ + DropdownMenuItem( + child: Text( + "Everyone", + style: GoogleFonts.inter(), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + value: "Everyone", + ), + ...roles + .sortedBy((element) => + element.priority.toString()) + .map((role) => + DropdownMenuItem( + child: Text( + "${role.name}", + overflow: + TextOverflow.ellipsis, + maxLines: 1, + style: GoogleFonts.inter( + color: iconColor), + ), + value: role.name, + )) + .toList(), + ...data.item2 + .sortedBy((element) => element.name) + .map((peer) { + return !peer.isLocal + ? DropdownMenuItem( + child: Text( + "${peer.name} ${peer.isLocal ? "(You)" : ""}", + style: GoogleFonts.inter( + color: iconColor), + overflow: + TextOverflow.ellipsis, + maxLines: 1, + ), + value: peer.peerId, + ) + : null; + }) + .whereNotNull() + .toList(), + ], + ), + ); + } else + return CircularProgressIndicator( + color: Colors.white, + ); + }), ], ), ), + Divider( + height: 5, + color: Colors.grey, + ), Expanded( child: Selector, int>>( selector: (_, meetingStore) => Tuple2( meetingStore.messages, meetingStore.messages.length), builder: (context, data, _) { - // if (!_meetingStore.isMeetingStarted) return SizedBox(); - if (data.item2 == 0) return Center( child: Text( @@ -165,90 +186,52 @@ class _ChatWidgetState extends State { )); return ListView( + controller: _scrollController, children: List.generate( data.item2, (index) => Container( - padding: EdgeInsets.symmetric( - vertical: 3, horizontal: 10), - margin: EdgeInsets.symmetric(vertical: 3), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - Expanded( - child: Text( + margin: EdgeInsets.symmetric(vertical: 2), + child: data.item1[index].sender?.isLocal ?? false + ? SendMessageScreen( + senderName: data.item1[index].sender?.name ?? "", - style: GoogleFonts.inter( - fontSize: 14.0, - color: iconColor, - fontWeight: FontWeight.w600), - ), - ), - Text( - formatter - .format(data.item1[index].time), - style: GoogleFonts.inter( - fontSize: 10.0, - color: iconColor, - fontWeight: FontWeight.w900), - ) - ], - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Flexible( - child: Text( + date: formatter + .format(data.item1[index].time), + message: data.item1[index].message.toString(), - style: GoogleFonts.inter( - fontSize: 14.0, - color: iconColor, - fontWeight: FontWeight.w700), - ), - ), - Text( - HMSMessageRecipientValues - .getValueFromHMSMessageRecipientType( - data - .item1[index] - .hmsMessageRecipient! - .hmsMessageRecipientType) - .toLowerCase(), - style: GoogleFonts.inter( - fontSize: 14.0, - color: Colors.blue, - fontWeight: FontWeight.w500), - ), - ], - ), - ], - ), - decoration: BoxDecoration( - border: Border( - left: BorderSide( - color: Colors.blue, - width: 5, - ), - )), + role: data.item1[index].hmsMessageRecipient == + null + ? "" + : sender(data.item1[index] + .hmsMessageRecipient!)) + : ReceiveMessageScreen( + message: + data.item1[index].message.toString(), + senderName: + data.item1[index].sender?.name ?? "", + date: formatter + .format(data.item1[index].time), + role: data.item1[index] + .hmsMessageRecipient == + null + ? "" + : sender(data.item1[index].hmsMessageRecipient!)), ), ), ); }, ), ), + Divider( + height: 5, + color: Colors.grey, + ), Container( - color: Colors.grey[700], child: Row( children: [ Container( margin: EdgeInsets.only(bottom: 5.0, left: 5.0), child: TextField( - autofocus: true, style: GoogleFonts.inter(color: iconColor), controller: messageTextController, decoration: new InputDecoration( @@ -262,7 +245,7 @@ class _ChatWidgetState extends State { left: 15, bottom: 11, top: 11, right: 15), hintText: "Type a Message"), ), - width: widthOfScreen - 45.0, + width: widthOfScreen - 60, ), GestureDetector( onTap: () async { @@ -290,10 +273,16 @@ class _ChatWidgetState extends State { .sendDirectMessage(message, peer!); } messageTextController.clear(); + _scrollDown(); }, - child: Icon( - Icons.send, - color: Colors.grey[300], + child: CircleAvatar( + radius: 20, + backgroundColor: Colors.blue, + child: Icon( + Icons.send, + color: Colors.white, + size: 20, + ), ), ) ], @@ -311,6 +300,9 @@ void chatMessages(BuildContext context) { MeetingStore meetingStore = context.read(); showModalBottomSheet( context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), builder: (ctx) => ChangeNotifierProvider.value( value: meetingStore, child: ChatWidget(meetingStore)), isScrollControlled: true); diff --git a/example/lib/common/ui/organisms/chat_shape.dart b/example/lib/common/ui/organisms/chat_shape.dart new file mode 100644 index 000000000..e74ceee4c --- /dev/null +++ b/example/lib/common/ui/organisms/chat_shape.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class CustomShape extends CustomPainter { + final Color bgColor; + + CustomShape(this.bgColor); + + @override + void paint(Canvas canvas, Size size) { + var paint = Paint()..color = bgColor; + + var path = Path(); + path.lineTo(-5, 0); + path.lineTo(0, 10); + path.lineTo(5, 0); + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return false; + } +} diff --git a/example/lib/common/ui/organisms/full_screen_view.dart b/example/lib/common/ui/organisms/full_screen_view.dart index e9871b702..6d157b8d4 100644 --- a/example/lib/common/ui/organisms/full_screen_view.dart +++ b/example/lib/common/ui/organisms/full_screen_view.dart @@ -18,6 +18,7 @@ Widget fullScreenView( required int itemCount, required int screenShareCount, required BuildContext context, + required bool isPortrait, required Size size}) { return GridView.builder( shrinkWrap: true, @@ -78,5 +79,5 @@ Widget fullScreenView( controller: Provider.of(context).controller, gridDelegate: SliverStairedGridDelegate( startCrossAxisDirectionReversed: false, - pattern: [StairedGridTile(1, Utilities.getRatio(size))])); + pattern: [StairedGridTile(1, Utilities.getRatio(size, context))])); } diff --git a/example/lib/common/ui/organisms/grid_audio_view.dart b/example/lib/common/ui/organisms/grid_audio_view.dart index fb4b0931d..d9d36de0b 100644 --- a/example/lib/common/ui/organisms/grid_audio_view.dart +++ b/example/lib/common/ui/organisms/grid_audio_view.dart @@ -12,6 +12,8 @@ import 'package:hmssdk_flutter_example/meeting/peer_track_node.dart'; Widget gridAudioView( {required List peerTracks, required int itemCount, + required bool isPortrait, + required BuildContext context, required Size size}) { return GridView.builder( itemCount: itemCount, @@ -27,14 +29,17 @@ Widget gridAudioView( }, gridDelegate: SliverStairedGridDelegate( startCrossAxisDirectionReversed: true, - pattern: pattern(itemCount, size)), + pattern: isPortrait + ? portraitPattern(itemCount, size, context) + : landscapePattern(itemCount, size, context)), physics: PageScrollPhysics(), scrollDirection: Axis.horizontal, ); } -List pattern(int itemCount, Size size) { - double ratio = Utilities.getRatio(size); +List portraitPattern( + int itemCount, Size size, BuildContext context) { + double ratio = Utilities.getRatio(size, context); List tiles = []; int gridView = itemCount ~/ 6; @@ -65,3 +70,16 @@ List pattern(int itemCount, Size size) { } return tiles; } + +List landscapePattern( + int itemCount, Size size, BuildContext context) { + double ratio = Utilities.getRatio(size, context); + List tiles = []; + int gridView = itemCount ~/ 2; + int tileLeft = itemCount - (gridView * 2); + for (int i = 0; i < (itemCount - tileLeft); i++) { + tiles.add(StairedGridTile(1, ratio / 0.5)); + } + if (tileLeft == 1) tiles.add(StairedGridTile(1, ratio)); + return tiles; +} diff --git a/example/lib/common/ui/organisms/grid_hero_view.dart b/example/lib/common/ui/organisms/grid_hero_view.dart index 1f1f0c038..893a6e0e7 100644 --- a/example/lib/common/ui/organisms/grid_hero_view.dart +++ b/example/lib/common/ui/organisms/grid_hero_view.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hmssdk_flutter_example/common/util/app_color.dart'; +import 'package:hmssdk_flutter_example/common/util/utility_function.dart'; import 'package:provider/provider.dart'; //Project imports @@ -17,12 +18,14 @@ Widget gridHeroView( required int itemCount, required int screenShareCount, required BuildContext context, + required bool isPortrait, required Size size}) { return GridView.builder( shrinkWrap: true, cacheExtent: 600, physics: PageScrollPhysics(), itemCount: itemCount, + scrollDirection: isPortrait ? Axis.vertical : Axis.horizontal, itemBuilder: (context, index) { if (peerTracks[index].track?.source != "REGULAR") { return ChangeNotifierProvider.value( @@ -68,11 +71,14 @@ Widget gridHeroView( controller: Provider.of(context).controller, gridDelegate: SliverStairedGridDelegate( startCrossAxisDirectionReversed: false, - pattern: pattern(itemCount, screenShareCount, size))); + pattern: isPortrait + ? portraitPattern(itemCount, screenShareCount, size, context) + : landscapePattern(itemCount, screenShareCount, size, context))); } -List pattern(int itemCount, int screenShareCount, Size size) { - double ratio = (size.width) / (size.height * 0.82); +List portraitPattern( + int itemCount, int screenShareCount, Size size, BuildContext context) { + double ratio = 1 / Utilities.getRatio(size, context); List tiles = []; for (int i = 0; i < screenShareCount; i++) { @@ -104,3 +110,29 @@ List pattern(int itemCount, int screenShareCount, Size size) { } return tiles; } + +List landscapePattern( + int itemCount, int screenShareCount, Size size, BuildContext context) { + double ratio = Utilities.getRatio(size, context); + List tiles = []; + for (int i = 0; i < screenShareCount; i++) { + tiles.add(StairedGridTile(1, ratio)); + } + int normalTile = (itemCount - screenShareCount); + if (normalTile == 1) { + tiles.add(StairedGridTile(1, ratio)); + } else { + tiles.add(StairedGridTile(1, ratio / 0.8)); + tiles.add(StairedGridTile(0.33, ratio / 0.6)); + tiles.add(StairedGridTile(0.33, ratio / 0.6)); + tiles.add(StairedGridTile(0.33, ratio / 0.6)); + } + int gridView = normalTile ~/ 2; + int tileLeft = normalTile - (gridView * 2); + for (int i = 0; i < (normalTile - tileLeft - 4); i++) { + tiles.add(StairedGridTile(1, ratio / 0.5)); + } + if (tileLeft == 1) tiles.add(StairedGridTile(1, ratio)); + + return tiles; +} diff --git a/example/lib/common/ui/organisms/grid_video_view.dart b/example/lib/common/ui/organisms/grid_video_view.dart index 52a8691c1..04f13a898 100644 --- a/example/lib/common/ui/organisms/grid_video_view.dart +++ b/example/lib/common/ui/organisms/grid_video_view.dart @@ -18,6 +18,7 @@ Widget gridVideoView( required int itemCount, required int screenShareCount, required BuildContext context, + required bool isPortrait, required Size size}) { return GridView.builder( shrinkWrap: true, @@ -78,11 +79,14 @@ Widget gridVideoView( controller: Provider.of(context).controller, gridDelegate: SliverStairedGridDelegate( startCrossAxisDirectionReversed: false, - pattern: pattern(itemCount, screenShareCount, size))); + pattern: isPortrait + ? portraitPattern(itemCount, screenShareCount, size, context) + : landscapePattern(itemCount, screenShareCount, size, context))); } -List pattern(int itemCount, int screenShareCount, Size size) { - double ratio = Utilities.getRatio(size); +List portraitPattern( + int itemCount, int screenShareCount, Size size, BuildContext context) { + double ratio = Utilities.getRatio(size, context); List tiles = []; for (int i = 0; i < screenShareCount; i++) { tiles.add(StairedGridTile(1, ratio)); @@ -105,3 +109,21 @@ List pattern(int itemCount, int screenShareCount, Size size) { } return tiles; } + +List landscapePattern( + int itemCount, int screenShareCount, Size size, BuildContext context) { + double ratio = Utilities.getRatio(size, context); + List tiles = []; + for (int i = 0; i < screenShareCount; i++) { + tiles.add(StairedGridTile(1, ratio)); + } + int normalTile = (itemCount - screenShareCount); + int gridView = normalTile ~/ 2; + int tileLeft = normalTile - (gridView * 2); + for (int i = 0; i < (normalTile - tileLeft); i++) { + tiles.add(StairedGridTile(1, ratio / 0.5)); + } + if (tileLeft == 1) tiles.add(StairedGridTile(1, ratio)); + + return tiles; +} diff --git a/example/lib/common/ui/organisms/hand_raise.dart b/example/lib/common/ui/organisms/hand_raise.dart index fb222d991..5588608fd 100644 --- a/example/lib/common/ui/organisms/hand_raise.dart +++ b/example/lib/common/ui/organisms/hand_raise.dart @@ -21,8 +21,9 @@ class HandRaise extends StatelessWidget { height: 35, ), ), - bottom: 5.0, - left: 5.0, + top: 30.0, + right: 0, + left: 0, ) : Container(); }, diff --git a/example/lib/common/ui/organisms/peer_name.dart b/example/lib/common/ui/organisms/peer_name.dart index 6757c71a1..952d5bf1d 100644 --- a/example/lib/common/ui/organisms/peer_name.dart +++ b/example/lib/common/ui/organisms/peer_name.dart @@ -23,9 +23,10 @@ class _PeerNameState extends State { peerTrackNode.peer.isLocal), builder: (_, data, __) { return Align( - alignment: Alignment.bottomCenter, + alignment: Alignment.topCenter, child: Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), + padding: + const EdgeInsets.symmetric(vertical: 10.0, horizontal: 35), child: Text( "${data.item3 ? "You (" : ""}${data.item1}${data.item3 ? ")" : ""} ${data.item2 ? " Degraded" : ""}", maxLines: 1, diff --git a/example/lib/common/ui/organisms/receive_message.dart b/example/lib/common/ui/organisms/receive_message.dart new file mode 100644 index 000000000..d5dde6ce8 --- /dev/null +++ b/example/lib/common/ui/organisms/receive_message.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:hmssdk_flutter_example/common/ui/organisms/chat_shape.dart'; +import "dart:math" show pi; + +class ReceiveMessageScreen extends StatelessWidget { + final String message; + final String? senderName; + final String date; + final String role; + const ReceiveMessageScreen({ + Key? key, + required this.message, + required this.senderName, + required this.date, + required this.role, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(right: 50.0, left: 10, top: 15, bottom: 5), + child: Stack( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(pi), + child: CustomPaint( + painter: CustomShape(Colors.blue.shade300), + ), + ), + Flexible( + child: Container( + padding: + EdgeInsets.only(bottom: 14, left: 14, right: 14, top: 5), + decoration: BoxDecoration( + color: Colors.blue.shade300, + borderRadius: BorderRadius.only( + topRight: Radius.circular(18), + bottomLeft: Radius.circular(18), + bottomRight: Radius.circular(18), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + (senderName ?? "") + + (role == "" ? "" : " (to " + role + ")"), + style: GoogleFonts.inter( + fontSize: 14.0, + color: Colors.black, + fontWeight: FontWeight.w600), + ), + SizedBox( + height: 2, + ), + Text( + message, + style: GoogleFonts.inter( + fontSize: 14.0, + color: Colors.white, + fontWeight: FontWeight.w700), + ), + ], + ), + ), + ), + ], + ), + Positioned( + bottom: 2, + left: 15, + child: Text( + date, + style: GoogleFonts.inter( + fontSize: 10.0, + color: Colors.black, + fontWeight: FontWeight.w900), + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/common/ui/organisms/send_message.dart b/example/lib/common/ui/organisms/send_message.dart new file mode 100644 index 000000000..59667e9ea --- /dev/null +++ b/example/lib/common/ui/organisms/send_message.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:hmssdk_flutter_example/common/ui/organisms/chat_shape.dart'; + +class SendMessageScreen extends StatelessWidget { + final String message; + final String? senderName; + final String date; + final String role; + const SendMessageScreen({ + Key? key, + required this.message, + required this.senderName, + required this.date, + required this.role, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(right: 10.0, left: 50, top: 15, bottom: 5), + child: Stack( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: 100), + child: Container( + padding: EdgeInsets.only( + bottom: 14, left: 14, right: 14, top: 5), + decoration: BoxDecoration( + color: Colors.green.shade300, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(18), + bottomLeft: Radius.circular(18), + bottomRight: Radius.circular(18), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + (senderName ?? "") + + (role == "" ? "" : " (to " + role + ")"), + style: GoogleFonts.inter( + fontSize: 14.0, + color: Colors.black, + fontWeight: FontWeight.w600), + ), + SizedBox( + height: 2, + ), + Text( + message, + style: GoogleFonts.inter( + fontSize: 14.0, + color: Colors.white, + fontWeight: FontWeight.w700), + ), + ], + ), + ), + ), + ), + CustomPaint( + painter: CustomShape( + Colors.green.shade300, + )), + ], + ), + Positioned( + bottom: 2, + right: 15, + child: Text( + date, + style: GoogleFonts.inter( + fontSize: 10.0, + color: Colors.black, + fontWeight: FontWeight.w900), + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/common/ui/organisms/title_bar.dart b/example/lib/common/ui/organisms/title_bar.dart index 8ffdb9956..6b653c24e 100644 --- a/example/lib/common/ui/organisms/title_bar.dart +++ b/example/lib/common/ui/organisms/title_bar.dart @@ -20,7 +20,7 @@ class TitleBar extends StatelessWidget { builder: (_, speakerName, __) { return (speakerName != null) ? Container( - width: 164, + width: MediaQuery.of(context).size.width * 0.7, child: Text("🔊 $speakerName", overflow: TextOverflow.clip, style: GoogleFonts.inter())) diff --git a/example/lib/common/ui/organisms/user_name_dialog_organism.dart b/example/lib/common/ui/organisms/user_name_dialog_organism.dart index 691bdb18a..69f25e268 100644 --- a/example/lib/common/ui/organisms/user_name_dialog_organism.dart +++ b/example/lib/common/ui/organisms/user_name_dialog_organism.dart @@ -16,25 +16,18 @@ class _UserNameDialogOrganismState extends State { @override Widget build(BuildContext context) { return AlertDialog( - content: Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - autofocus: true, - controller: userNameController, - style: GoogleFonts.inter(), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(16)), - ), - hintText: 'Enter your Name', - hintStyle: GoogleFonts.inter( - color: iconColor, - )), + content: TextField( + autofocus: true, + controller: userNameController, + style: GoogleFonts.inter(), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(16)), ), - ], - ), + hintText: 'Enter your Name', + hintStyle: GoogleFonts.inter( + color: iconColor, + )), ), actions: [ ElevatedButton( diff --git a/example/lib/common/ui/organisms/video_tile.dart b/example/lib/common/ui/organisms/video_tile.dart index 04c58b8e9..94a66f7f7 100644 --- a/example/lib/common/ui/organisms/video_tile.dart +++ b/example/lib/common/ui/organisms/video_tile.dart @@ -52,7 +52,7 @@ class _VideoTileState extends State { bool removePeerPermission = _meetingStore.localPeer?.role.permissions.removeOthers ?? false; bool changeRolePermission = - _meetingStore.localPeer!.role.permissions.changeRole ?? false; + _meetingStore.localPeer?.role.permissions.changeRole ?? false; return FocusDetector( onFocusLost: () { @@ -214,6 +214,11 @@ class _VideoTileState extends State { scaleType: widget.scaleType, ), PeerName(), + RTCStatsView(isLocal: false), + Align( + alignment: Alignment.topRight, + child: UtilityComponents.rotateScreen(context), + ) ], ), ), diff --git a/example/lib/common/util/app_color.dart b/example/lib/common/util/app_color.dart index 876d3395d..ab586e30b 100644 --- a/example/lib/common/util/app_color.dart +++ b/example/lib/common/util/app_color.dart @@ -1,10 +1,20 @@ import 'package:flutter/material.dart'; Color iconColor = - WidgetsBinding.instance?.window.platformBrightness == Brightness.dark - ? Colors.white - : Colors.black; + // WidgetsBinding.instance?.window.platformBrightness == Brightness.dark + // ? + Colors.white; +// : Colors.black; void updateColor(ThemeMode mode) { iconColor = mode == ThemeMode.dark ? Colors.white : Colors.black; } + +Color defaultColor = Color.fromRGBO(245, 249, 255, 0.95); +Color subHeadingColor = Color.fromRGBO(224, 236, 255, 0.8); +Color hintColor = Color.fromRGBO(195, 208, 229, 0.5); +Color surfaceColor = Color.fromRGBO(29, 34, 41, 1); +Color disabledTextColor = Color.fromRGBO(255, 255, 255, 0.48); +Color enabledTextColor = Color.fromRGBO(255, 255, 255, 0.98); +Color borderColor = Color.fromRGBO(45, 52, 64, 1); +Color dividerColor = Color.fromRGBO(27, 31, 38, 1); diff --git a/example/lib/common/util/utility_components.dart b/example/lib/common/util/utility_components.dart index b95ecc985..e5da0b617 100644 --- a/example/lib/common/util/utility_components.dart +++ b/example/lib/common/util/utility_components.dart @@ -1,7 +1,9 @@ //Package imports import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:hmssdk_flutter_example/common/constant.dart'; import 'package:hmssdk_flutter_example/common/util/app_color.dart'; import 'package:hmssdk_flutter_example/enum/meeting_mode.dart'; import 'package:provider/provider.dart'; @@ -169,6 +171,107 @@ class UtilityComponents { return answer; } + static showHLSDialog({required BuildContext context}) async { + TextEditingController textController = TextEditingController(); + textController.text = Constant.rtmpUrl; + bool isSingleFileChecked = false, isVODChecked = false; + MeetingStore _meetingStore = context.read(); + await showDialog( + context: context, + builder: (context) => StatefulBuilder(builder: (context, setState) { + return AlertDialog( + content: Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + autofocus: true, + controller: textController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(16)), + ), + hintText: "Enter HLS Url"), + ), + SizedBox( + height: 10, + ), + Text("Recording"), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Single file per layer", + style: GoogleFonts.inter( + color: iconColor, + ), + ), + Checkbox( + value: isSingleFileChecked, + activeColor: Colors.blue, + onChanged: (bool? value) { + if (value != null) { + isSingleFileChecked = value; + setState(() {}); + } + }), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Video on Demand", + style: GoogleFonts.inter( + color: iconColor, + ), + ), + Checkbox( + value: isVODChecked, + activeColor: Colors.blue, + onChanged: (bool? value) { + if (value != null) { + isVODChecked = value; + setState(() {}); + } + }), + ], + ) + ], + ), + ), + actions: [ + ElevatedButton( + child: Text( + 'Cancel', + style: GoogleFonts.inter(), + ), + onPressed: () { + Navigator.pop(context, ''); + }, + ), + ElevatedButton( + child: Text( + 'OK', + style: GoogleFonts.inter(), + ), + onPressed: () { + if (textController.text == "") { + } else { + _meetingStore.startHLSStreaming( + textController.text.trim(), + isSingleFileChecked, + isVODChecked); + Navigator.pop(context); + } + }, + ), + ], + ); + })); + } + static showRoleList(BuildContext context, List roles, MeetingStore _meetingStore) async { List _selectedRoles = []; @@ -274,7 +377,7 @@ class UtilityComponents { })); } - static Future> showRTMPInputDialog( + static Future> showRTMPInputDialog( {context, String placeholder = "", String prefilledValue = "", @@ -283,7 +386,7 @@ class UtilityComponents { if (prefilledValue.isNotEmpty) { textController.text = prefilledValue; } - Map answer = await showDialog( + Map answer = await showDialog( context: context, builder: (context) => StatefulBuilder(builder: (context, setState) { return AlertDialog( @@ -326,7 +429,7 @@ class UtilityComponents { style: GoogleFonts.inter(), ), onPressed: () { - Navigator.pop(context, {"url": "", "toRecord": "false"}); + Navigator.pop(context, {"url": "", "toRecord": false}); }, ), ElevatedButton( @@ -335,11 +438,10 @@ class UtilityComponents { style: GoogleFonts.inter(), ), onPressed: () { - if (textController.text == "" && !isRecordingEnabled) { - } else { + if (textController.text != "") { Navigator.pop(context, { "url": textController.text, - "toRecord": isRecordingEnabled.toString() + "toRecord": isRecordingEnabled }); } }, @@ -384,4 +486,50 @@ class UtilityComponents { ), ); } + + static Widget rotateScreen(BuildContext context) { + MeetingStore _meetingStore = Provider.of(context); + return GestureDetector( + onTap: () { + if (_meetingStore.isLandscapeLocked) + _meetingStore.setLandscapeLock(false); + else { + _meetingStore.setLandscapeLock(true); + } + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + "assets/icons/rotate.svg", + color: _meetingStore.isLandscapeLocked ? Colors.blue : iconColor, + ), + ), + ); + } + + static showErrorDialog(BuildContext context) { + return showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Wrong Meeting Url", + style: GoogleFonts.inter( + color: Colors.red.shade300, + fontSize: 16, + fontWeight: FontWeight.w600), + ), + ], + ), + content: Text("Please enter a valid meeting URL", + style: GoogleFonts.inter( + color: defaultColor, + fontSize: 14, + fontWeight: FontWeight.w400)), + ); + }); + } } diff --git a/example/lib/common/util/utility_function.dart b/example/lib/common/util/utility_function.dart index 756f17ab1..bae870ce1 100644 --- a/example/lib/common/util/utility_function.dart +++ b/example/lib/common/util/utility_function.dart @@ -1,9 +1,15 @@ //Package imports +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hmssdk_flutter_example/common/constant.dart'; +import 'package:hmssdk_flutter_example/enum/meeting_flow.dart'; +import 'package:permission_handler/permission_handler.dart'; class Utilities { static String getAvatarTitle(String name) { - List? parts = name.split(" "); + List? parts = name.trim().split(" "); if (parts.length == 1) { name = parts[0][0]; } else if (parts.length >= 2) { @@ -30,7 +36,67 @@ class Utilities { Color(0xFF8FF5FB) ]; - static double getRatio(Size size) { - return (size.height * 0.82) / (size.width); + static double getRatio(Size size, BuildContext context) { + EdgeInsets viewPadding = MediaQuery.of(context).viewPadding; + return (size.height - + viewPadding.top - + viewPadding.bottom - + kToolbarHeight) / + (size.width - viewPadding.left - viewPadding.right); + } + + static void setRTMPUrl(String roomUrl) { + List urlSplit = roomUrl.split('/'); + int index = urlSplit.lastIndexOf("meeting"); + if (index != -1) { + urlSplit[index] = "preview"; + } + Constant.rtmpUrl = urlSplit.join('/') + "?token=beam_recording"; + } + + static Future getPermissions() async { + if (Platform.isIOS) return true; + await Permission.camera.request(); + await Permission.microphone.request(); + await Permission.bluetoothConnect.request(); + + while ((await Permission.camera.isDenied)) { + await Permission.camera.request(); + } + while ((await Permission.microphone.isDenied)) { + await Permission.microphone.request(); + } + while ((await Permission.bluetoothConnect.isDenied)) { + await Permission.bluetoothConnect.request(); + } + return true; + } + + static Future getCameraPermissions() async { + if (Platform.isIOS) return true; + await Permission.camera.request(); + + while ((await Permission.camera.isDenied)) { + await Permission.camera.request(); + } + + return true; + } + + static MeetingFlow deriveFlow(String roomUrl) { + final joinFlowRegex = RegExp("\.100ms\.live\/(preview|meeting)\/"); + final hlsFlowRegex = RegExp("\.100ms\.live\/hls-streaming\/"); + + if (joinFlowRegex.hasMatch(roomUrl)) { + return MeetingFlow.join; + } else if (hlsFlowRegex.hasMatch(roomUrl)) { + return MeetingFlow.hlsStreaming; + } else { + return MeetingFlow.none; + } + } + + static void showToast(String message) { + Fluttertoast.showToast(msg: message, backgroundColor: Colors.black87); } } diff --git a/example/lib/enum/meeting_flow.dart b/example/lib/enum/meeting_flow.dart index c49fa8700..729801c1d 100644 --- a/example/lib/enum/meeting_flow.dart +++ b/example/lib/enum/meeting_flow.dart @@ -1,2 +1,28 @@ //enum to set the meeting flow -enum MeetingFlow { join } +enum MeetingFlow { join, hlsStreaming, none } + +extension MeetingFlowValues on MeetingFlow { + static MeetingFlow getMeetingFlowfromName(String name) { + switch (name) { + case 'meeting': + return MeetingFlow.join; + case 'preview': + return MeetingFlow.join; + case 'hls-streaming': + return MeetingFlow.hlsStreaming; + default: + return MeetingFlow.none; + } + } + + static String getNameFromMeetingFlow(MeetingFlow flow) { + switch (flow) { + case MeetingFlow.join: + return 'meeting'; + case MeetingFlow.hlsStreaming: + return 'hls-streaming'; + case MeetingFlow.none: + return 'none'; + } + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 135bda5f7..0aa234602 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,5 @@ //Dart imports import 'dart:async'; -import 'dart:io'; //Package imports import 'package:firebase_core/firebase_core.dart'; @@ -9,7 +8,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:hmssdk_flutter_example/common/util/app_color.dart'; -import 'package:permission_handler/permission_handler.dart'; +import 'package:hmssdk_flutter_example/common/util/utility_function.dart'; +import 'package:hmssdk_flutter_example/qr_code_screen.dart'; import 'package:provider/provider.dart'; import 'package:wakelock/wakelock.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -45,9 +45,10 @@ class HMSExampleApp extends StatefulWidget { } class _HMSExampleAppState extends State { - ThemeMode _themeMode = ThemeMode.system; + ThemeMode _themeMode = ThemeMode.dark; bool isDarkMode = WidgetsBinding.instance?.window.platformBrightness == Brightness.dark; + ThemeData _darkTheme = ThemeData( brightness: Brightness.dark, primaryColor: Color.fromARGB(255, 13, 107, 184), @@ -64,6 +65,8 @@ class _HMSExampleAppState extends State { @override Widget build(BuildContext context) { + print("Current Mode is $isDarkMode"); + print("${WidgetsBinding.instance?.window.platformBrightness}"); return MaterialApp( home: HomePage(), theme: _lightTheme, @@ -104,24 +107,6 @@ class _HomePageState extends State { buildSignature: 'Unknown', ); - Future getPermissions() async { - if (Platform.isIOS) return true; - await Permission.camera.request(); - await Permission.microphone.request(); - await Permission.bluetoothConnect.request(); - - while ((await Permission.camera.isDenied)) { - await Permission.camera.request(); - } - while ((await Permission.microphone.isDenied)) { - await Permission.microphone.request(); - } - while ((await Permission.bluetoothConnect.isDenied)) { - await Permission.bluetoothConnect.request(); - } - return true; - } - @override void initState() { super.initState(); @@ -141,15 +126,6 @@ class _HomePageState extends State { }); } - void setRTMPUrl(String roomUrl) { - List urlSplit = roomUrl.split('/'); - int index = urlSplit.lastIndexOf("meeting"); - if (index != -1) { - urlSplit[index] = "preview"; - } - Constant.rtmpUrl = urlSplit.join('/') + "?token=beam_recording"; - } - void handleClick(int value) { switch (value) { case 1: @@ -169,243 +145,358 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { bool isDarkMode = HMSExampleApp.of(context).isDarkMode; + double width = MediaQuery.of(context).size.width; + double height = MediaQuery.of(context).size.height; + return WillPopScope( onWillPop: _closeApp, - child: Scaffold( - appBar: AppBar( - backgroundColor: isDarkMode ? Colors.black : Colors.white, - elevation: 0, - title: Text( - '100ms', - style: GoogleFonts.inter(color: iconColor), - ), - actions: [ - IconButton( - onPressed: () { - if (isDarkMode) { - HMSExampleApp.of(context).changeTheme(ThemeMode.light); - } else { - HMSExampleApp.of(context).changeTheme(ThemeMode.dark); - } - }, - icon: isDarkMode - ? SvgPicture.asset( - 'assets/icons/light_mode.svg', - color: iconColor, - ) - : SvgPicture.asset( - 'assets/icons/dark_mode.svg', - color: iconColor, - )), - PopupMenuButton( - onSelected: handleClick, - icon: SvgPicture.asset( - 'assets/icons/settings.svg', - color: iconColor, + child: SafeArea( + child: Scaffold( + // appBar: AppBar( + // backgroundColor: isDarkMode ? Colors.black : Colors.white, + // elevation: 0, + // title: Text( + // '100ms', + // style: GoogleFonts.inter(color: iconColor), + // ), + // actions: [ + // IconButton( + // onPressed: () { + // if (isDarkMode) { + // HMSExampleApp.of(context).changeTheme(ThemeMode.light); + // } else { + // HMSExampleApp.of(context).changeTheme(ThemeMode.dark); + // } + // }, + // icon: isDarkMode + // ? SvgPicture.asset( + // 'assets/icons/light_mode.svg', + // color: iconColor, + // ) + // : SvgPicture.asset( + // 'assets/icons/dark_mode.svg', + // color: iconColor, + // )), + // PopupMenuButton( + // onSelected: handleClick, + // icon: SvgPicture.asset( + // 'assets/icons/settings.svg', + // color: iconColor, + // ), + // itemBuilder: (BuildContext context) { + // return [ + // PopupMenuItem( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // if (skipPreview) + // Text("Enable Preview", + // style: GoogleFonts.inter(color: iconColor)) + // else + // Text( + // "Disable Preview", + // style: GoogleFonts.inter(color: Colors.blue), + // ), + // if (skipPreview) + // SvgPicture.asset( + // 'assets/icons/preview_state_on.svg', + // color: iconColor) + // else + // SvgPicture.asset( + // 'assets/icons/preview_state_off.svg', + // color: Colors.blue, + // ), + // ], + // ), + // value: 1, + // ), + // PopupMenuItem( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // if (mirrorCamera) + // Text("Disable Mirroring", + // style: GoogleFonts.inter(color: Colors.blue)) + // else + // Text( + // "Enable Mirroring", + // style: GoogleFonts.inter(color: iconColor), + // ), + // Icon( + // Icons.camera_front, + // color: mirrorCamera ? Colors.blue : iconColor, + // ), + // ], + // ), + // value: 2, + // ), + // PopupMenuItem( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // if (showStats) + // Text("Disable Stats", + // style: GoogleFonts.inter(color: Colors.blue)) + // else + // Text( + // "Enable Stats", + // style: GoogleFonts.inter(color: iconColor), + // ), + // SvgPicture.asset( + // 'assets/icons/stats.svg', + // color: showStats ? Colors.blue : iconColor, + // ), + // ], + // ), + // value: 3, + // ), + // PopupMenuItem( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text("Version ${_packageInfo.version}", + // style: GoogleFonts.inter(color: iconColor)), + // ], + // ), + // value: 4, + // ), + // ]; + // }, + // ), + // ], + // ), + body: Center( + child: SingleChildScrollView( + child: Column( + children: [ + SvgPicture.asset( + 'assets/welcome.svg', + width: width * 0.95, ), - itemBuilder: (BuildContext context) { - return [ - PopupMenuItem( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (skipPreview) - Text("Enable Preview", - style: GoogleFonts.inter(color: iconColor)) - else - Text( - "Disable Preview", - style: GoogleFonts.inter(color: Colors.blue), - ), - if (skipPreview) - SvgPicture.asset( - 'assets/icons/preview_state_on.svg', - color: iconColor) - else - SvgPicture.asset( - 'assets/icons/preview_state_off.svg', - color: Colors.blue, - ), - ], - ), - value: 1, - ), - PopupMenuItem( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (mirrorCamera) - Text("Disable Mirroring", - style: GoogleFonts.inter(color: Colors.blue)) - else - Text( - "Enable Mirroring", - style: GoogleFonts.inter(color: iconColor), - ), - Icon( - Icons.camera_front, - color: mirrorCamera ? Colors.blue : iconColor, - ), - ], - ), - value: 2, - ), - PopupMenuItem( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (showStats) - Text("Disable Stats", - style: GoogleFonts.inter(color: Colors.blue)) - else - Text( - "Enable Stats", - style: GoogleFonts.inter(color: iconColor), - ), - SvgPicture.asset( - 'assets/icons/stats.svg', - color: showStats ? Colors.blue : iconColor, - ), - ], - ), - value: 3, - ), - PopupMenuItem( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("Version ${_packageInfo.version}", - style: GoogleFonts.inter(color: iconColor)), - ], - ), - value: 4, - ), - ]; - }, - ), - ], - ), - body: Center( - child: SingleChildScrollView( - child: Column( - children: [ - Image.asset( - "assets/100ms.gif", - width: 120, - height: 120, - ), - SizedBox( - height: 70, - ), - Text('Join Meeting', + SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: Text('Experience the power of 100ms', + textAlign: TextAlign.center, style: GoogleFonts.inter( - height: 1, - fontSize: 24, - fontWeight: FontWeight.bold)), - SizedBox( - height: 8, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: TextField( - style: GoogleFonts.inter(), - controller: roomIdController, - keyboardType: TextInputType.url, - decoration: InputDecoration( - hintText: 'Enter Room URL', - hintStyle: GoogleFonts.inter(), - suffixIcon: IconButton( - onPressed: roomIdController.clear, - icon: Icon(Icons.clear), - ), - border: OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(16)))), - ), + color: defaultColor, + height: 1.5, + fontSize: 34, + fontWeight: FontWeight.w600)), + ), + SizedBox( + height: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 27), + child: Text( + 'Jump right in by pasting a room link or\n scanning a QR code', + textAlign: TextAlign.center, + style: GoogleFonts.inter( + color: subHeadingColor, + height: 1.5, + fontSize: 16, + fontWeight: FontWeight.w400)), + ), + SizedBox( + height: 15, + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + child: Row( + children: [ + Text("Joining Link", + style: GoogleFonts.inter( + color: defaultColor, + height: 1.5, + fontSize: 14, + fontWeight: FontWeight.w400)), + ], ), - SizedBox( - height: 30, + ), + SizedBox( + width: width * 0.95, + child: TextField( + style: GoogleFonts.inter(), + controller: roomIdController, + keyboardType: TextInputType.url, + decoration: InputDecoration( + contentPadding: + EdgeInsets.symmetric(vertical: 14, horizontal: 16), + fillColor: surfaceColor, + filled: true, + hintText: 'Paste the link here', + hintStyle: GoogleFonts.inter( + color: hintColor, + height: 1.5, + fontSize: 16, + fontWeight: FontWeight.w400), + suffixIcon: IconButton( + onPressed: roomIdController.clear, + icon: Icon(Icons.clear), + ), + enabledBorder: OutlineInputBorder( + borderSide: + BorderSide(color: borderColor, width: 1), + borderRadius: BorderRadius.all(Radius.circular(8))), + border: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(8)))), ), - ElevatedButton( + ), + SizedBox( + height: 16, + ), + SizedBox( + width: width * 0.95, + child: ValueListenableBuilder( + valueListenable: roomIdController, + builder: (context, value, child) { + return ElevatedButton( + style: ButtonStyle( + shadowColor: + MaterialStateProperty.all(surfaceColor), + backgroundColor: roomIdController.text.isEmpty + ? MaterialStateProperty.all(surfaceColor) + : MaterialStateProperty.all(Colors.blue), + shape: MaterialStateProperty.all< + RoundedRectangleBorder>( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () async { + if (roomIdController.text.isEmpty) { + return; + } + Utilities.setRTMPUrl(roomIdController.text); + String user = await showDialog( + context: context, + builder: (_) => UserNameDialogOrganism()); + if (user.isNotEmpty) { + bool res = await Utilities.getPermissions(); + if (res) { + FocusManager.instance.primaryFocus?.unfocus(); + if (skipPreview) { + HMSSDKInteractor _hmsSDKInteractor = + HMSSDKInteractor(); + _hmsSDKInteractor.showStats = showStats; + _hmsSDKInteractor.mirrorCamera = mirrorCamera; + _hmsSDKInteractor.skipPreview = true; + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => ListenableProvider.value( + value: MeetingStore( + hmsSDKInteractor: + _hmsSDKInteractor), + child: MeetingPage( + roomId: + roomIdController.text.trim(), + flow: MeetingFlow.join, + user: user, + isAudioOn: true)))); + } else { + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => ListenableProvider.value( + value: PreviewStore(), + child: PreviewPage( + roomId: + roomIdController.text.trim(), + user: user, + flow: MeetingFlow.join, + mirror: mirrorCamera, + showStats: showStats, + ), + ))); + } + } + } + }, + child: Container( + padding: const EdgeInsets.fromLTRB(8, 16, 8, 16), + decoration: BoxDecoration( + borderRadius: + BorderRadius.all(Radius.circular(8))), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Join Now', + style: GoogleFonts.inter( + color: roomIdController.text.isEmpty + ? disabledTextColor + : enabledTextColor, + height: 1, + fontSize: 16, + fontWeight: FontWeight.w600)), + ], + ), + ), + ); + }), + ), + SizedBox( + height: 20, + ), + SizedBox( + width: width * 0.95, + child: Divider( + height: 5, + color: dividerColor, + )), + SizedBox( + height: 20, + ), + SizedBox( + width: width * 0.95, + child: ElevatedButton( style: ButtonStyle( shadowColor: MaterialStateProperty.all(Colors.blue), shape: MaterialStateProperty.all( RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), + borderRadius: BorderRadius.circular(8.0), ))), onPressed: () async { - if (roomIdController.text.isEmpty) { - return; - } - setRTMPUrl(roomIdController.text); - String user = await showDialog( - context: context, - builder: (_) => UserNameDialogOrganism()); - if (user.isNotEmpty) { - bool res = await getPermissions(); - if (res) { - FocusManager.instance.primaryFocus?.unfocus(); - if (skipPreview) { - HMSSDKInteractor _hmsSDKInteractor = - HMSSDKInteractor(); - _hmsSDKInteractor.showStats = showStats; - _hmsSDKInteractor.mirrorCamera = mirrorCamera; - _hmsSDKInteractor.skipPreview = true; - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => ListenableProvider.value( - value: MeetingStore( - hmsSDKInteractor: _hmsSDKInteractor), - child: MeetingPage( - roomId: roomIdController.text.trim(), - flow: MeetingFlow.join, - user: user, - isAudioOn: true)))); - } else { - Navigator.of(context).push(MaterialPageRoute( - builder: (_) => ListenableProvider.value( - value: PreviewStore(), - child: PreviewPage( - roomId: roomIdController.text.trim(), - user: user, - flow: MeetingFlow.join, - mirror: mirrorCamera, - showStats: showStats, - ), - ))); - } - } + bool res = await Utilities.getCameraPermissions(); + if (res) { + Navigator.push(context, + MaterialPageRoute(builder: (_) => QRCodeScreen())); } }, child: Container( - width: 250, - padding: const EdgeInsets.fromLTRB(4, 10, 4, 10), + padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(12))), + borderRadius: BorderRadius.all(Radius.circular(8))), child: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('Join Meeting', - style: - GoogleFonts.inter(height: 1, fontSize: 20)), + Icon( + Icons.qr_code, + size: 22, + color: enabledTextColor, + ), SizedBox( width: 5, ), - Icon(Icons.arrow_right_alt_outlined, size: 22), + Text('Scan QR Code', + style: GoogleFonts.inter( + height: 1, + fontSize: 16, + fontWeight: FontWeight.w600, + color: enabledTextColor)), ], ), ), ), - SizedBox( - height: 50, - ), - Text("Made with ❤️ by 100ms", - style: GoogleFonts.inter(color: iconColor)) - ], - ), + ), + ], ), - )), + ), + )), + ), ); } } diff --git a/example/lib/meeting/hms_sdk_interactor.dart b/example/lib/meeting/hms_sdk_interactor.dart index f3582c35f..8c8b82914 100644 --- a/example/lib/meeting/hms_sdk_interactor.dart +++ b/example/lib/meeting/hms_sdk_interactor.dart @@ -1,5 +1,4 @@ //Dart imports -import 'dart:io'; //Project imports import 'package:hmssdk_flutter/hmssdk_flutter.dart'; @@ -16,8 +15,8 @@ class HMSSDKInteractor { //Contains the default setting to jump directly in meeting i.e. skipping preview bool skipPreview = false; - HMSSDKInteractor() { - hmsSDK = HMSSDK(); + HMSSDKInteractor({String? appGroup, String? preferredExtension}) { + hmsSDK = HMSSDK(appGroup: appGroup, preferredExtension: preferredExtension); hmsSDK.build(); } @@ -43,11 +42,7 @@ class HMSSDKInteractor { } Future isScreenShareActive() async { - if (Platform.isAndroid) { - return await hmsSDK.isScreenShareActive(); - } else { - return false; - } + return await hmsSDK.isScreenShareActive(); } void sendBroadcastMessage( @@ -124,7 +119,7 @@ class HMSSDKInteractor { hmsSDK.stopCapturing(); } - Future getLocalPeer() async { + Future getLocalPeer() async { return await hmsSDK.getLocalPeer(); } @@ -238,13 +233,18 @@ class HMSSDKInteractor { } void startHLSStreaming( - String meetingUrl, HMSActionResultListener hmsActionResultListener) { + String meetingUrl, HMSActionResultListener hmsActionResultListener, + {bool singleFilePerLayer = false, bool enableVOD = false}) { List hmsHlsMeetingUrls = []; hmsHlsMeetingUrls.add(HMSHLSMeetingURLVariant( meetingUrl: meetingUrl, metadata: "HLS started from Flutter")); + HMSHLSRecordingConfig hmshlsRecordingConfig = HMSHLSRecordingConfig( + singleFilePerLayer: singleFilePerLayer, videoOnDemand: enableVOD); + HMSHLSConfig hmshlsConfig = HMSHLSConfig( + meetingURLVariant: hmsHlsMeetingUrls, + hmsHLSRecordingConfig: hmshlsRecordingConfig); - HMSHLSConfig hmshlsConfig = new HMSHLSConfig(hmsHlsMeetingUrls); hmsSDK.startHlsStreaming( hmshlsConfig: hmshlsConfig, hmsActionResultListener: hmsActionResultListener); diff --git a/example/lib/meeting/meeting_page.dart b/example/lib/meeting/meeting_page.dart index c4269757f..b5775712a 100644 --- a/example/lib/meeting/meeting_page.dart +++ b/example/lib/meeting/meeting_page.dart @@ -1,6 +1,3 @@ -//Dart imports -import 'dart:io'; - //Package imports import 'package:connectivity_checker/connectivity_checker.dart'; import 'package:flutter/material.dart'; @@ -51,12 +48,11 @@ class MeetingPage extends StatefulWidget { } class _MeetingPageState extends State - with WidgetsBindingObserver, TickerProviderStateMixin { + with TickerProviderStateMixin { CustomLogger logger = CustomLogger(); int appBarIndex = 0; bool audioViewOn = false; bool videoPreviousState = false; - bool isRecordingStarted = false; bool isBRB = false; final scrollController = DraggableScrollableController(); late AnimationController animationController; @@ -64,7 +60,6 @@ class _MeetingPageState extends State @override void initState() { super.initState(); - WidgetsBinding.instance!.addObserver(this); initMeeting(); checkAudioState(); setInitValues(); @@ -150,27 +145,22 @@ class _MeetingPageState extends State UtilityComponents.showRoleList(context, roles, _meetingStore); break; case 9: - if (_meetingStore.isRecordingStarted) { + if (_meetingStore.streamingType["rtmp"] == true) { _meetingStore.stopRtmpAndRecording(); - isRecordingStarted = false; } else { - if (isRecordingStarted == false) { - Map data = - await UtilityComponents.showRTMPInputDialog( - context: context, - placeholder: "Enter Comma separated RTMP Urls", - isRecordingEnabled: false); - List? urls; - if (data["url"]!.isNotEmpty) { - urls = data["url"]!.split(","); - } - if (data["toRecord"] == "true" || urls != null) { - _meetingStore.startRtmpOrRecording( - meetingUrl: Constant.rtmpUrl, - toRecord: data["toRecord"] == "true" ? true : false, - rtmpUrls: urls); - isRecordingStarted = true; - } + Map data = + await UtilityComponents.showRTMPInputDialog( + context: context, + placeholder: "Enter Comma separated RTMP Urls", + isRecordingEnabled: + _meetingStore.recordingType["browser"] == true); + List? urls; + if (data["url"]!.isNotEmpty) { + urls = data["url"]!.split(","); + } + if (urls != null) { + _meetingStore.startRtmpOrRecording( + meetingUrl: Constant.rtmpUrl, toRecord: false, rtmpUrls: urls); } } break; @@ -178,19 +168,21 @@ class _MeetingPageState extends State if (_meetingStore.hasHlsStarted) { _meetingStore.stopHLSStreaming(); } else { - String url = await UtilityComponents.showInputDialog( - context: context, - placeholder: "Enter HLS Url", - prefilledValue: Constant.rtmpUrl); - if (url.isNotEmpty) { - _meetingStore.startHLSStreaming(url); - } + UtilityComponents.showHLSDialog(context: context); } break; case 11: - _meetingStore.changeStatsVisible(); + if (_meetingStore.recordingType["browser"] == true) { + _meetingStore.stopRtmpAndRecording(); + } else { + _meetingStore.startRtmpOrRecording( + meetingUrl: Constant.rtmpUrl, toRecord: true, rtmpUrls: []); + } break; case 12: + _meetingStore.changeStatsVisible(); + break; + case 13: UtilityComponents.onEndRoomPressed(context); break; default: @@ -216,23 +208,36 @@ class _MeetingPageState extends State Navigator.of(context).popUntil((route) => route.isFirst); }); } + bool isPortraitMode = + MediaQuery.of(context).orientation == Orientation.portrait; return data.item1 ? OfflineWidget() : Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( + automaticallyImplyLeading: false, title: TitleBar(), actions: [ - Selector( - selector: (_, meetingStore) => - meetingStore.isRecordingStarted, - builder: (_, isRecordingStarted, __) { - return isRecordingStarted - ? SvgPicture.asset( + Selector, Map>>( + selector: (_, meetingStore) => Tuple2( + meetingStore.recordingType, + meetingStore.streamingType), + builder: (_, data, __) { + return Row( + children: [ + if (data.item1.containsValue(true)) + SvgPicture.asset( "assets/icons/record.svg", color: Colors.red, - ) - : Container(); + ), + if (data.item2.containsValue(true)) + SvgPicture.asset( + "assets/icons/stream.svg", + color: Colors.red, + ), + ], + ); }, ), IconButton( @@ -256,154 +261,163 @@ class _MeetingPageState extends State dropDownMenu(), ], ), - body: Stack( - children: [ - Container( - height: MediaQuery.of(context).size.height * 0.82, - child: Selector< - MeetingStore, - Tuple6, bool, int, int, - MeetingMode, PeerTrackNode?>>( - selector: (_, meetingStore) => Tuple6( - meetingStore.peerTracks, - meetingStore.isHLSLink, - meetingStore.peerTracks.length, - meetingStore.screenShareCount, - meetingStore.meetingMode, - meetingStore.peerTracks.length > 0 - ? meetingStore.peerTracks[ - meetingStore.screenShareCount] - : null), - builder: (_, data, __) { - if (data.item2) { - return Selector( - selector: (_, meetingStore) => - meetingStore.hasHlsStarted, - builder: (_, hasHlsStarted, __) { - return hasHlsStarted - ? Center( - child: Container( - child: HLSViewer( - streamUrl: context - .read< - MeetingStore>() - .streamUrl), - ), - ) - : Center( - child: Column( - mainAxisAlignment: - MainAxisAlignment - .center, - crossAxisAlignment: - CrossAxisAlignment - .center, - children: [ - Padding( - padding: - const EdgeInsets - .only( - bottom: 8.0), - child: Text( - "Waiting for HLS to start...", - style: - GoogleFonts.inter( - color: - iconColor, - fontSize: 20), + body: SafeArea( + child: Stack( + children: [ + Container( + child: Selector< + MeetingStore, + Tuple6, bool, int, + int, MeetingMode, PeerTrackNode?>>( + selector: (_, meetingStore) => Tuple6( + meetingStore.peerTracks, + meetingStore.isHLSLink, + meetingStore.peerTracks.length, + meetingStore.screenShareCount, + meetingStore.meetingMode, + meetingStore.peerTracks.length > 0 + ? meetingStore.peerTracks[ + meetingStore.screenShareCount] + : null), + builder: (_, data, __) { + if (data.item2) { + return Selector( + selector: (_, meetingStore) => + meetingStore.hasHlsStarted, + builder: (_, hasHlsStarted, __) { + return hasHlsStarted + ? Center( + child: Container( + child: HLSViewer( + streamUrl: context + .read< + MeetingStore>() + .streamUrl), + ), + ) + : Center( + child: Column( + mainAxisAlignment: + MainAxisAlignment + .center, + crossAxisAlignment: + CrossAxisAlignment + .center, + children: [ + Padding( + padding: + const EdgeInsets + .only( + bottom: 8.0), + child: Text( + "Waiting for HLS to start...", + style: GoogleFonts + .inter( + color: + iconColor, + fontSize: + 20), + ), ), - ), - RotationTransition( - child: Image.asset( - "assets/icons/hms_icon_loading.png"), - turns: - animationController, - ) - ], - ), - ); - }); - } - if (data.item3 == 0) { - return Center( - child: RotationTransition( - child: Image.asset( - "assets/icons/hms_icon_loading.png"), - turns: animationController, - )); - } - Size size = MediaQuery.of(context).size; - if (data.item5 == MeetingMode.Hero) { - return gridHeroView( + RotationTransition( + child: Image.asset( + "assets/icons/hms_icon_loading.png"), + turns: + animationController, + ) + ], + ), + ); + }); + } + if (data.item3 == 0) { + return Center( + child: RotationTransition( + child: Image.asset( + "assets/icons/hms_icon_loading.png"), + turns: animationController, + )); + } + Size size = MediaQuery.of(context).size; + if (data.item5 == MeetingMode.Hero) { + return gridHeroView( + peerTracks: data.item1, + itemCount: data.item3, + screenShareCount: data.item4, + context: context, + isPortrait: isPortraitMode, + size: size); + } + if (data.item5 == MeetingMode.Audio) { + return gridAudioView( + peerTracks: + data.item1.sublist(data.item4), + itemCount: data.item1 + .sublist(data.item4) + .length, + context: context, + isPortrait: isPortraitMode, + size: size); + } + if (data.item5 == MeetingMode.Single) { + return fullScreenView( + peerTracks: data.item1, + itemCount: data.item3, + screenShareCount: data.item4, + context: context, + isPortrait: isPortraitMode, + size: size); + } + return gridVideoView( peerTracks: data.item1, itemCount: data.item3, screenShareCount: data.item4, context: context, + isPortrait: isPortraitMode, size: size); + }), + ), + Selector( + selector: (_, meetingStore) => + meetingStore.isHLSLink, + builder: (_, isHlsRunning, __) { + return Positioned( + bottom: 0, + left: 0, + right: 0, + child: isHlsRunning + ? hlsBottomBarWidget() + : normalBottomBarWidget(), + ); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.roleChangeRequest, + builder: (_, roleChangeRequest, __) { + if (roleChangeRequest != null) { + WidgetsBinding.instance! + .addPostFrameCallback((_) { + UtilityComponents.showRoleChangeDialog( + roleChangeRequest, context); + }); } - if (data.item5 == MeetingMode.Audio) { - return gridAudioView( - peerTracks: - data.item1.sublist(data.item4), - itemCount: data.item1 - .sublist(data.item4) - .length, - size: size); - } - if (data.item5 == MeetingMode.Single) { - return fullScreenView( - peerTracks: data.item1, - itemCount: data.item3, - screenShareCount: data.item4, - context: context, - size: size); + return SizedBox(); + }), + Selector( + selector: (_, meetingStore) => + meetingStore.hmsTrackChangeRequest, + builder: (_, hmsTrackChangeRequest, __) { + if (hmsTrackChangeRequest != null) { + WidgetsBinding.instance! + .addPostFrameCallback((_) { + UtilityComponents.showTrackChangeDialog( + hmsTrackChangeRequest, context); + }); } - return gridVideoView( - peerTracks: data.item1, - itemCount: data.item3, - screenShareCount: data.item4, - context: context, - size: size); + return SizedBox(); }), - ), - Selector( - selector: (_, meetingStore) => - meetingStore.isHLSLink, - builder: (_, isHlsRunning, __) { - return Positioned( - bottom: 0, - child: isHlsRunning - ? hlsBottomBarWidget() - : normalBottomBarWidget(), - ); - }), - Selector( - selector: (_, meetingStore) => - meetingStore.roleChangeRequest, - builder: (_, roleChangeRequest, __) { - if (roleChangeRequest != null) { - WidgetsBinding.instance! - .addPostFrameCallback((_) { - UtilityComponents.showRoleChangeDialog( - roleChangeRequest, context); - }); - } - return Container(); - }), - Selector( - selector: (_, meetingStore) => - meetingStore.hmsTrackChangeRequest, - builder: (_, hmsTrackChangeRequest, __) { - if (hmsTrackChangeRequest != null) { - WidgetsBinding.instance! - .addPostFrameCallback((_) { - UtilityComponents.showTrackChangeDialog( - hmsTrackChangeRequest, context); - }); - } - return Container(); - }), - ], + ], + ), ), ); }, @@ -416,8 +430,11 @@ class _MeetingPageState extends State } Widget normalBottomBarWidget() { - return SizedBox( + return Container( width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.2), + borderRadius: BorderRadius.circular(40)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -729,8 +746,7 @@ class _MeetingPageState extends State ), if ((meetingStore.localPeer != null) && meetingStore.localPeer!.role.publishSettings!.allowed - .contains("screen") && - Platform.isAndroid) + .contains("screen")) PopupMenuItem( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -810,18 +826,18 @@ class _MeetingPageState extends State mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - meetingStore.isRecordingStarted - ? "Stop RTMP/Rec" - : "Start RTMP/Rec", + meetingStore.streamingType["rtmp"] == true + ? "Stop RTMP" + : "Start RTMP", style: GoogleFonts.inter( - color: meetingStore.isRecordingStarted + color: meetingStore.streamingType["rtmp"] == true ? Colors.blue : iconColor, )), SvgPicture.asset( - "assets/icons/record.svg", - color: meetingStore.isRecordingStarted - ? Colors.red + "assets/icons/stream.svg", + color: meetingStore.streamingType["rtmp"] == true + ? Colors.blue : iconColor, ), ]), @@ -847,6 +863,29 @@ class _MeetingPageState extends State ]), value: 10, ), + if (!(meetingStore.localPeer?.role.name.contains("hls-") ?? true)) + PopupMenuItem( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + meetingStore.recordingType["browser"] == true + ? "Stop Recording" + : "Start Recording", + style: GoogleFonts.inter( + color: meetingStore.recordingType["browser"] == true + ? Colors.blue + : iconColor, + )), + SvgPicture.asset( + "assets/icons/record.svg", + color: meetingStore.recordingType["browser"] == true + ? Colors.red + : iconColor, + ), + ]), + value: 11, + ), PopupMenuItem( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -863,7 +902,7 @@ class _MeetingPageState extends State ? Colors.blue : iconColor), ]), - value: 11, + value: 12, ), if (meetingStore.localPeer!.role.permissions.endRoom!) PopupMenuItem( @@ -881,7 +920,7 @@ class _MeetingPageState extends State color: iconColor, ), ]), - value: 12, + value: 13, ), ]; }, diff --git a/example/lib/meeting/meeting_store.dart b/example/lib/meeting/meeting_store.dart index 03c8438d8..0051d70ed 100644 --- a/example/lib/meeting/meeting_store.dart +++ b/example/lib/meeting/meeting_store.dart @@ -1,7 +1,8 @@ //Package imports import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; +import 'package:flutter/services.dart'; +import 'package:hmssdk_flutter_example/common/util/utility_function.dart'; import 'package:hmssdk_flutter_example/enum/meeting_mode.dart'; import 'package:hmssdk_flutter_example/model/rtc_stats.dart'; import 'package:intl/intl.dart'; @@ -13,6 +14,7 @@ import 'package:hmssdk_flutter_example/meeting/peer_track_node.dart'; import 'package:hmssdk_flutter_example/service/room_service.dart'; class MeetingStore extends ChangeNotifier + with WidgetsBindingObserver implements HMSUpdateListener, HMSActionResultListener, HMSStatsListener { late HMSSDKInteractor _hmsSDKInteractor; @@ -54,7 +56,13 @@ class MeetingStore extends ChangeNotifier bool isRoomEnded = false; - bool isRecordingStarted = false; + Map recordingType = { + "browser": false, + "server": false, + "hls": false + }; + + Map streamingType = {"rtmp": false, "hls": false}; String description = "Meeting Ended"; @@ -99,6 +107,8 @@ class MeetingStore extends ChangeNotifier MeetingMode meetingMode = MeetingMode.Video; + bool isLandscapeLocked = false; + Future join(String user, String roomUrl) async { List? token = await RoomService().getToken(user: user, room: roomUrl); @@ -110,17 +120,14 @@ class MeetingStore extends ChangeNotifier captureNetworkQualityInPreview: true); _hmsSDKInteractor.addUpdateListener(this); + WidgetsBinding.instance!.addObserver(this); _hmsSDKInteractor.join(config: config); return true; } void leave() async { - if (isScreenShareOn) { - isScreenShareOn = false; - _hmsSDKInteractor.stopScreenShare(); - } - _hmsSDKInteractor.removeStatsListener(this); + WidgetsBinding.instance!.removeObserver(this); _hmsSDKInteractor.leave(hmsActionResultListener: this); } @@ -233,6 +240,21 @@ class MeetingStore extends ChangeNotifier notifyListeners(); } + void setLandscapeLock(bool value) { + if (value) { + SystemChrome.setPreferredOrientations( + [DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft]); + } else { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight + ]); + } + isLandscapeLocked = value; + notifyListeners(); + } + @override void onJoin({required HMSRoom room}) async { isMeetingStarted = true; @@ -243,14 +265,21 @@ class MeetingStore extends ChangeNotifier } else { hasHlsStarted = false; } - if (room.hmsBrowserRecordingState?.running == true || - room.hmsServerRecordingState?.running == true || - room.hmsRtmpStreamingState?.running == true || - room.hmshlsStreamingState?.running == true) - isRecordingStarted = true; - else - isRecordingStarted = false; - + if (room.hmsBrowserRecordingState?.running == true) { + recordingType["browser"] = true; + } + if (room.hmsServerRecordingState?.running == true) { + recordingType["server"] = true; + } + if (room.hmshlsRecordingState?.running == true) { + recordingType["hls"] = true; + } + if (room.hmsRtmpStreamingState?.running == true) { + streamingType["rtmp"] = true; + } + if (room.hmshlsStreamingState?.running == true) { + streamingType["hls"] = true; + } for (HMSPeer each in room.peers!) { if (each.isLocal) { int index = peerTracks @@ -264,17 +293,24 @@ class MeetingStore extends ChangeNotifier localPeer = each; addPeer(localPeer!); if (localPeer!.role.name.contains("hls-") == true) isHLSLink = true; - + index = peerTracks + .indexWhere((element) => element.uid == each.peerId + "mainVideo"); if (each.videoTrack != null) { if (each.videoTrack!.kind == HMSTrackKind.kHMSTrackKindVideo) { - int index = peerTracks.indexWhere( - (element) => element.uid == each.peerId + "mainVideo"); peerTracks[index].track = each.videoTrack!; if (each.videoTrack!.isMute) { this.isVideoOn = false; } } } + if (each.audioTrack != null) { + if (each.audioTrack!.kind == HMSTrackKind.kHMSTrackKindAudio) { + peerTracks[index].audioTrack = each.audioTrack!; + if (each.audioTrack!.isMute) { + this.isMicOn = false; + } + } + } break; } } @@ -286,18 +322,21 @@ class MeetingStore extends ChangeNotifier void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) { switch (update) { case HMSRoomUpdate.browserRecordingStateUpdated: - isRecordingStarted = room.hmsBrowserRecordingState?.running ?? false; + recordingType["browser"] = + room.hmsBrowserRecordingState?.running ?? false; break; - case HMSRoomUpdate.serverRecordingStateUpdated: - isRecordingStarted = room.hmsServerRecordingState?.running ?? false; + recordingType["server"] = + room.hmsServerRecordingState?.running ?? false; + break; + case HMSRoomUpdate.hlsRecordingStateUpdated: + recordingType["hls"] = room.hmshlsRecordingState?.running ?? false; break; - case HMSRoomUpdate.rtmpStreamingStateUpdated: - isRecordingStarted = room.hmsRtmpStreamingState?.running ?? false; + streamingType["rtmp"] = room.hmsRtmpStreamingState?.running ?? false; break; case HMSRoomUpdate.hlsStreamingStateUpdated: - isRecordingStarted = room.hmshlsStreamingState?.running ?? false; + streamingType["hls"] = room.hmshlsStreamingState?.running ?? false; hasHlsStarted = room.hmshlsStreamingState?.running ?? false; streamUrl = hasHlsStarted ? room.hmshlsStreamingState?.variants[0]?.hlsStreamUrl ?? "" @@ -367,7 +406,7 @@ class MeetingStore extends ChangeNotifier } @override - void onError({required HMSException error}) { + void onHMSError({required HMSException error}) { this.hmsException = hmsException; notifyListeners(); } @@ -514,6 +553,12 @@ class MeetingStore extends ChangeNotifier case HMSPeerUpdate.peerLeft: peerTracks.removeWhere( (leftPeer) => leftPeer.uid == peer.peerId + "mainVideo"); + int index = peerTracks + .indexWhere((element) => element.peer.peerId == peer.peerId); + if (index != -1) { + peerTracks.removeAt(index); + screenShareCount--; + } removePeer(peer); notifyListeners(); break; @@ -599,14 +644,9 @@ class MeetingStore extends ChangeNotifier PeerTrackNode( peer: peer, uid: peer.peerId + track.trackId, - track: track as HMSVideoTrack)); + track: track as HMSVideoTrack, + stats: RTCStats())); isScreenShareActive(); - // for (var node in peerTracks) { - // if (node.isOffscreen == false) { - // node.setOffScreenStatus(true); - // } - // } - // controller.jumpTo(0); notifyListeners(); } break; @@ -615,9 +655,11 @@ class MeetingStore extends ChangeNotifier screenShareCount--; peerTracks.removeWhere( (element) => element.uid == peer.peerId + track.trackId); - notifyListeners(); - } else { + if (screenShareCount == 0) { + setLandscapeLock(false); + } isScreenShareActive(); + notifyListeners(); } break; case HMSTrackUpdate.trackMuted: @@ -685,7 +727,7 @@ class MeetingStore extends ChangeNotifier // HmsSdkManager.hmsSdkInteractor?.removeHMSLogger(); // } - Future getLocalPeer() async { + Future getLocalPeer() async { return await _hmsSDKInteractor.getLocalPeer(); } @@ -755,8 +797,10 @@ class MeetingStore extends ChangeNotifier _hmsSDKInteractor.changeName(name: name, hmsActionResultListener: this); } - void startHLSStreaming(String meetingUrl) { - _hmsSDKInteractor.startHLSStreaming(meetingUrl, this); + void startHLSStreaming( + String meetingUrl, bool singleFile, bool videoOnDemand) { + _hmsSDKInteractor.startHLSStreaming(meetingUrl, this, + singleFilePerLayer: singleFile, enableVOD: videoOnDemand); } void stopHLSStreaming() { @@ -933,10 +977,6 @@ class MeetingStore extends ChangeNotifier return activeSpeakerIds.containsKey(uid) ? activeSpeakerIds[uid]! : -1; } - void showToast(String message) { - Fluttertoast.showToast(msg: message, backgroundColor: Colors.black87); - } - @override void onSuccess( {HMSActionResultListenerMethod methodType = @@ -948,10 +988,12 @@ class MeetingStore extends ChangeNotifier isRoomEnded = true; screenShareCount = 0; this.meetingMode = MeetingMode.Video; + isScreenShareOn = false; + setLandscapeLock(false); notifyListeners(); break; case HMSActionResultListenerMethod.changeTrackState: - showToast("Track State Changed"); + Utilities.showToast("Track State Changed"); break; case HMSActionResultListenerMethod.changeMetadata: notifyListeners(); @@ -961,31 +1003,38 @@ class MeetingStore extends ChangeNotifier notifyListeners(); break; case HMSActionResultListenerMethod.removePeer: - // TODO: Handle this case. + HMSPeer peer = arguments!['peer']; + Utilities.showToast("Removed ${peer.name} from room"); break; case HMSActionResultListenerMethod.acceptChangeRole: - showToast("Accept role change successful"); + Utilities.showToast("Accept role change successful"); break; case HMSActionResultListenerMethod.changeRole: - showToast("Change role successful"); + Utilities.showToast("Change role successful"); break; case HMSActionResultListenerMethod.changeTrackStateForRole: message = arguments!['roles'] == null ? "Successfully Muted All" : "Successfully Muted Role"; - showToast(message); + Utilities.showToast(message); break; case HMSActionResultListenerMethod.startRtmpOrRecording: - showToast("RTMP start successful"); - + if (arguments != null) { + if (arguments["rtmp_urls"].length == 0 && arguments["to_record"]) { + Utilities.showToast("Recording Started"); + } else if (arguments["rtmp_urls"].length != 0 && + arguments["to_record"] == false) { + Utilities.showToast("RTMP Started"); + } + } break; case HMSActionResultListenerMethod.stopRtmpAndRecording: - showToast("RTMP stop successful"); + Utilities.showToast("Stopped successfully"); break; case HMSActionResultListenerMethod.unknown: break; case HMSActionResultListenerMethod.changeName: - showToast("Change name successful"); + Utilities.showToast("Change name successful"); break; case HMSActionResultListenerMethod.sendBroadcastMessage: var message = HMSMessage( @@ -1029,22 +1078,22 @@ class MeetingStore extends ChangeNotifier break; case HMSActionResultListenerMethod.hlsStreamingStarted: hasHlsStarted = true; - showToast("HLS Streaming Started"); + Utilities.showToast("HLS Streaming Started"); notifyListeners(); break; case HMSActionResultListenerMethod.hlsStreamingStopped: hasHlsStarted = false; - showToast("HLS Streaming Stopped"); + Utilities.showToast("HLS Streaming Stopped"); notifyListeners(); break; case HMSActionResultListenerMethod.startScreenShare: - showToast("Screen Share Started"); + Utilities.showToast("Screen Share Started"); isScreenShareActive(); break; case HMSActionResultListenerMethod.stopScreenShare: - showToast("Screen Share Stopped"); + Utilities.showToast("Screen Share Stopped"); isScreenShareActive(); break; } @@ -1060,63 +1109,67 @@ class MeetingStore extends ChangeNotifier FirebaseCrashlytics.instance.log(hmsException.toString()); switch (methodType) { case HMSActionResultListenerMethod.leave: - showToast("Leave Operation failed"); + Utilities.showToast("Leave Operation failed"); break; case HMSActionResultListenerMethod.changeTrackState: - showToast("Change Track state failed"); + Utilities.showToast("Change Track state failed"); break; case HMSActionResultListenerMethod.changeMetadata: // TODO: Handle this case. break; case HMSActionResultListenerMethod.endRoom: - showToast("End room failed"); + Utilities.showToast("End room failed"); break; case HMSActionResultListenerMethod.removePeer: - showToast("Remove peer failed"); + Utilities.showToast("Remove peer failed"); break; case HMSActionResultListenerMethod.acceptChangeRole: - showToast("Accept change role failed"); + Utilities.showToast("Accept change role failed"); break; case HMSActionResultListenerMethod.changeRole: - showToast("Change role failed"); + Utilities.showToast("Change role failed"); break; case HMSActionResultListenerMethod.changeTrackStateForRole: - showToast("Failed to change track state"); + Utilities.showToast("Failed to change track state"); break; case HMSActionResultListenerMethod.startRtmpOrRecording: - if (hmsException.code?.errorCode == "400") { - isRecordingStarted = true; + if (arguments != null) { + if (arguments["rtmp_urls"].length == 0 && arguments["to_record"]) { + Utilities.showToast("Recording failed"); + } else if (arguments["rtmp_urls"].length != 0 && + arguments["to_record"] == false) { + Utilities.showToast("RTMP failed"); + } } - showToast("Start RTMP Streaming failed"); break; case HMSActionResultListenerMethod.stopRtmpAndRecording: - showToast("Stop RTMP Streaming failed"); + Utilities.showToast("Stop RTMP Streaming failed"); break; case HMSActionResultListenerMethod.changeName: - showToast("Name change failed"); + Utilities.showToast("Name change failed"); break; case HMSActionResultListenerMethod.sendBroadcastMessage: - // TODO: Handle this case. + Utilities.showToast("Sending broadcast message failed"); break; case HMSActionResultListenerMethod.sendGroupMessage: - // TODO: Handle this case. + Utilities.showToast("Sending group message failed"); break; case HMSActionResultListenerMethod.sendDirectMessage: - // TODO: Handle this case. + Utilities.showToast("Sending direct message failed"); break; case HMSActionResultListenerMethod.hlsStreamingStarted: - showToast("Start HLS failed"); + Utilities.showToast("Start HLS failed"); break; case HMSActionResultListenerMethod.hlsStreamingStopped: - showToast("Stop HLS failed"); + Utilities.showToast("Stop HLS failed"); break; case HMSActionResultListenerMethod.startScreenShare: isScreenShareActive(); - showToast("Start screenshare failed"); + Utilities.showToast("Start screenshare failed"); break; case HMSActionResultListenerMethod.stopScreenShare: isScreenShareActive(); - showToast("Stop screenshare failed"); + Utilities.showToast("Stop screenshare failed"); break; case HMSActionResultListenerMethod.unknown: break; @@ -1126,4 +1179,42 @@ class MeetingStore extends ChangeNotifier Future?> getPeers() async { return await _hmsSDKInteractor.getPeers(); } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) async { + super.didChangeAppLifecycleState(state); + + if (state == AppLifecycleState.resumed) { + List? peersList = await getPeers(); + + peersList?.forEach((element) { + if (!element.isLocal) { + (element.audioTrack as HMSRemoteAudioTrack?)?.setVolume(10.0); + element.auxiliaryTracks?.forEach((element) { + if (element.kind == HMSTrackKind.kHMSTrackKindAudio) { + (element as HMSRemoteAudioTrack?)?.setVolume(10.0); + } + }); + } else { + if ((element.videoTrack != null && isVideoOn)) startCapturing(); + } + }); + } else if (state == AppLifecycleState.paused) { + HMSLocalPeer? localPeer = await getLocalPeer(); + if (localPeer != null && !(localPeer.videoTrack?.isMute ?? true)) { + stopCapturing(); + } + for (PeerTrackNode peerTrackNode in peerTracks) { + peerTrackNode.setOffScreenStatus(true); + } + } else if (state == AppLifecycleState.inactive) { + HMSLocalPeer? localPeer = await getLocalPeer(); + if (localPeer != null && !(localPeer.videoTrack?.isMute ?? true)) { + stopCapturing(); + } + for (PeerTrackNode peerTrackNode in peerTracks) { + peerTrackNode.setOffScreenStatus(true); + } + } + } } diff --git a/example/lib/preview/preview_page.dart b/example/lib/preview/preview_page.dart index 23ffe11d1..33abd154f 100644 --- a/example/lib/preview/preview_page.dart +++ b/example/lib/preview/preview_page.dart @@ -61,8 +61,8 @@ class _PreviewPageState extends State .read() .startPreview(user: widget.user, roomId: widget.roomId); if (ans == false) { - UtilityComponents.showToastWithString("Unable to preview"); Navigator.of(context).pop(); + UtilityComponents.showErrorDialog(context); } } @@ -150,20 +150,43 @@ class _PreviewPageState extends State child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + if (_previewStore.isStreamingStarted) + Container( + height: 33, + width: 33, + decoration: BoxDecoration( + color: Colors.transparent.withOpacity(0.2), + borderRadius: + _previewStore.isRecordingStarted + ? BorderRadius.only( + topLeft: Radius.circular(5), + bottomLeft: Radius.circular(5)) + : BorderRadius.circular(5)), + child: SvgPicture.asset( + "assets/icons/stream.svg", + color: Colors.red, + ), + ), if (_previewStore.isRecordingStarted) Container( - height: 35, - width: 35, + height: 33, + width: 33, decoration: BoxDecoration( color: Colors.transparent.withOpacity(0.2), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(5), - bottomRight: Radius.circular(5))), + borderRadius: + _previewStore.isStreamingStarted + ? BorderRadius.only( + topRight: Radius.circular(5), + bottomRight: Radius.circular(5)) + : BorderRadius.circular(5)), child: SvgPicture.asset( "assets/icons/record.svg", color: Colors.red, ), ), + SizedBox( + width: 5, + ), if (_previewStore.peers.isNotEmpty) GestureDetector( onTap: () { @@ -236,13 +259,8 @@ class _PreviewPageState extends State decoration: BoxDecoration( color: Colors.transparent .withOpacity(0.2), - borderRadius: _previewStore - .isRecordingStarted - ? BorderRadius.only( - topRight: Radius.circular(5), - bottomRight: - Radius.circular(5)) - : BorderRadius.circular(5)), + borderRadius: + BorderRadius.circular(5)), child: Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/example/lib/preview/preview_store.dart b/example/lib/preview/preview_store.dart index db11249df..dcde46935 100644 --- a/example/lib/preview/preview_store.dart +++ b/example/lib/preview/preview_store.dart @@ -10,7 +10,10 @@ class PreviewStore extends ChangeNotifier late HMSSDKInteractor hmsSDKInteractor; PreviewStore() { - hmsSDKInteractor = HMSSDKInteractor(); + hmsSDKInteractor = HMSSDKInteractor( + appGroup: "group.flutterhms", + preferredExtension: + "live.100ms.flutter.FlutterBroadcastUploadExtension"); } List localTracks = []; @@ -28,12 +31,14 @@ class PreviewStore extends ChangeNotifier bool isRecordingStarted = false; + bool isStreamingStarted = false; + List peers = []; int? networkQuality; @override - void onError({required HMSException error}) { + void onHMSError({required HMSException error}) { updateError(error); } @@ -114,11 +119,15 @@ class PreviewStore extends ChangeNotifier isRecordingStarted = room.hmsServerRecordingState?.running ?? false; break; + case HMSRoomUpdate.hlsRecordingStateUpdated: + isRecordingStarted = room.hmshlsRecordingState?.running ?? false; + break; + case HMSRoomUpdate.rtmpStreamingStateUpdated: - isRecordingStarted = room.hmsRtmpStreamingState?.running ?? false; + isStreamingStarted = room.hmsRtmpStreamingState?.running ?? false; break; case HMSRoomUpdate.hlsStreamingStateUpdated: - isRecordingStarted = room.hmshlsStreamingState?.running ?? false; + isStreamingStarted = room.hmshlsStreamingState?.running ?? false; break; default: break; diff --git a/example/lib/qr_code_screen.dart b/example/lib/qr_code_screen.dart new file mode 100644 index 000000000..94b6afaa2 --- /dev/null +++ b/example/lib/qr_code_screen.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:hmssdk_flutter_example/common/ui/organisms/user_name_dialog_organism.dart'; +import 'package:hmssdk_flutter_example/common/util/app_color.dart'; +import 'package:hmssdk_flutter_example/common/util/utility_function.dart'; +import 'package:hmssdk_flutter_example/enum/meeting_flow.dart'; +import 'package:hmssdk_flutter_example/preview/preview_page.dart'; +import 'package:hmssdk_flutter_example/preview/preview_store.dart'; +import 'package:provider/provider.dart'; +import 'package:qr_code_scanner/qr_code_scanner.dart'; + +class QRCodeScreen extends StatefulWidget { + @override + State createState() => _QRCodeScreenState(); +} + +class _QRCodeScreenState extends State { + final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + QRViewController? controller; + + void _onQRViewCreated(QRViewController controller) { + this.controller = controller; + controller.scannedDataStream.listen((scanData) async { + controller.pauseCamera(); + if (scanData.code != null) { + MeetingFlow flow = Utilities.deriveFlow(scanData.code!); + if (flow == MeetingFlow.join) { + Utilities.setRTMPUrl(scanData.code!); + String user = await showDialog( + context: context, builder: (_) => UserNameDialogOrganism()); + if (user.isNotEmpty) { + bool res = await Utilities.getPermissions(); + if (res) { + FocusManager.instance.primaryFocus?.unfocus(); + Navigator.of(context).pushReplacement(MaterialPageRoute( + builder: (_) => ListenableProvider.value( + value: PreviewStore(), + child: PreviewPage( + roomId: scanData.code!.trim(), + user: user, + flow: MeetingFlow.join, + mirror: true, + showStats: false, + ), + ))); + } + } else { + controller.resumeCamera(); + } + } else { + Utilities.showToast("Invalid QR Code"); + controller.resumeCamera(); + } + } + }); + } + + @override + Widget build(BuildContext context) { + double width = MediaQuery.of(context).size.width; + double height = MediaQuery.of(context).size.height; + Orientation orientation = MediaQuery.of(context).orientation; + return Scaffold( + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(top: 40.0, left: 20, right: 20), + child: Column( + children: [ + Row( + children: [ + CircleAvatar( + radius: 18, + backgroundColor: borderColor, + child: IconButton( + icon: Icon( + Icons.arrow_back_ios_new, + size: 16, + color: defaultColor, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + SizedBox( + width: 20, + ), + Text( + "Scan QR Code", + style: GoogleFonts.inter( + color: defaultColor, + fontSize: 16, + fontWeight: FontWeight.w600), + ) + ], + ), + SizedBox( + height: 30, + ), + Container( + height: + orientation == Orientation.portrait ? height * 0.75 : 500, + width: MediaQuery.of(context).size.width - 40, + child: QRView( + key: qrKey, + onQRViewCreated: _onQRViewCreated, + overlay: QrScannerOverlayShape( + borderRadius: 10, + borderWidth: 5, + borderColor: Colors.white, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: SizedBox( + width: width * 0.95, + child: ElevatedButton( + style: ButtonStyle( + shadowColor: MaterialStateProperty.all(surfaceColor), + backgroundColor: + MaterialStateProperty.all(Colors.transparent), + side: MaterialStateProperty.all( + BorderSide(color: borderColor)), + shape: + MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ))), + onPressed: () { + Navigator.pop(context); + }, + child: Container( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.link, + size: 22, + color: enabledTextColor, + ), + SizedBox( + width: 8, + ), + Text('Join with Link Instead', + style: GoogleFonts.inter( + height: 1, + fontSize: 16, + fontWeight: FontWeight.w500, + color: enabledTextColor)), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 2c843d3cc..16dd422be 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -91,7 +91,7 @@ packages: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" dropdown_button2: dependency: "direct main" description: @@ -133,77 +133,77 @@ packages: name: firebase_analytics url: "https://pub.dartlang.org" source: hosted - version: "9.1.9" + version: "9.1.10" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.1.7" + version: "3.1.8" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web url: "https://pub.dartlang.org" source: hosted - version: "0.4.0+14" + version: "0.4.0+15" firebase_core: dependency: "direct main" description: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "1.17.1" + version: "1.18.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.4.0" + version: "4.4.1" firebase_core_web: dependency: transitive description: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.6.5" firebase_crashlytics: dependency: "direct main" description: name: firebase_crashlytics url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "3.2.7" + version: "3.2.8" firebase_performance: dependency: "direct main" description: name: firebase_performance url: "https://pub.dartlang.org" source: hosted - version: "0.8.0+13" + version: "0.8.0+14" firebase_performance_platform_interface: dependency: transitive description: name: firebase_performance_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+7" + version: "0.1.1+8" firebase_performance_web: dependency: transitive description: name: firebase_performance_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+13" + version: "0.1.0+14" flutter: dependency: "direct main" description: flutter @@ -215,7 +215,7 @@ packages: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.9.2" + version: "0.9.3" flutter_staggered_grid_view: dependency: "direct main" description: @@ -267,7 +267,7 @@ packages: path: ".." relative: true source: path - version: "0.7.2" + version: "0.7.3" html: dependency: transitive description: @@ -414,7 +414,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.10" + version: "2.0.11" path_provider_android: dependency: transitive description: @@ -463,14 +463,14 @@ packages: name: permission_handler url: "https://pub.dartlang.org" source: hosted - version: "9.2.0" + version: "10.0.0" permission_handler_android: dependency: transitive description: name: permission_handler_android url: "https://pub.dartlang.org" source: hosted - version: "9.0.2+1" + version: "10.0.0" permission_handler_apple: dependency: transitive description: @@ -527,6 +527,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.0.3" + qr_code_scanner: + dependency: "direct main" + description: + name: qr_code_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" quiver: dependency: transitive description: @@ -608,28 +615,28 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "2.4.2" + version: "2.4.5" video_player_android: dependency: transitive description: name: video_player_android url: "https://pub.dartlang.org" source: hosted - version: "2.3.5" + version: "2.3.6" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation url: "https://pub.dartlang.org" source: hosted - version: "2.3.4" + version: "2.3.5" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "5.1.2" + version: "5.1.3" video_player_web: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ca35ab3e4..14ac9239c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -38,7 +38,8 @@ dependencies: avatar_glow: flutter_svg: google_fonts: - fluttertoast: ^8.0.9 + fluttertoast: + qr_code_scanner: dev_dependencies: flutter_test: diff --git a/example_riverpod/.gitignore b/example_riverpod/.gitignore new file mode 100644 index 000000000..0fa6b675c --- /dev/null +++ b/example_riverpod/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example_riverpod/.metadata b/example_riverpod/.metadata new file mode 100644 index 000000000..3c3e4b52f --- /dev/null +++ b/example_riverpod/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 5464c5bac742001448fe4fc0597be939379f88ea + channel: stable + +project_type: app diff --git a/example_riverpod/README.md b/example_riverpod/README.md new file mode 100644 index 000000000..652ed1bb8 --- /dev/null +++ b/example_riverpod/README.md @@ -0,0 +1,16 @@ +# example_riverpod + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example_riverpod/analysis_options.yaml b/example_riverpod/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/example_riverpod/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example_riverpod/android/.gitignore b/example_riverpod/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/example_riverpod/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/example_riverpod/android/app/build.gradle b/example_riverpod/android/app/build.gradle new file mode 100644 index 000000000..0665f670b --- /dev/null +++ b/example_riverpod/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example_riverpod" + minSdkVersion 21 + targetSdkVersion 32 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/example_riverpod/android/app/proguard-rules.pro b/example_riverpod/android/app/proguard-rules.pro new file mode 100644 index 000000000..5f6821f1a --- /dev/null +++ b/example_riverpod/android/app/proguard-rules.pro @@ -0,0 +1,32 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +# https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android +-keepattributes SourceFile,LineNumberTable # Keep file names and line numbers. +#-keep public class * extends java.lang.Exception # Optional: Keep custom exceptions. + +# https://developer.android.com/guide/navigation/navigation-pass-data#proguard_considerations + + +# Video libs +-keep class org.webrtc.** { *; } +-keep class live.hms.video.** { *; } \ No newline at end of file diff --git a/example_riverpod/android/app/src/debug/AndroidManifest.xml b/example_riverpod/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..432009a45 --- /dev/null +++ b/example_riverpod/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example_riverpod/android/app/src/main/AndroidManifest.xml b/example_riverpod/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..79e0a743d --- /dev/null +++ b/example_riverpod/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/example_riverpod/android/app/src/main/kotlin/com/example/example_riverpod/MainActivity.kt b/example_riverpod/android/app/src/main/kotlin/com/example/example_riverpod/MainActivity.kt new file mode 100644 index 000000000..80c8bebcd --- /dev/null +++ b/example_riverpod/android/app/src/main/kotlin/com/example/example_riverpod/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example_riverpod + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example_riverpod/android/app/src/main/res/drawable-v21/launch_background.xml b/example_riverpod/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/example_riverpod/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example_riverpod/android/app/src/main/res/drawable/launch_background.xml b/example_riverpod/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/example_riverpod/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example_riverpod/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example_riverpod/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/example_riverpod/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example_riverpod/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example_riverpod/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/example_riverpod/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example_riverpod/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example_riverpod/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/example_riverpod/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example_riverpod/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example_riverpod/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/example_riverpod/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example_riverpod/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example_riverpod/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/example_riverpod/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example_riverpod/android/app/src/main/res/values-night/styles.xml b/example_riverpod/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..3db14bb53 --- /dev/null +++ b/example_riverpod/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example_riverpod/android/app/src/main/res/values/styles.xml b/example_riverpod/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..d460d1e92 --- /dev/null +++ b/example_riverpod/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example_riverpod/android/app/src/profile/AndroidManifest.xml b/example_riverpod/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..432009a45 --- /dev/null +++ b/example_riverpod/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example_riverpod/android/build.gradle b/example_riverpod/android/build.gradle new file mode 100644 index 000000000..4256f9173 --- /dev/null +++ b/example_riverpod/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/example_riverpod/android/gradle.properties b/example_riverpod/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/example_riverpod/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/example_riverpod/android/gradle/wrapper/gradle-wrapper.properties b/example_riverpod/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..bc6a58afd --- /dev/null +++ b/example_riverpod/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/example_riverpod/android/settings.gradle b/example_riverpod/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/example_riverpod/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example_riverpod/ios/.gitignore b/example_riverpod/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/example_riverpod/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example_riverpod/ios/Flutter/AppFrameworkInfo.plist b/example_riverpod/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..8d4492f97 --- /dev/null +++ b/example_riverpod/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/example_riverpod/ios/Flutter/Debug.xcconfig b/example_riverpod/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/example_riverpod/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/example_riverpod/ios/Flutter/Release.xcconfig b/example_riverpod/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/example_riverpod/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/example_riverpod/ios/Podfile b/example_riverpod/ios/Podfile new file mode 100644 index 000000000..2c068c404 --- /dev/null +++ b/example_riverpod/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example_riverpod/ios/Podfile.lock b/example_riverpod/ios/Podfile.lock new file mode 100644 index 000000000..dcab16ec1 --- /dev/null +++ b/example_riverpod/ios/Podfile.lock @@ -0,0 +1,33 @@ +PODS: + - Flutter (1.0.0) + - HMSSDK (0.3.1): + - HMSWebRTC (= 1.0.4897) + - hmssdk_flutter (0.7.3): + - Flutter + - HMSSDK (= 0.3.1) + - HMSWebRTC (1.0.4897) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - hmssdk_flutter (from `.symlinks/plugins/hmssdk_flutter/ios`) + +SPEC REPOS: + trunk: + - HMSSDK + - HMSWebRTC + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + hmssdk_flutter: + :path: ".symlinks/plugins/hmssdk_flutter/ios" + +SPEC CHECKSUMS: + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + HMSSDK: 9780df180a4c8bd3446fd2981b338856fd02b3a4 + hmssdk_flutter: dbcba4eb09a0403ed3f7aeca310cfb06b4cfcc77 + HMSWebRTC: aa317dbbf56a191463a90491e5e37f298f8fa29c + +PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 + +COCOAPODS: 1.11.3 diff --git a/example_riverpod/ios/Runner.xcodeproj/project.pbxproj b/example_riverpod/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..bfbce9640 --- /dev/null +++ b/example_riverpod/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,552 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + FF45A4D9914FE6F74FD08C85 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2569E711E998F783C1944FEC /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2569E711E998F783C1944FEC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3447365D551A751DA6FDE2E3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A9FE6863E244E8A3E415B262 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + B9C0C4513520F47128FD17DF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FF45A4D9914FE6F74FD08C85 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + BEEDA785A4C0FCA116D0AFFD /* Pods */, + BBCDD932BB452E4B1F78EC84 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + BBCDD932BB452E4B1F78EC84 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 2569E711E998F783C1944FEC /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + BEEDA785A4C0FCA116D0AFFD /* Pods */ = { + isa = PBXGroup; + children = ( + A9FE6863E244E8A3E415B262 /* Pods-Runner.debug.xcconfig */, + 3447365D551A751DA6FDE2E3 /* Pods-Runner.release.xcconfig */, + B9C0C4513520F47128FD17DF /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + ED530326C6258CB46807CB96 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + F29023A8889557146A268C44 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + ED530326C6258CB46807CB96 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F29023A8889557146A268C44 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5N85PP82A9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.exampleRiverpod; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5N85PP82A9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.exampleRiverpod; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 5N85PP82A9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.exampleRiverpod; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/example_riverpod/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example_riverpod/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example_riverpod/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..c87d15a33 --- /dev/null +++ b/example_riverpod/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example_riverpod/ios/Runner.xcworkspace/contents.xcworkspacedata b/example_riverpod/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/example_riverpod/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/example_riverpod/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example_riverpod/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/example_riverpod/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example_riverpod/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example_riverpod/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/example_riverpod/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example_riverpod/ios/Runner/AppDelegate.swift b/example_riverpod/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/example_riverpod/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/example_riverpod/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example_riverpod/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example_riverpod/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/example_riverpod/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example_riverpod/ios/Runner/Base.lproj/Main.storyboard b/example_riverpod/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/example_riverpod/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example_riverpod/ios/Runner/Info.plist b/example_riverpod/ios/Runner/Info.plist new file mode 100644 index 000000000..e73077c8a --- /dev/null +++ b/example_riverpod/ios/Runner/Info.plist @@ -0,0 +1,53 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example Riverpod + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example_riverpod + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSCameraUsageDescription + 100ms needs access to Camera to enable high quality video conferencing. + NSLocalNetworkUsageDescription + 100ms needs access to Local Network to enable high quality video conferencing. + NSMicrophoneUsageDescription + 100ms needs access to Microphone to enable high quality video conferencing. + + diff --git a/example_riverpod/ios/Runner/Runner-Bridging-Header.h b/example_riverpod/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/example_riverpod/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example_riverpod/lib/hms_sdk_interactor.dart b/example_riverpod/lib/hms_sdk_interactor.dart new file mode 100644 index 000000000..fd16960b8 --- /dev/null +++ b/example_riverpod/lib/hms_sdk_interactor.dart @@ -0,0 +1,284 @@ +//Dart imports +import 'dart:io'; + +//Project imports +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class HMSSDKInteractor { + late HMSConfig config; + late List messages; + late HMSSDK hmsSDK; + + //Contains the default local camera mirroring settings + bool mirrorCamera = true; + //Contains the default RTC stats setting + bool showStats = false; + //Contains the default setting to jump directly in meeting i.e. skipping preview + bool skipPreview = false; + + HMSSDKInteractor() { + hmsSDK = HMSSDK(); + hmsSDK.build(); + } + + void join({required HMSConfig config}) async { + this.config = config; + hmsSDK.join(config: this.config); + } + + void leave({required HMSActionResultListener hmsActionResultListener}) async { + hmsSDK.leave(hmsActionResultListener: hmsActionResultListener); + } + + Future switchAudio({bool isOn = false}) async { + return await hmsSDK.switchAudio(isOn: isOn); + } + + Future switchVideo({bool isOn = false}) async { + return await hmsSDK.switchVideo(isOn: isOn); + } + + Future switchCamera() async { + return await hmsSDK.switchCamera(); + } + + Future isScreenShareActive() async { + if (Platform.isAndroid) { + return await hmsSDK.isScreenShareActive(); + } else { + return false; + } + } + + void sendBroadcastMessage( + String message, HMSActionResultListener hmsActionResultListener) { + hmsSDK.sendBroadcastMessage( + message: message, + type: "chat", + hmsActionResultListener: hmsActionResultListener); + } + + void sendDirectMessage(String message, HMSPeer peerTo, + HMSActionResultListener hmsActionResultListener) async { + hmsSDK.sendDirectMessage( + message: message, + peerTo: peerTo, + type: "chat", + hmsActionResultListener: hmsActionResultListener); + } + + void sendGroupMessage(String message, List hmsRolesTo, + HMSActionResultListener hmsActionResultListener) async { + hmsSDK.sendGroupMessage( + message: message, + hmsRolesTo: hmsRolesTo, + type: "chat", + hmsActionResultListener: hmsActionResultListener); + } + + Future preview({required HMSConfig config}) async { + this.config = config; + return hmsSDK.preview(config: config); + } + + void startHMSLogger(HMSLogLevel webRtclogLevel, HMSLogLevel logLevel) { + hmsSDK.startHMSLogger(webRtclogLevel: webRtclogLevel, logLevel: logLevel); + } + + void removeHMSLogger() { + hmsSDK.removeHMSLogger(); + } + + void addLogsListener(HMSLogListener hmsLogListener) { + hmsSDK.addLogListener(hmsLogListener: hmsLogListener); + } + + void removeLogsListener(HMSLogListener hmsLogListener) { + hmsSDK.removeLogListener(hmsLogListener: hmsLogListener); + } + + void addUpdateListener(HMSUpdateListener listener) { + hmsSDK.addUpdateListener(listener: listener); + } + + void removeUpdateListener(HMSUpdateListener listener) { + hmsSDK.removeUpdateListener(listener: listener); + } + + void addPreviewListener(HMSPreviewListener listener) { + hmsSDK.addPreviewListener(listener: listener); + } + + void removePreviewListener(HMSPreviewListener listener) { + hmsSDK.removePreviewListener(listener: listener); + } + + void acceptChangeRole(HMSRoleChangeRequest hmsRoleChangeRequest, + HMSActionResultListener hmsActionResultListener) { + hmsSDK.acceptChangeRole( + hmsRoleChangeRequest: hmsRoleChangeRequest, + hmsActionResultListener: hmsActionResultListener); + } + + void stopCapturing() { + hmsSDK.stopCapturing(); + } + + Future getLocalPeer() async { + return await hmsSDK.getLocalPeer(); + } + + Future startCapturing() async { + return await hmsSDK.startCapturing(); + } + + Future getPeer({required String peerId}) async { + List? peers = await hmsSDK.getPeers(); + + return peers?.firstWhere((element) => element.peerId == peerId); + } + + void changeTrackState(HMSTrack forRemoteTrack, bool mute, + HMSActionResultListener hmsActionResultListener) { + hmsSDK.changeTrackState( + forRemoteTrack: forRemoteTrack, + mute: mute, + hmsActionResultListener: hmsActionResultListener); + } + + void endRoom(bool lock, String reason, + HMSActionResultListener hmsActionResultListener) { + hmsSDK.endRoom( + lock: lock, + reason: reason, + hmsActionResultListener: hmsActionResultListener); + } + + void removePeer( + HMSPeer peer, HMSActionResultListener hmsActionResultListener) { + hmsSDK.removePeer( + peer: peer, + reason: "Removing Peer from Flutter", + hmsActionResultListener: hmsActionResultListener); + } + + void changeRole( + {required HMSPeer forPeer, + required HMSRole toRole, + bool force = false, + required HMSActionResultListener hmsActionResultListener}) { + hmsSDK.changeRole( + forPeer: forPeer, + toRole: toRole, + force: force, + hmsActionResultListener: hmsActionResultListener); + } + + Future> getRoles() async { + return await hmsSDK.getRoles(); + } + + Future isAudioMute(HMSPeer? peer) async { + return await hmsSDK.isAudioMute(peer: peer); + } + + Future isVideoMute(HMSPeer? peer) async { + return await hmsSDK.isVideoMute(peer: peer); + } + + void muteAll() { + // TODO: add permission checks in exmaple app UI + hmsSDK.muteAll(); + } + + void startScreenShare({HMSActionResultListener? hmsActionResultListener}) { + hmsSDK.startScreenShare(hmsActionResultListener: hmsActionResultListener); + } + + void stopScreenShare({HMSActionResultListener? hmsActionResultListener}) { + hmsSDK.stopScreenShare(hmsActionResultListener: hmsActionResultListener); + } + + void unMuteAll() { + hmsSDK.unMuteAll(); + } + + void setPlayBackAllowed(bool allow) { + hmsSDK.setPlayBackAllowed(allow: allow); + } + + void startRtmpOrRecording(HMSRecordingConfig hmsRecordingConfig, + HMSActionResultListener hmsActionResultListener) { + hmsSDK.startRtmpOrRecording( + hmsRecordingConfig: hmsRecordingConfig, + hmsActionResultListener: hmsActionResultListener); + } + + void stopRtmpAndRecording(HMSActionResultListener hmsActionResultListener) { + hmsSDK.stopRtmpAndRecording( + hmsActionResultListener: hmsActionResultListener); + } + + Future getRoom() async { + return await hmsSDK.getRoom(); + } + + void changeMetadata( + {required String metadata, + required HMSActionResultListener hmsActionResultListener}) { + hmsSDK.changeMetadata( + metadata: metadata, hmsActionResultListener: hmsActionResultListener); + } + + void changeName( + {required String name, + required HMSActionResultListener hmsActionResultListener}) { + hmsSDK.changeName( + name: name, hmsActionResultListener: hmsActionResultListener); + } + + void startHLSStreaming( + String meetingUrl, HMSActionResultListener hmsActionResultListener, + {bool singleFilePerLayer = false, bool enableVOD = false}) { + List hmsHlsMeetingUrls = []; + + hmsHlsMeetingUrls.add(HMSHLSMeetingURLVariant( + meetingUrl: meetingUrl, metadata: "HLS started from Flutter")); + HMSHLSRecordingConfig hmshlsRecordingConfig = HMSHLSRecordingConfig( + singleFilePerLayer: singleFilePerLayer, videoOnDemand: enableVOD); + HMSHLSConfig hmshlsConfig = HMSHLSConfig( + meetingURLVariant: hmsHlsMeetingUrls, + hmsHLSRecordingConfig: hmshlsRecordingConfig); + + hmsSDK.startHlsStreaming( + hmshlsConfig: hmshlsConfig, + hmsActionResultListener: hmsActionResultListener); + } + + void stopHLSStreaming( + {required HMSActionResultListener hmsActionResultListener}) { + hmsSDK.stopHlsStreaming(hmsActionResultListener: hmsActionResultListener); + } + + void changeTrackStateForRole(bool mute, HMSTrackKind? kind, String? source, + List? roles, HMSActionResultListener? hmsActionResultListener) { + hmsSDK.changeTrackStateForRole( + mute: mute, + kind: kind, + source: source, + roles: roles, + hmsActionResultListener: hmsActionResultListener); + } + + Future?> getPeers() async { + return await hmsSDK.getPeers(); + } + + void addStatsListener(HMSStatsListener listener) { + hmsSDK.addStatsListener(listener: listener); + } + + void removeStatsListener(HMSStatsListener listener) { + hmsSDK.removeStatsListener(listener: listener); + } +} diff --git a/example_riverpod/lib/main.dart b/example_riverpod/lib/main.dart new file mode 100644 index 000000000..e029c0606 --- /dev/null +++ b/example_riverpod/lib/main.dart @@ -0,0 +1,114 @@ +import 'dart:io'; + +import 'package:example_riverpod/preview/preview_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:permission_handler/permission_handler.dart'; + +void main() { + runApp(const ProviderScope(child: MyApp())); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: '100ms mobx', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + debugShowCheckedModeBanner: false, + home: const HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + _HomePageState createState() => _HomePageState(); +} + +Future getPermissions() async { + if (Platform.isIOS) return true; + await Permission.camera.request(); + await Permission.microphone.request(); + await Permission.bluetoothConnect.request(); + + while ((await Permission.camera.isDenied)) { + await Permission.camera.request(); + } + while ((await Permission.microphone.isDenied)) { + await Permission.microphone.request(); + } + while ((await Permission.bluetoothConnect.isDenied)) { + await Permission.bluetoothConnect.request(); + } + return true; +} + +class _HomePageState extends State { + TextEditingController txtName = TextEditingController(text: ""); + TextEditingController txtId = TextEditingController(text: ""); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("mobx Clone"), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Name", + style: TextStyle(fontSize: 20), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TextField( + controller: txtName, + decoration: const InputDecoration(hintText: 'Enter Your Name'), + ), + ), + const SizedBox( + height: 10, + ), + const Text( + "Enter Link", + style: TextStyle(fontSize: 20), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TextField( + controller: txtId, + decoration: const InputDecoration( + border: InputBorder.none, hintText: 'Enter Room Link'), + ), + ), + ElevatedButton( + onPressed: () async { + if (await getPermissions()) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PreviewScreen( + name: txtName.text, + roomLink: txtId.text, + )), + ); + } + }, + child: const Text( + "Join", + style: TextStyle(fontSize: 20), + )) + ], + ), + ); + } +} diff --git a/example_riverpod/lib/meeting/meeting_page.dart b/example_riverpod/lib/meeting/meeting_page.dart new file mode 100644 index 000000000..e9b91bec5 --- /dev/null +++ b/example_riverpod/lib/meeting/meeting_page.dart @@ -0,0 +1,96 @@ +import 'package:example_riverpod/hms_sdk_interactor.dart'; +import 'package:example_riverpod/meeting/meeting_store.dart'; +import 'package:example_riverpod/meeting/video_tiles.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class MeetingPage extends ConsumerStatefulWidget { + final String roomLink, name; + final bool isAudioOn; + final HMSSDKInteractor hmsSDKInteractor; + const MeetingPage( + {Key? key, + required this.name, + required this.roomLink, + required this.isAudioOn, + required this.hmsSDKInteractor}) + : super(key: key); + + @override + _MeetingPageState createState() => _MeetingPageState(); +} + +class _MeetingPageState extends ConsumerState { + late ChangeNotifierProvider meetingStoreProvider; + @override + void initState() { + super.initState(); + joinMeeting(); + } + + joinMeeting() async { + meetingStoreProvider = ChangeNotifierProvider((ref) { + return MeetingStore(hmsSDKInteractor: widget.hmsSDKInteractor); + }); + bool ans = + await ref.read(meetingStoreProvider).join(widget.name, widget.roomLink); + if (ans == false) { + Navigator.of(context).pop(); + } + if (!widget.isAudioOn) ref.read(meetingStoreProvider).switchAudio(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + return false; + }, + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text("Riverpod Example"), + automaticallyImplyLeading: false, + ), + body: VideoTiles( + peerTracks: ref.watch(meetingStoreProvider).peerTracks, + ), + bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + backgroundColor: Colors.black, + selectedItemColor: Colors.greenAccent, + unselectedItemColor: Colors.grey, + items: [ + const BottomNavigationBarItem( + icon: Icon(Icons.cancel), + label: 'Leave Meeting', + ), + BottomNavigationBarItem( + icon: Icon(ref.watch(meetingStoreProvider).isMicOn + ? Icons.mic + : Icons.mic_off), + label: 'Mic', + ), + BottomNavigationBarItem( + icon: Icon(ref.watch(meetingStoreProvider).isVideoOn + ? Icons.videocam + : Icons.videocam_off), + label: 'Camera', + ), + ], + onTap: _onItemTapped), + ), + ); + } + + void _onItemTapped(int index) { + if (index == 0) { + ref.read(meetingStoreProvider).leave(); + Navigator.pop(context); + } else if (index == 1) { + ref.read(meetingStoreProvider).switchAudio(); + } else { + ref.read(meetingStoreProvider).switchVideo(); + } + } +} diff --git a/example_riverpod/lib/meeting/meeting_store.dart b/example_riverpod/lib/meeting/meeting_store.dart new file mode 100644 index 000000000..c3191c9f3 --- /dev/null +++ b/example_riverpod/lib/meeting/meeting_store.dart @@ -0,0 +1,560 @@ +//Package imports + +import 'package:example_riverpod/hms_sdk_interactor.dart'; +import 'package:example_riverpod/meeting/peer_track_node.dart'; +import 'package:example_riverpod/service/room_service.dart'; +import 'package:flutter/material.dart'; + +//Project imports +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class MeetingStore extends ChangeNotifier + with WidgetsBindingObserver + implements HMSUpdateListener, HMSActionResultListener, HMSStatsListener { + late HMSSDKInteractor _hmsSDKInteractor; + + MeetingStore({required HMSSDKInteractor hmsSDKInteractor}) { + _hmsSDKInteractor = hmsSDKInteractor; + } + + // HMSLogListener + + HMSException? hmsException; + + bool isMeetingStarted = false; + + bool isVideoOn = true; + + bool isMicOn = true; + + bool reconnecting = false; + + bool reconnected = false; + + bool isRoomEnded = false; + + List roles = []; + + List peers = []; + + HMSPeer? localPeer; + + List audioTracks = []; + + List peerTracks = []; + + HMSRoom? hmsRoom; + + int firstTimeBuild = 0; + + Future join(String user, String roomUrl) async { + List? token = + await RoomService().getToken(user: user, room: roomUrl); + if (token == null) return false; + HMSConfig config = HMSConfig(authToken: token[0]!, userName: user); + + _hmsSDKInteractor.addUpdateListener(this); + WidgetsBinding.instance!.addObserver(this); + _hmsSDKInteractor.join(config: config); + return true; + } + + void leave() async { + _hmsSDKInteractor.removeStatsListener(this); + WidgetsBinding.instance!.removeObserver(this); + _hmsSDKInteractor.leave(hmsActionResultListener: this); + } + + Future switchAudio() async { + await _hmsSDKInteractor.switchAudio(isOn: isMicOn); + isMicOn = !isMicOn; + notifyListeners(); + } + + Future switchVideo() async { + await _hmsSDKInteractor.switchVideo(isOn: isVideoOn); + isVideoOn = !isVideoOn; + notifyListeners(); + } + + Future switchCamera() async { + await _hmsSDKInteractor.switchCamera(); + } + + void sendBroadcastMessage(String message) { + _hmsSDKInteractor.sendBroadcastMessage(message, this); + } + + void sendDirectMessage(String message, HMSPeer peer) async { + _hmsSDKInteractor.sendDirectMessage(message, peer, this); + } + + void sendGroupMessage(String message, List roles) async { + _hmsSDKInteractor.sendGroupMessage(message, roles, this); + } + + Future isAudioMute(HMSPeer? peer) async { + return await _hmsSDKInteractor.isAudioMute(peer); + } + + Future isVideoMute(HMSPeer? peer) async { + return await _hmsSDKInteractor.isVideoMute(peer); + } + + Future startCapturing() async { + return await _hmsSDKInteractor.startCapturing(); + } + + void stopCapturing() { + _hmsSDKInteractor.stopCapturing(); + } + + void removePeer(HMSPeer peer) { + peers.remove(peer); + // removeTrackWithPeerId(peer.peerId); + } + + void addPeer(HMSPeer peer) { + if (!peers.contains(peer)) peers.add(peer); + } + + void onRoleUpdated(int index, HMSPeer peer) { + peers[index] = peer; + } + + void updatePeerAt(peer) { + int index = this.peers.indexOf(peer); + this.peers.removeAt(index); + this.peers.insert(index, peer); + } + + @override + void onJoin({required HMSRoom room}) async { + isMeetingStarted = true; + hmsRoom = room; + for (HMSPeer each in room.peers!) { + if (each.isLocal) { + int index = peerTracks + .indexWhere((element) => element.uid == each.peerId + "mainVideo"); + if (index == -1) { + peerTracks + .add(PeerTrackNode(peer: each, uid: each.peerId + "mainVideo")); + } + localPeer = each; + addPeer(localPeer!); + index = peerTracks + .indexWhere((element) => element.uid == each.peerId + "mainVideo"); + if (each.videoTrack != null) { + if (each.videoTrack!.kind == HMSTrackKind.kHMSTrackKindVideo) { + peerTracks[index].track = each.videoTrack!; + if (each.videoTrack!.isMute) { + isVideoOn = false; + } + } + } + if (each.audioTrack != null) { + if (each.audioTrack!.kind == HMSTrackKind.kHMSTrackKindAudio) { + peerTracks[index].audioTrack = each.audioTrack!; + if (each.audioTrack!.isMute) { + isMicOn = false; + } + } + } + break; + } + } + roles = await getRoles(); + notifyListeners(); + } + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) {} + + @override + void onPeerUpdate( + {required HMSPeer peer, required HMSPeerUpdate update}) async { + peerOperation(peer, update); + } + + @override + void onTrackUpdate( + {required HMSTrack track, + required HMSTrackUpdate trackUpdate, + required HMSPeer peer}) { + if (peer.isLocal) { + if (track.kind == HMSTrackKind.kHMSTrackKindAudio && + track.source == "REGULAR") { + isMicOn = !track.isMute; + } + if (track.kind == HMSTrackKind.kHMSTrackKindVideo && + track.source == "REGULAR") { + isVideoOn = !track.isMute; + } + notifyListeners(); + } + + if (track.kind == HMSTrackKind.kHMSTrackKindAudio) { + int index = peerTracks + .indexWhere((element) => element.uid == peer.peerId + "mainVideo"); + if (index != -1) { + PeerTrackNode peerTrackNode = peerTracks[index]; + peerTrackNode.audioTrack = track as HMSAudioTrack; + peerTrackNode.notify(); + } + return; + } + + if (track.source == "REGULAR") { + int index = peerTracks + .indexWhere((element) => element.uid == peer.peerId + "mainVideo"); + if (index != -1) { + PeerTrackNode peerTrackNode = peerTracks[index]; + peerTrackNode.track = track as HMSVideoTrack; + peerTrackNode.notify(); + } else { + return; + } + } + } + + @override + void onHMSError({required HMSException error}) { + this.hmsException = hmsException; + notifyListeners(); + } + + @override + void onMessage({required HMSMessage message}) {} + + @override + void onRoleChangeRequest({required HMSRoleChangeRequest roleChangeRequest}) {} + + @override + void onUpdateSpeakers({required List updateSpeakers}) {} + + @override + void onReconnecting() { + reconnected = false; + reconnecting = true; + } + + @override + void onReconnected() { + reconnecting = false; + reconnected = true; + } + + int trackChange = -1; + + @override + void onChangeTrackStateRequest( + {required HMSTrackChangeRequest hmsTrackChangeRequest}) { + if (hmsTrackChangeRequest.track.kind == HMSTrackKind.kHMSTrackKindVideo) { + isVideoOn = false; + } else { + isMicOn = false; + } + notifyListeners(); + } + + void rejectrequest() { + notifyListeners(); + } + + @override + void onRemovedFromRoom( + {required HMSPeerRemovedFromPeer hmsPeerRemovedFromPeer}) { + peerTracks.clear(); + isRoomEnded = true; + notifyListeners(); + } + + void changeRole( + {required HMSPeer peer, + required HMSRole roleName, + bool forceChange = false}) { + _hmsSDKInteractor.changeRole( + toRole: roleName, + forPeer: peer, + force: forceChange, + hmsActionResultListener: this); + } + + Future> getRoles() async { + return await _hmsSDKInteractor.getRoles(); + } + + void changeTrackState(HMSTrack track, bool mute) { + return _hmsSDKInteractor.changeTrackState(track, mute, this); + } + + void peerOperation(HMSPeer peer, HMSPeerUpdate update) { + switch (update) { + case HMSPeerUpdate.peerJoined: + if (peer.role.name.contains("hls-") == false) { + int index = peerTracks.indexWhere( + (element) => element.uid == peer.peerId + "mainVideo"); + if (index == -1) { + peerTracks + .add(PeerTrackNode(peer: peer, uid: peer.peerId + "mainVideo")); + } + notifyListeners(); + } + addPeer(peer); + break; + + case HMSPeerUpdate.peerLeft: + peerTracks.removeWhere( + (leftPeer) => leftPeer.uid == peer.peerId + "mainVideo"); + removePeer(peer); + notifyListeners(); + break; + + case HMSPeerUpdate.roleUpdated: + if (peer.isLocal) localPeer = peer; + + int index = peerTracks + .indexWhere((element) => element.uid == peer.peerId + "mainVideo"); + if (index == -1) { + peerTracks + .add(PeerTrackNode(peer: peer, uid: peer.peerId + "mainVideo")); + } + + updatePeerAt(peer); + notifyListeners(); + break; + + case HMSPeerUpdate.metadataChanged: + int index = peerTracks + .indexWhere((element) => element.uid == peer.peerId + "mainVideo"); + if (index != -1) { + PeerTrackNode peerTrackNode = peerTracks[index]; + peerTrackNode.peer = peer; + peerTrackNode.notify(); + } + updatePeerAt(peer); + break; + + case HMSPeerUpdate.nameChanged: + if (peer.isLocal) { + int localPeerIndex = peerTracks.indexWhere( + (element) => element.uid == localPeer!.peerId + "mainVideo"); + if (localPeerIndex != -1) { + PeerTrackNode peerTrackNode = peerTracks[localPeerIndex]; + peerTrackNode.peer = peer; + localPeer = peer; + peerTrackNode.notify(); + } + } else { + int remotePeerIndex = peerTracks.indexWhere( + (element) => element.uid == peer.peerId + "mainVideo"); + if (remotePeerIndex != -1) { + PeerTrackNode peerTrackNode = peerTracks[remotePeerIndex]; + peerTrackNode.peer = peer; + peerTrackNode.notify(); + } + } + updatePeerAt(peer); + break; + + case HMSPeerUpdate.networkQualityUpdated: + break; + + case HMSPeerUpdate.defaultUpdate: + break; + + default: + } + } + + Future getLocalPeer() async { + return await _hmsSDKInteractor.getLocalPeer(); + } + + Future getRoom() async { + HMSRoom? room = await _hmsSDKInteractor.getRoom(); + return room; + } + + Future getPeer({required String peerId}) async { + return await _hmsSDKInteractor.getPeer(peerId: peerId); + } + + @override + void onLocalAudioStats( + {required HMSLocalAudioStats hmsLocalAudioStats, + required HMSLocalAudioTrack track, + required HMSPeer peer}) {} + + @override + void onLocalVideoStats( + {required HMSLocalVideoStats hmsLocalVideoStats, + required HMSLocalVideoTrack track, + required HMSPeer peer}) {} + + @override + void onRemoteAudioStats( + {required HMSRemoteAudioStats hmsRemoteAudioStats, + required HMSRemoteAudioTrack track, + required HMSPeer peer}) {} + + @override + void onRemoteVideoStats( + {required HMSRemoteVideoStats hmsRemoteVideoStats, + required HMSRemoteVideoTrack track, + required HMSPeer peer}) {} + + @override + void onRTCStats({required HMSRTCStatsReport hmsrtcStatsReport}) {} + + @override + void onSuccess( + {HMSActionResultListenerMethod methodType = + HMSActionResultListenerMethod.unknown, + Map? arguments}) { + switch (methodType) { + case HMSActionResultListenerMethod.leave: + peerTracks.clear(); + isRoomEnded = true; + notifyListeners(); + break; + case HMSActionResultListenerMethod.changeTrackState: + break; + case HMSActionResultListenerMethod.changeMetadata: + notifyListeners(); + break; + case HMSActionResultListenerMethod.endRoom: + this.isRoomEnded = true; + notifyListeners(); + break; + case HMSActionResultListenerMethod.removePeer: + // TODO: Handle this case. + break; + case HMSActionResultListenerMethod.acceptChangeRole: + break; + case HMSActionResultListenerMethod.changeRole: + break; + case HMSActionResultListenerMethod.changeTrackStateForRole: + break; + case HMSActionResultListenerMethod.startRtmpOrRecording: + break; + case HMSActionResultListenerMethod.stopRtmpAndRecording: + break; + case HMSActionResultListenerMethod.unknown: + break; + case HMSActionResultListenerMethod.changeName: + break; + case HMSActionResultListenerMethod.sendBroadcastMessage: + break; + case HMSActionResultListenerMethod.sendGroupMessage: + break; + case HMSActionResultListenerMethod.sendDirectMessage: + break; + case HMSActionResultListenerMethod.hlsStreamingStarted: + break; + case HMSActionResultListenerMethod.hlsStreamingStopped: + break; + + case HMSActionResultListenerMethod.startScreenShare: + break; + + case HMSActionResultListenerMethod.stopScreenShare: + break; + } + } + + @override + void onException( + {HMSActionResultListenerMethod methodType = + HMSActionResultListenerMethod.unknown, + Map? arguments, + required HMSException hmsException}) { + this.hmsException = hmsException; + switch (methodType) { + case HMSActionResultListenerMethod.leave: + break; + case HMSActionResultListenerMethod.changeTrackState: + break; + case HMSActionResultListenerMethod.changeMetadata: + // TODO: Handle this case. + break; + case HMSActionResultListenerMethod.endRoom: + break; + case HMSActionResultListenerMethod.removePeer: + break; + case HMSActionResultListenerMethod.acceptChangeRole: + break; + case HMSActionResultListenerMethod.changeRole: + break; + case HMSActionResultListenerMethod.changeTrackStateForRole: + break; + case HMSActionResultListenerMethod.startRtmpOrRecording: + break; + case HMSActionResultListenerMethod.stopRtmpAndRecording: + break; + case HMSActionResultListenerMethod.changeName: + break; + case HMSActionResultListenerMethod.sendBroadcastMessage: + // TODO: Handle this case. + break; + case HMSActionResultListenerMethod.sendGroupMessage: + // TODO: Handle this case. + break; + case HMSActionResultListenerMethod.sendDirectMessage: + // TODO: Handle this case. + break; + case HMSActionResultListenerMethod.hlsStreamingStarted: + break; + case HMSActionResultListenerMethod.hlsStreamingStopped: + break; + case HMSActionResultListenerMethod.startScreenShare: + break; + case HMSActionResultListenerMethod.stopScreenShare: + break; + case HMSActionResultListenerMethod.unknown: + break; + } + } + + Future?> getPeers() async { + return await _hmsSDKInteractor.getPeers(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) async { + super.didChangeAppLifecycleState(state); + + if (state == AppLifecycleState.resumed) { + List? peersList = await getPeers(); + + peersList?.forEach((element) { + if (!element.isLocal) { + (element.audioTrack as HMSRemoteAudioTrack?)?.setVolume(10.0); + element.auxiliaryTracks?.forEach((element) { + if (element.kind == HMSTrackKind.kHMSTrackKindAudio) { + (element as HMSRemoteAudioTrack?)?.setVolume(10.0); + } + }); + } else { + if ((element.videoTrack != null && isVideoOn)) startCapturing(); + } + }); + } else if (state == AppLifecycleState.paused) { + HMSLocalPeer? localPeer = await getLocalPeer(); + if (localPeer != null && !(localPeer.videoTrack?.isMute ?? true)) { + stopCapturing(); + } + for (PeerTrackNode peerTrackNode in peerTracks) { + peerTrackNode.setOffScreenStatus(true); + } + } else if (state == AppLifecycleState.inactive) { + HMSLocalPeer? localPeer = await getLocalPeer(); + if (localPeer != null && !(localPeer.videoTrack?.isMute ?? true)) { + stopCapturing(); + } + for (PeerTrackNode peerTrackNode in peerTracks) { + peerTrackNode.setOffScreenStatus(true); + } + } + } +} diff --git a/example_riverpod/lib/meeting/peer_track_node.dart b/example_riverpod/lib/meeting/peer_track_node.dart new file mode 100644 index 000000000..6b5eff278 --- /dev/null +++ b/example_riverpod/lib/meeting/peer_track_node.dart @@ -0,0 +1,43 @@ +//Package Imports +import 'package:flutter/foundation.dart'; + +//Project Imports +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class PeerTrackNode extends ChangeNotifier { + HMSPeer peer; + String uid; + HMSVideoTrack? track; + HMSAudioTrack? audioTrack; + bool isOffscreen; + + PeerTrackNode({ + required this.peer, + this.track, + this.audioTrack, + required this.uid, + this.isOffscreen = true, + }); + + @override + String toString() { + return 'PeerTrackNode{peerId: ${peer.peerId}, name: ${peer.name}, track: $track}, isVideoOn: $isOffscreen }'; + } + + @override + int get hashCode => peer.peerId.hashCode; + + void notify() { + notifyListeners(); + } + + void setOffScreenStatus(bool currentState) { + this.isOffscreen = currentState; + notify(); + } + + @override + bool operator ==(Object other) { + return super == other; + } +} diff --git a/example_riverpod/lib/meeting/video_tiles.dart b/example_riverpod/lib/meeting/video_tiles.dart new file mode 100644 index 000000000..493a7aa3b --- /dev/null +++ b/example_riverpod/lib/meeting/video_tiles.dart @@ -0,0 +1,84 @@ +import 'package:example_riverpod/meeting/peer_track_node.dart'; +import 'package:example_riverpod/service/utility_function.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class VideoTiles extends StatelessWidget { + final List peerTracks; + const VideoTiles({Key? key, required this.peerTracks}) : super(key: key); + + @override + Widget build(BuildContext context) { + return GridView.builder( + itemCount: peerTracks.length, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, crossAxisSpacing: 2), + itemBuilder: (context, index) { + final peerTrackProvider = ChangeNotifierProvider((ref) { + return peerTracks[index]; + }); + return Stack( + children: [ + Consumer( + builder: (context, ref, child) { + HMSVideoTrack? videoTrack = ref.watch(peerTrackProvider).track; + if (videoTrack != null && videoTrack.isMute == false) { + return HMSVideoView( + track: videoTrack, + scaleType: ScaleType.SCALE_ASPECT_FILL, + ); + } else { + return SizedBox( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + child: Center( + child: CircleAvatar( + backgroundColor: Utilities.getBackgroundColour( + ref.read(peerTrackProvider).peer.name), + radius: 36, + child: Text( + Utilities.getAvatarTitle( + ref.read(peerTrackProvider).peer.name), + style: const TextStyle( + fontSize: 36, + color: Colors.white, + ), + ))), + ); + } + }, + ), + Align( + alignment: Alignment.bottomCenter, + child: Consumer(builder: (context, ref, child) { + HMSPeer hmsPeer = ref.watch(peerTrackProvider).peer; + return Text(hmsPeer.name); + }), + ), + Positioned( + top: 5, + right: 5, + child: Consumer(builder: (context, ref, child) { + HMSAudioTrack? audioTrack = + ref.watch(peerTrackProvider).audioTrack; + if (audioTrack != null && audioTrack.isMute == false) { + return const SizedBox(); + } else { + return const Icon( + Icons.mic_off, + color: Colors.red, + ); + } + }), + ), + Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + decoration: BoxDecoration(border: Border.all(width: 1))) + ], + ); + }, + ); + } +} diff --git a/example_riverpod/lib/preview/preview_screen.dart b/example_riverpod/lib/preview/preview_screen.dart new file mode 100644 index 000000000..e322a272d --- /dev/null +++ b/example_riverpod/lib/preview/preview_screen.dart @@ -0,0 +1,175 @@ +import 'package:example_riverpod/meeting/meeting_page.dart'; +import 'package:example_riverpod/preview/preview_store.dart'; +import 'package:example_riverpod/service/utility_function.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class PreviewScreen extends ConsumerStatefulWidget { + final String roomLink; + final String name; + + const PreviewScreen({Key? key, required this.name, required this.roomLink}) + : super(key: key); + + @override + _PreviewScreenState createState() => _PreviewScreenState(); +} + +class _PreviewScreenState extends ConsumerState { + final previewStoreProvider = ChangeNotifierProvider((ref) { + return PreviewStore(); + }); + + @override + void initState() { + super.initState(); + initPreview(); + } + + void initPreview() async { + bool ans = await ref + .read(previewStoreProvider) + .startPreview(user: widget.name, roomId: widget.roomLink); + if (ans == false) { + Navigator.of(context).pop(); + } + } + + @override + Widget build(BuildContext context) { + var size = MediaQuery.of(context).size; + final double itemHeight = size.height; + final double itemWidth = size.width; + final _previewStore = ref.watch(previewStoreProvider); + return WillPopScope( + onWillPop: () async { + _previewStore.removePreviewListener(); + return true; + }, + child: Scaffold( + body: Stack( + children: [ + (_previewStore.localTracks.isEmpty) + ? const Align( + alignment: Alignment.center, + child: CircularProgressIndicator()) + : SizedBox( + height: itemHeight, + width: itemWidth, + child: (_previewStore.isVideoOn) + ? HMSVideoView( + scaleType: ScaleType.SCALE_ASPECT_FILL, + track: _previewStore.localTracks[0], + matchParent: false, + ) + : SizedBox( + height: itemHeight, + width: itemWidth, + child: Center( + child: CircleAvatar( + backgroundColor: + Utilities.getBackgroundColour( + _previewStore.peer!.name), + radius: 36, + child: Text( + Utilities.getAvatarTitle( + _previewStore.peer!.name), + style: const TextStyle( + fontSize: 36, + color: Colors.white, + ), + ))), + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Align( + alignment: Alignment.bottomCenter, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // if (context.read().peer != null && + // context.read().peer!.role.publishSettings!.allowed + // .contains("video")) + (_previewStore.peer != null && + _previewStore.peer!.role.publishSettings!.allowed + .contains("video")) + ? GestureDetector( + onTap: _previewStore.localTracks.isEmpty + ? null + : () async { + _previewStore.switchVideo( + isOn: _previewStore.isVideoOn); + }, + child: CircleAvatar( + radius: 25, + backgroundColor: + Colors.transparent.withOpacity(0.2), + child: (_previewStore.isVideoOn) + ? const Icon( + Icons.videocam, + color: Colors.blue, + ) + : const Icon( + Icons.videocam_off, + color: Colors.blue, + ), + ), + ) + : Container(), + _previewStore.peer != null + ? ElevatedButton( + style: + ElevatedButton.styleFrom(primary: Colors.blue), + onPressed: () { + _previewStore.removePreviewListener(); + Navigator.of(context) + .pushReplacement(MaterialPageRoute( + builder: (_) => MeetingPage( + roomLink: widget.roomLink, + name: widget.name, + isAudioOn: _previewStore.isAudioOn, + hmsSDKInteractor: + _previewStore.hmsSDKInteractor, + ), + )); + }, + child: const Text( + 'Join Now', + style: TextStyle(height: 1, fontSize: 18), + ), + ) + : const SizedBox(), + (_previewStore.peer != null && + _previewStore.peer!.role.publishSettings!.allowed + .contains("audio")) + ? GestureDetector( + onTap: () async { + _previewStore.switchAudio(); + }, + child: CircleAvatar( + radius: 25, + backgroundColor: + Colors.transparent.withOpacity(0.2), + child: (_previewStore.isAudioOn) + ? const Icon( + Icons.mic, + color: Colors.blue, + ) + : const Icon( + Icons.mic_off, + color: Colors.blue, + )), + ) + : Container(), + ], + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/example_riverpod/lib/preview/preview_store.dart b/example_riverpod/lib/preview/preview_store.dart new file mode 100644 index 000000000..609d31d03 --- /dev/null +++ b/example_riverpod/lib/preview/preview_store.dart @@ -0,0 +1,106 @@ +//Package imports +import 'package:example_riverpod/hms_sdk_interactor.dart'; +import 'package:example_riverpod/service/room_service.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class PreviewStore extends ChangeNotifier + implements HMSPreviewListener, HMSLogListener { + late HMSSDKInteractor hmsSDKInteractor; + + PreviewStore() { + hmsSDKInteractor = HMSSDKInteractor(); + } + + List localTracks = []; + + HMSPeer? peer; + + HMSException? error; + + HMSRoom? room; + + bool isVideoOn = true; + + bool isAudioOn = true; + + int? networkQuality; + + @override + void onHMSError({required HMSException error}) { + updateError(error); + } + + @override + void onPreview({required HMSRoom room, required List localTracks}) { + this.room = room; + for (HMSPeer each in room.peers!) { + if (each.isLocal) { + peer = each; + break; + } + } + List videoTracks = []; + for (var track in localTracks) { + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + videoTracks.add(track as HMSVideoTrack); + } + } + this.localTracks = videoTracks; + notifyListeners(); + } + + Future startPreview( + {required String user, required String roomId}) async { + List? token = + await RoomService().getToken(user: user, room: roomId); + + if (token == null) return false; + if (token[0] == null) return false; + HMSConfig config = HMSConfig(authToken: token[0]!, userName: user); + hmsSDKInteractor.addPreviewListener(this); + hmsSDKInteractor.preview(config: config); + return true; + } + + @override + void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) {} + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) {} + + void removePreviewListener() { + hmsSDKInteractor.removePreviewListener(this); + } + + void stopCapturing() { + hmsSDKInteractor.stopCapturing(); + } + + void startCapturing() { + hmsSDKInteractor.startCapturing(); + } + + void switchVideo({bool isOn = false}) { + hmsSDKInteractor.switchVideo(isOn: isOn); + isVideoOn = !isVideoOn; + notifyListeners(); + } + + void switchAudio({bool isOn = false}) { + hmsSDKInteractor.switchAudio(isOn: isOn); + isAudioOn = !isAudioOn; + notifyListeners(); + } + + void addLogsListener(HMSLogListener hmsLogListener) {} + + void removeLogsListener(HMSLogListener hmsLogListener) {} + + @override + void onLogMessage({required hmsLogList}) {} + + void updateError(HMSException error) { + this.error = error; + } +} diff --git a/example_riverpod/lib/service/room_service.dart b/example_riverpod/lib/service/room_service.dart new file mode 100644 index 000000000..b432bcda4 --- /dev/null +++ b/example_riverpod/lib/service/room_service.dart @@ -0,0 +1,57 @@ +//dart imports +import 'dart:convert'; + +//Package imports +import 'package:http/http.dart' as http; + +class RoomService { + Future?> getToken( + {required String user, required String room}) async { + List codeAndDomain = getCode(room) ?? []; + if (codeAndDomain.isEmpty) { + return null; + } + Uri endPoint = codeAndDomain[2] == "true" + ? Uri.parse("https://prod-in.100ms.live/hmsapi/get-token") + : Uri.parse("https://qa-in.100ms.live/hmsapi/get-token"); + http.Response response = await http.post(endPoint, body: { + 'code': (codeAndDomain[1] ?? "").trim(), + 'user_id': user, + }, headers: { + 'subdomain': (codeAndDomain[0] ?? "").trim() + }); + + var body = json.decode(response.body); + return [body['token'], codeAndDomain[2]!.trim()]; + } + + List? getCode(String roomUrl) { + String url = roomUrl; + if (url == "") return []; + url = url.trim(); + bool isProdM = url.contains(".app.100ms.live/meeting/"); + bool isProdP = url.contains(".app.100ms.live/preview/"); + bool isQaM = url.contains(".qa-app.100ms.live/meeting/"); + bool isQaP = url.contains(".qa-app.100ms.live/preview/"); + + if (!isProdM && !isQaM && isQaP && isProdP) return []; + + List codeAndDomain = []; + String code = ""; + String subDomain = ""; + if (isProdM || isProdP) { + codeAndDomain = isProdM + ? url.split(".app.100ms.live/meeting/") + : url.split(".app.100ms.live/preview/"); + code = codeAndDomain[1]; + subDomain = codeAndDomain[0].split("https://")[1] + ".app.100ms.live"; + } else if (isQaM || isQaP) { + codeAndDomain = isQaM + ? url.split(".qa-app.100ms.live/meeting/") + : url.split(".qa-app.100ms.live/preview/"); + code = codeAndDomain[1]; + subDomain = codeAndDomain[0].split("https://")[1] + ".qa-app.100ms.live"; + } + return [subDomain, code, isProdM || isProdP ? "true" : "false"]; + } +} diff --git a/example_riverpod/lib/service/utility_function.dart b/example_riverpod/lib/service/utility_function.dart new file mode 100644 index 000000000..943c2d5fe --- /dev/null +++ b/example_riverpod/lib/service/utility_function.dart @@ -0,0 +1,41 @@ +//Package imports +import 'package:flutter/material.dart'; + +class Utilities { + static String getAvatarTitle(String name) { + List? parts = name.trim().split(" "); + if (parts.length == 1) { + name = parts[0][0]; + } else if (parts.length >= 2) { + name = parts[0][0]; + if (parts[1] == "" || parts[1] == " ") { + name += parts[0][1]; + } else { + name += parts[1][0]; + } + } + return name.toUpperCase(); + } + + static Color getBackgroundColour(String name) { + return Utilities + .colors[name.toUpperCase().codeUnitAt(0) % Utilities.colors.length]; + } + + static List colors = [ + Color(0xFFFAC919), + Color(0xFF00AE63), + Color(0xFF6554C0), + Color(0xFFF69133), + Color(0xFF8FF5FB) + ]; + + static double getRatio(Size size, BuildContext context) { + EdgeInsets viewPadding = MediaQuery.of(context).viewPadding; + return (size.height - + viewPadding.top - + viewPadding.bottom - + kToolbarHeight) / + (size.width - viewPadding.left - viewPadding.right); + } +} diff --git a/example_riverpod/pubspec.lock b/example_riverpod/pubspec.lock new file mode 100644 index 000000000..da31998d5 --- /dev/null +++ b/example_riverpod/pubspec.lock @@ -0,0 +1,217 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + hmssdk_flutter: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.7.3" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + riverpod: + dependency: transitive + description: + name: riverpod + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + state_notifier: + dependency: transitive + description: + name: state_notifier + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.2+1" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.8" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" +sdks: + dart: ">=2.16.2 <3.0.0" + flutter: ">=1.20.0" diff --git a/example_riverpod/pubspec.yaml b/example_riverpod/pubspec.yaml new file mode 100644 index 000000000..fe5068d85 --- /dev/null +++ b/example_riverpod/pubspec.yaml @@ -0,0 +1,92 @@ +name: example_riverpod +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.16.2 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + hmssdk_flutter: + path: ../ + + cupertino_icons: + flutter_riverpod: + http: + permission_handler: + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example_riverpod/test/widget_test.dart b/example_riverpod/test/widget_test.dart new file mode 100644 index 000000000..58cd5d423 --- /dev/null +++ b/example_riverpod/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example_riverpod/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/ios/Classes/Models/HMSAudioAction.swift b/ios/Classes/Models/HMSAudioAction.swift index a387d3669..118ce07d6 100644 --- a/ios/Classes/Models/HMSAudioAction.swift +++ b/ios/Classes/Models/HMSAudioAction.swift @@ -9,45 +9,45 @@ import Foundation import HMSSDK class HMSAudioAction{ - static func audioActions(_ call: FlutterMethodCall, result: @escaping FlutterResult,hmsSDK:HMSSDK?) { - switch call.method { - case "switch_audio": - switchAudio(call, result,hmsSDK: hmsSDK) - - case "is_audio_mute": - isAudioMute(call, result,hmsSDK: hmsSDK) - - case "mute_all": - toggleAudioMuteAll(result, shouldMute: true,hmsSDK: hmsSDK) - - case "un_mute_all": - toggleAudioMuteAll(result, shouldMute: false,hmsSDK: hmsSDK) - - case "set_volume": - setVolume(call, result,hmsSDK: hmsSDK) - default: - result(FlutterMethodNotImplemented) + static func audioActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + switch call.method { + case "switch_audio": + switchAudio(call, result, hmsSDK) + + case "is_audio_mute": + isAudioMute(call, result, hmsSDK) + + case "mute_all": + toggleAudioMuteAll(result, hmsSDK, shouldMute: true) + + case "un_mute_all": + toggleAudioMuteAll(result, hmsSDK, shouldMute: false) + + case "set_volume": + setVolume(call, result, hmsSDK) + default: + result(FlutterMethodNotImplemented) } } - static private func switchAudio(_ call: FlutterMethodCall, _ result: FlutterResult,hmsSDK:HMSSDK?) { + static private func switchAudio(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { let arguments = call.arguments as! [AnyHashable: Any] - + guard let shouldMute = arguments["is_on"] as? Bool, let peer = hmsSDK?.localPeer, let audio = peer.audioTrack as? HMSLocalAudioTrack else { - result(false) - return - } - + result(false) + return + } + audio.setMute(shouldMute) - + result(true) } - - static private func isAudioMute(_ call: FlutterMethodCall, _ result: FlutterResult,hmsSDK:HMSSDK?) { + + static private func isAudioMute(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { let arguments = call.arguments as! [AnyHashable: Any] - + if let peerID = arguments["peer_id"] as? String, let peer = HMSCommonAction.getPeer(by: peerID,hmsSDK: hmsSDK) { if let audio = peer.audioTrack { result(audio.isMute()) @@ -59,12 +59,12 @@ class HMSAudioAction{ return } } - + result(false) } - - static private func toggleAudioMuteAll(_ result: FlutterResult, shouldMute: Bool,hmsSDK:HMSSDK?) { - + + static private func toggleAudioMuteAll(_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?, shouldMute: Bool) { + hmsSDK?.remotePeers?.forEach { peer in if let audio = peer.remoteAudioTrack() { audio.setPlaybackAllowed(!shouldMute) @@ -75,32 +75,32 @@ class HMSAudioAction{ } } } - + result(nil) } - - static private func setVolume(_ call: FlutterMethodCall, _ result: FlutterResult,hmsSDK:HMSSDK?) { + + static private func setVolume(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { let arguments = call.arguments as! [AnyHashable: Any] - + guard let volume = arguments["volume"] as? Double, let trackID = arguments["track_id"] as? String, let track = HMSUtilities.getTrack(for: trackID, in: hmsSDK!.room!) else { let error = HMSCommonAction.getError(message: "Invalid arguments passed in \(#function)", - description: "Message is nil", - params: ["function": #function, "arguments": arguments]) + description: "Message is nil", + params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } - + if let remoteAudio = track as? HMSRemoteAudioTrack { remoteAudio.setVolume(volume) result(nil) return } - + let error = HMSCommonAction.getError(message: "Invalid arguments passed in \(#function)", - params: ["function": #function, "arguments": arguments]) + params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) } } diff --git a/ios/Classes/Models/HMSHLSAction.swift b/ios/Classes/Models/HMSHLSAction.swift index 39af752eb..23a48c256 100644 --- a/ios/Classes/Models/HMSHLSAction.swift +++ b/ios/Classes/Models/HMSHLSAction.swift @@ -9,32 +9,41 @@ import Foundation import HMSSDK class HMSHLSAction { - static func hlsActions(_ call: FlutterMethodCall, result: @escaping FlutterResult, hmsSDK:HMSSDK?) { - switch call.method { - case "hls_start_streaming": - startHlsStreaming(call, result,hmsSDK: hmsSDK) - - case "hls_stop_streaming": - stopHlsStreaming(call, result,hmsSDK: hmsSDK) - default: - result(FlutterMethodNotImplemented) + static func hlsActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { + switch call.method { + case "hls_start_streaming": + startHlsStreaming(call, result, hmsSDK) + + case "hls_stop_streaming": + stopHlsStreaming(call, result, hmsSDK) + default: + result(FlutterMethodNotImplemented) } } - static private func startHlsStreaming(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, hmsSDK:HMSSDK?) { + static private func startHlsStreaming(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { let arguments = call.arguments as! [AnyHashable: Any] guard let meetingUrlVariantsList = arguments["meeting_url_variants"] as? [[String: String]] else { let error = HMSCommonAction.getError(message: "Wrong Paramenter found in \(#function)", - description: "Paramenter is nil", - params: ["function": #function, "arguments": arguments]) + description: "Paramenter is nil", + params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } + let recordingConfig = arguments["recording_config"] as? [String:Bool]? ?? nil + var meetingUrlVariant = [HMSHLSMeetingURLVariant]() meetingUrlVariantsList.forEach { meetingUrlVariant.append(HMSHLSMeetingURLVariant(meetingURL: URL(string: $0["meeting_url"]!)!, metadata: $0["meta_data"] ?? "")) } - - let hlsConfig = HMSHLSConfig(variants: meetingUrlVariant) + + var hlsConfig:HMSHLSConfig + + if(recordingConfig==nil){ + hlsConfig = HMSHLSConfig(variants: meetingUrlVariant) + }else{ + let hmsHLSRecordingConfig = HMSHLSRecordingConfig(singleFilePerLayer: recordingConfig!["single_file_per_layer"]!, enableVOD: recordingConfig!["video_on_demand"]!) + hlsConfig = HMSHLSConfig(variants: meetingUrlVariant,recording: hmsHLSRecordingConfig) + } hmsSDK?.startHLSStreaming(config: hlsConfig) { _, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) @@ -43,8 +52,8 @@ class HMSHLSAction { } } } - - static private func stopHlsStreaming(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, hmsSDK:HMSSDK?) { + + static private func stopHlsStreaming(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { let arguments = call.arguments as? [AnyHashable: Any] let config = getHLSConfig(from: arguments) hmsSDK?.stopHLSStreaming(config: config) { _, error in @@ -55,14 +64,14 @@ class HMSHLSAction { } } } - + static private func getHLSConfig(from arguments: [AnyHashable: Any]?) -> HMSHLSConfig? { guard let meetingUrlVariantsList = arguments?["meeting_url_variants"] as? [[String: String]] else { return nil } var meetingUrlVariant = [HMSHLSMeetingURLVariant]() meetingUrlVariantsList.forEach { meetingUrlVariant.append(HMSHLSMeetingURLVariant(meetingURL: URL(string: $0["meeting_url"]!)!, metadata: $0["meta_data"] ?? "")) } - + return HMSHLSConfig(variants: meetingUrlVariant) } } diff --git a/ios/Classes/Models/HMSMessageAction.swift b/ios/Classes/Models/HMSMessageAction.swift index 61ee04456..c6d805a5c 100644 --- a/ios/Classes/Models/HMSMessageAction.swift +++ b/ios/Classes/Models/HMSMessageAction.swift @@ -9,35 +9,35 @@ import Foundation import HMSSDK class HMSMessageAction { - static func messageActions(_ call: FlutterMethodCall, result: @escaping FlutterResult,hmsSDK:HMSSDK?) { - switch call.method { - case "send_broadcast_message": - sendBroadcastMessage(call, result, hmsSDK: hmsSDK) - - case "send_direct_message": - sendDirectMessage(call, result, hmsSDK: hmsSDK) - - case "send_group_message": - sendGroupMessage(call, result, hmsSDK: hmsSDK) - default: - result(FlutterMethodNotImplemented) + static func messageActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { + switch call.method { + case "send_broadcast_message": + sendBroadcastMessage(call, result, hmsSDK) + + case "send_direct_message": + sendDirectMessage(call, result, hmsSDK) + + case "send_group_message": + sendGroupMessage(call, result, hmsSDK) + default: + result(FlutterMethodNotImplemented) } } - static private func sendBroadcastMessage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, hmsSDK:HMSSDK?) { + static private func sendBroadcastMessage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { let arguments = call.arguments as! [AnyHashable: Any] - + guard let message = arguments["message"] as? String else { let error = HMSCommonAction.getError(message: "No message found in \(#function)", - description: "Message is nil", - params: ["function": #function, "arguments": arguments]) + description: "Message is nil", + params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } - + let type = arguments["type"] as? String ?? "chat" - + hmsSDK?.sendBroadcastMessage(type: type, message: message) { _, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) @@ -46,24 +46,24 @@ class HMSMessageAction { } } } - - static private func sendDirectMessage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, hmsSDK:HMSSDK?) { - + + static private func sendDirectMessage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + let arguments = call.arguments as! [AnyHashable: Any] - + guard let message = arguments["message"] as? String, let peerID = arguments["peer_id"] as? String, let peer = HMSCommonAction.getPeer(by: peerID,hmsSDK: hmsSDK) else { let error = HMSCommonAction.getError(message: "Invalid arguments passed in \(#function)", - description: "Message is nil", - params: ["function": #function, "arguments": arguments]) + description: "Message is nil", + params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } - + let type = arguments["type"] as? String ?? "chat" - + hmsSDK?.sendDirectMessage(type: type, message: message, peer: peer) { _, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) @@ -72,24 +72,24 @@ class HMSMessageAction { } } } - - static private func sendGroupMessage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, hmsSDK:HMSSDK?) { - + + static private func sendGroupMessage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + let arguments = call.arguments as! [AnyHashable: Any] - + guard let message = arguments["message"] as? String, let rolesList = arguments["roles"] as? [String], let roles: [HMSRole] = (hmsSDK?.roles.filter { rolesList.contains($0.name) }) else { let error = HMSCommonAction.getError(message: "Invalid arguments passed in \(#function)", - description: "Message is nil", - params: ["function": #function, "arguments": arguments]) + description: "Message is nil", + params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } - + let type = arguments["type"] as? String ?? "chat" - + hmsSDK?.sendGroupMessage(type: type, message: message, roles: roles) { _, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) @@ -98,5 +98,5 @@ class HMSMessageAction { } } } - + } diff --git a/ios/Classes/Models/HMSRecordingAction.swift b/ios/Classes/Models/HMSRecordingAction.swift index 82e71c91c..b0b573d8b 100644 --- a/ios/Classes/Models/HMSRecordingAction.swift +++ b/ios/Classes/Models/HMSRecordingAction.swift @@ -8,34 +8,35 @@ import Foundation import HMSSDK -class HMSRecordingAction{ - static func recordingActions(_ call: FlutterMethodCall, result: @escaping FlutterResult,hmsSDK:HMSSDK?) { - switch call.method { - case "start_rtmp_or_recording": - startRtmpOrRecording(call, result,hmsSDK: hmsSDK) - - case "stop_rtmp_and_recording": - stopRtmpAndRecording(result,hmsSDK:hmsSDK) - default: - result(FlutterMethodNotImplemented) +class HMSRecordingAction { + + static func recordingActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + switch call.method { + case "start_rtmp_or_recording": + startRtmpOrRecording(call, result, hmsSDK) + + case "stop_rtmp_and_recording": + stopRtmpAndRecording(result, hmsSDK) + default: + result(FlutterMethodNotImplemented) } } - static private func startRtmpOrRecording(_ call: FlutterMethodCall, _ result: @escaping FlutterResult,hmsSDK:HMSSDK?) { - + static private func startRtmpOrRecording(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + let arguments = call.arguments as! [AnyHashable: Any] - + guard let record = arguments["to_record"] as? Bool else { let error = HMSCommonAction.getError(message: "Record boolean not found", params: ["function": #function, "arguments": arguments]) result(HMSErrorExtension.toDictionary(error)) return } - + var meetingURL: URL? if let meetingURLStr = arguments["meeting_url"] as? String { meetingURL = URL(string: meetingURLStr) } - + var rtmpURLs: [URL]? if let strings = arguments["rtmp_urls"] as? [String] { for string in strings { @@ -47,9 +48,9 @@ class HMSRecordingAction{ } } } - + let config = HMSRTMPConfig(meetingURL: meetingURL, rtmpURLs: rtmpURLs, record: record) - + hmsSDK?.startRTMPOrRecording(config: config) { _, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) @@ -58,8 +59,8 @@ class HMSRecordingAction{ } } } - - static private func stopRtmpAndRecording(_ result: @escaping FlutterResult,hmsSDK:HMSSDK?) { + + static private func stopRtmpAndRecording(_ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { hmsSDK?.stopRTMPAndRecording { _, error in if let error = error { result(HMSErrorExtension.toDictionary(error)) diff --git a/ios/Classes/Models/HMSRoomAction.swift b/ios/Classes/Models/HMSRoomAction.swift index 939a25ead..fcc6a4a7b 100644 --- a/ios/Classes/Models/HMSRoomAction.swift +++ b/ios/Classes/Models/HMSRoomAction.swift @@ -9,52 +9,52 @@ import Foundation import HMSSDK class HMSRoomAction { - static func roomActions(_ call: FlutterMethodCall, result: @escaping FlutterResult,hmsSDK: HMSSDK?) { + static func roomActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { switch call.method { - + case "get_room": - getRoom(result,hmsSDK: hmsSDK) - + getRoom(result, hmsSDK) + case "get_local_peer": - getLocalPeer(result,hmsSDK: hmsSDK) - + getLocalPeer(result, hmsSDK) + case "get_remote_peers": - getRemotePeers(result,hmsSDK: hmsSDK) - + getRemotePeers(result, hmsSDK) + case "get_peers": - getPeers(result,hmsSDK: hmsSDK) + getPeers(result, hmsSDK) default: result(FlutterMethodNotImplemented) } } - static private func getRoom(_ result: FlutterResult,hmsSDK: HMSSDK?) { - + static private func getRoom(_ result: FlutterResult, _ hmsSDK: HMSSDK?) { + guard let room = hmsSDK?.room else { result(nil); return } - + result(HMSRoomExtension.toDictionary(room)) } - static private func getLocalPeer(_ result: FlutterResult,hmsSDK: HMSSDK?) { - + static private func getLocalPeer(_ result: FlutterResult, _ hmsSDK: HMSSDK?) { + guard let localPeer = hmsSDK?.localPeer else { result(nil); return } - + result(HMSPeerExtension.toDictionary(localPeer)) } - - static private func getRemotePeers(_ result: FlutterResult,hmsSDK: HMSSDK?) { - + + static private func getRemotePeers(_ result: FlutterResult, _ hmsSDK: HMSSDK?) { + guard let peers = hmsSDK?.remotePeers else { result(nil); return } - + var listOfPeers = [[String: Any]]() peers.forEach { listOfPeers.append(HMSPeerExtension.toDictionary($0)) } result(listOfPeers) } - - static private func getPeers(_ result: FlutterResult,hmsSDK: HMSSDK?) { - + + static private func getPeers(_ result: FlutterResult, _ hmsSDK: HMSSDK?) { + guard let peers = hmsSDK?.room?.peers else { result(nil); return } - + var listOfPeers = [[String: Any]]() peers.forEach { listOfPeers.append(HMSPeerExtension.toDictionary($0)) } result(listOfPeers) diff --git a/ios/Classes/Models/HMSRoomExtension.swift b/ios/Classes/Models/HMSRoomExtension.swift index 8e7c954fc..e69bf8b89 100644 --- a/ios/Classes/Models/HMSRoomExtension.swift +++ b/ios/Classes/Models/HMSRoomExtension.swift @@ -40,7 +40,9 @@ class HMSRoomExtension { dict["server_recording_state"] = HMSStreamingStateExtension.toDictionary(server: room.serverRecordingState) - dict["hls_streaming_state"] = HMSStreamingStateExtension.toDictionary(hlsVariant: room.hlsStreamingState) + dict["hls_streaming_state"] = HMSStreamingStateExtension.toDictionary(hlsStreaming: room.hlsStreamingState) + + dict["hls_recording_state"] = HMSStreamingStateExtension.toDictionary(hlsRecording: room.hlsRecordingState) return dict } @@ -57,6 +59,8 @@ class HMSRoomExtension { return "server_recording_state_updated" case .hlsStreamingStateUpdated: return "hls_streaming_state_updated" + case .hlsRecordingStateUpdated: + return "hls_recording_state_updated" case .metaDataUpdated: return "meta_data_updated" default: diff --git a/ios/Classes/Models/HMSStreamingStateExtension.swift b/ios/Classes/Models/HMSStreamingStateExtension.swift index 939b63e9a..1ccaf9ea8 100644 --- a/ios/Classes/Models/HMSStreamingStateExtension.swift +++ b/ios/Classes/Models/HMSStreamingStateExtension.swift @@ -53,16 +53,30 @@ class HMSStreamingStateExtension { return dict } - static func toDictionary(hlsVariant: HMSHLSStreamingState) -> [String: Any] { + static func toDictionary(hlsStreaming: HMSHLSStreamingState) -> [String: Any] { var dict = [String: Any]() - dict["running"] = hlsVariant.running + dict["running"] = hlsStreaming.running var args = [Any]() - hlsVariant.variants.forEach { variant in + hlsStreaming.variants.forEach { variant in args.append(HMSHLSVariantExtension.toDictionary(variant)) } dict["variants"]=args return dict } + + static func toDictionary(hlsRecording: HMSHLSRecordingState) -> [String: Any] { + var dict = [String: Any]() + + dict["running"] = hlsRecording.running + if let startedAt = hlsRecording.startedAt { + dict["started_at"] = "\(startedAt)" + } + if let error = hlsRecording.error { + dict.merge(HMSErrorExtension.toDictionary(error)) { (_, new) in new } + } + + return dict + } } diff --git a/ios/Classes/Models/HMSVideoAction.swift b/ios/Classes/Models/HMSVideoAction.swift index 93b99b3f9..c3993a548 100644 --- a/ios/Classes/Models/HMSVideoAction.swift +++ b/ios/Classes/Models/HMSVideoAction.swift @@ -9,57 +9,57 @@ import Foundation import HMSSDK class HMSVideoAction{ - static func videoActions(_ call: FlutterMethodCall, result: @escaping FlutterResult,hmsSDK:HMSSDK?) { - switch call.method { - case "switch_video": - switchVideo(call, result,hmsSDK: hmsSDK) - - case "switch_camera": - switchCamera(result,hmsSDK: hmsSDK) - - case "start_capturing": - startCapturing(result,hmsSDK: hmsSDK) - - case "stop_capturing": - stopCapturing(result,hmsSDK: hmsSDK) - - case "is_video_mute": - isVideoMute(call, result,hmsSDK: hmsSDK) - - case "set_playback_allowed": - setPlaybackAllowed(call, result,hmsSDK: hmsSDK) - default: - result(FlutterMethodNotImplemented) + static func videoActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK: HMSSDK?) { + switch call.method { + case "switch_video": + switchVideo(call, result, hmsSDK) + + case "switch_camera": + switchCamera(result, hmsSDK) + + case "start_capturing": + startCapturing(result, hmsSDK) + + case "stop_capturing": + stopCapturing(result, hmsSDK) + + case "is_video_mute": + isVideoMute(call, result, hmsSDK) + + case "set_playback_allowed": + setPlaybackAllowed(call, result, hmsSDK) + default: + result(FlutterMethodNotImplemented) } } - static private func startCapturing(_ result: FlutterResult,hmsSDK:HMSSDK?) { + static private func startCapturing(_ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { guard let peer = hmsSDK?.localPeer, let track = peer.videoTrack as? HMSLocalVideoTrack else { result(false) return } - + track.startCapturing() - + result(true) } - - static private func stopCapturing(_ result: FlutterResult,hmsSDK:HMSSDK?) { + + static private func stopCapturing(_ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { guard let peer = hmsSDK?.localPeer, let track = peer.videoTrack as? HMSLocalVideoTrack else { result(false) return } - + track.stopCapturing() - + result(true) } - - static private func switchCamera(_ result: FlutterResult,hmsSDK:HMSSDK?) { + + static private func switchCamera(_ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { guard let peer = hmsSDK?.localPeer, let videoTrack = peer.videoTrack as? HMSLocalVideoTrack else { @@ -67,31 +67,31 @@ class HMSVideoAction{ result(HMSErrorExtension.toDictionary(error)) return } - + videoTrack.switchCamera() - + result(nil) } - - static private func switchVideo(_ call: FlutterMethodCall, _ result: FlutterResult,hmsSDK:HMSSDK?) { - + + static private func switchVideo(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { + let arguments = call.arguments as! [AnyHashable: Any] - + guard let shouldMute = arguments["is_on"] as? Bool, let peer = hmsSDK?.localPeer, let video = peer.videoTrack as? HMSLocalVideoTrack else { - result(false) - return - } - + result(false) + return + } + video.setMute(shouldMute) - + result(true) } - - static private func isVideoMute(_ call: FlutterMethodCall, _ result: FlutterResult,hmsSDK:HMSSDK?) { + + static private func isVideoMute(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { let arguments = call.arguments as! [AnyHashable: Any] - + if let peerID = arguments["peer_id"] as? String, let peer = HMSCommonAction.getPeer(by: peerID,hmsSDK: hmsSDK) { if let video = peer.videoTrack { result(video.isMute()) @@ -103,21 +103,21 @@ class HMSVideoAction{ return } } - + result(false) } - - static private func setPlaybackAllowed(_ call: FlutterMethodCall, _ result: FlutterResult,hmsSDK:HMSSDK?) { + + static private func setPlaybackAllowed(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ hmsSDK:HMSSDK?) { let arguments = call.arguments as! [AnyHashable: Any] - + let allowed = arguments["allowed"] as! Bool - + if let localPeer = hmsSDK?.localPeer { if let video = localPeer.videoTrack as? HMSLocalVideoTrack { video.setMute(!allowed) } } - + if let remotePeers = hmsSDK?.remotePeers { remotePeers.forEach { peer in if let video = peer.remoteVideoTrack() { @@ -130,7 +130,7 @@ class HMSVideoAction{ } } } - + result(nil) } } diff --git a/ios/Classes/SwiftHmssdkFlutterPlugin.swift b/ios/Classes/SwiftHmssdkFlutterPlugin.swift index f76b391e8..2898ce623 100644 --- a/ios/Classes/SwiftHmssdkFlutterPlugin.swift +++ b/ios/Classes/SwiftHmssdkFlutterPlugin.swift @@ -1,6 +1,8 @@ + import Flutter import UIKit import HMSSDK +import ReplayKit public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListener, FlutterStreamHandler, HMSPreviewListener, HMSLogger { @@ -40,6 +42,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene let videoViewFactory = HMSFlutterPlatformViewFactory(plugin: instance) registrar.register(videoViewFactory, withId: "HMSFlutterPlatformView") + eventChannel.setStreamHandler(instance) previewChannel.setStreamHandler(instance) logsChannel.setStreamHandler(instance) @@ -107,64 +110,70 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene // MARK: Room Actions case "build", "preview", "join", "leave": - buildActions(call, result: result) + buildActions(call, result) // MARK: Room Actions case "get_room", "get_local_peer", "get_remote_peers", "get_peers": - HMSRoomAction.roomActions(call, result: result,hmsSDK:hmsSDK) + HMSRoomAction.roomActions(call, result, hmsSDK) // MARK: - Audio Helpers case "switch_audio", "is_audio_mute", "mute_all", "un_mute_all", "set_volume": - HMSAudioAction.audioActions(call, result: result,hmsSDK: hmsSDK) + HMSAudioAction.audioActions(call, result, hmsSDK) // MARK: - Video Helpers case "switch_video", "switch_camera", "start_capturing", "stop_capturing", "is_video_mute", "set_playback_allowed": - HMSVideoAction.videoActions(call, result: result,hmsSDK: hmsSDK) + HMSVideoAction.videoActions(call, result, hmsSDK) // MARK: - Messaging case "send_broadcast_message", "send_direct_message", "send_group_message": - HMSMessageAction.messageActions(call, result: result,hmsSDK: hmsSDK) + HMSMessageAction.messageActions(call, result, hmsSDK) // MARK: - Role based Actions case "get_roles", "change_role", "accept_change_role", "end_room", "remove_peer", "on_change_track_state_request", "change_track_state_for_role": - roleActions(call, result: result) + roleActions(call, result) // MARK: - Peer Action case "change_metadata", "change_name": - peerActions(call, result: result) + peerActions(call, result) - // MARK: - Recording + // MARK: - RTMP case "start_rtmp_or_recording", "stop_rtmp_and_recording": - HMSRecordingAction.recordingActions(call, result: result,hmsSDK: hmsSDK) + HMSRecordingAction.recordingActions(call, result, hmsSDK) // MARK: - HLS case "hls_start_streaming", "hls_stop_streaming": - HMSHLSAction.hlsActions(call, result: result, hmsSDK: hmsSDK) + HMSHLSAction.hlsActions(call, result, hmsSDK) // MARK: - Logging case "start_hms_logger", "remove_hms_logger": - loggingActions(call, result: result) + loggingActions(call, result) - // MARK: - statsListener + // MARK: - Stats Listener case "start_stats_listener", "remove_stats_listener": - statsListenerAction(call, result: result) + statsListenerAction(call, result) + + // MARK: - Screen Share + + case "start_screen_share", "stop_screen_share", "is_screen_share_active": + screenShareActions(call, result) + default: result(FlutterMethodNotImplemented) } } - // MARK: Build Actions - private func buildActions(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + // MARK: - Build Actions + private func buildActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { switch call.method { case "build": build(call, result) @@ -184,7 +193,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } // MARK: - Role based Actions - private func roleActions(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + private func roleActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { switch call.method { case "get_roles": getRoles(call, result) @@ -212,8 +221,10 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } } - // MARK: - Peer - private func peerActions(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + + // MARK: - Peer Actions + + private func peerActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { switch call.method { case "change_metadata": changeMetadata(call, result) @@ -226,7 +237,7 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } // MARK: - Logging - private func loggingActions(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + private func loggingActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { switch call.method { case "start_hms_logger": startHMSLogger(call) @@ -239,7 +250,9 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } } - private func statsListenerAction(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + // MARK: - Stats Listener + + private func statsListenerAction(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { switch call.method { case "start_stats_listener": @@ -253,6 +266,49 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene } } + // MARK: - Screen Share + var isScreenShareOn = false { + didSet { + screenShareActionResult?(nil) + screenShareActionResult = nil + } + } + var preferredExtension: String? + var systemBroadcastPicker: RPSystemBroadcastPickerView? + var screenShareActionResult: FlutterResult? + + private func screenShareActions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + + switch call.method { + case "start_screen_share", "stop_screen_share": + guard let preferredExtension = preferredExtension else { + let error = getError(message: "Could not start Screen share, preferredExtension not passed in Build Method", params: ["function": #function]) + result(HMSErrorExtension.toDictionary(error)) + screenShareActionResult = nil + return + } + + screenShareActionResult = result + + if systemBroadcastPicker == nil { + systemBroadcastPicker = RPSystemBroadcastPickerView() + systemBroadcastPicker!.preferredExtension = preferredExtension + systemBroadcastPicker!.showsMicrophoneButton = false + } + + for view in systemBroadcastPicker!.subviews { + if let button = view as? UIButton { + button.sendActions(for: .allEvents) + } + } + + case "is_screen_share_active": + result(isScreenShareOn) + default: + print("Not Valid") + } + } + // MARK: - Room Actions private func build(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { @@ -288,6 +344,11 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene trackSettings = HMSTrackSettings(videoSettings: videoSettings, audioSettings: audioSettings) } + if let prefExtension = arguments["preferred_extension"] as? String { + preferredExtension = prefExtension + } + + var setLogger = false if let level = arguments["log_level"] as? String { logLevel = getLogLevel(from: level) @@ -296,6 +357,9 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene hmsSDK = HMSSDK.build { sdk in + if let appGroup = arguments["app_group"] as? String { + sdk.appGroup = appGroup + } if let settings = trackSettings { sdk.trackSettings = settings } @@ -673,7 +737,13 @@ public class SwiftHmssdkFlutterPlugin: NSObject, FlutterPlugin, HMSUpdateListene "update": HMSTrackExtension.getValueOf(update) ] ] as [String: Any] - + if peer.isLocal && track.source.uppercased() == "SCREEN" { + if update == .trackAdded { + isScreenShareOn = true + }else if update == .trackRemoved { + isScreenShareOn = false + } + } eventSink?(data) } diff --git a/ios/Classes/Views/HMSFlutterPlatformViewFactory.swift b/ios/Classes/Views/HMSFlutterPlatformViewFactory.swift index 92e37293e..206d1f3e8 100644 --- a/ios/Classes/Views/HMSFlutterPlatformViewFactory.swift +++ b/ios/Classes/Views/HMSFlutterPlatformViewFactory.swift @@ -5,7 +5,6 @@ // Copyright © 2021 100ms. All rights reserved. // -import Foundation import Flutter import HMSSDK import UIKit @@ -16,6 +15,7 @@ class HMSFlutterPlatformViewFactory: NSObject, FlutterPlatformViewFactory { init(plugin: SwiftHmssdkFlutterPlugin) { self.plugin = plugin + super.init() } func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { diff --git a/ios/hmssdk_flutter.podspec b/ios/hmssdk_flutter.podspec index da5ebb95d..1f07c0f16 100644 --- a/ios/hmssdk_flutter.podspec +++ b/ios/hmssdk_flutter.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'hmssdk_flutter' - s.version = '0.7.2' + s.version = '0.7.3' s.summary = 'The Flutter plugin for 100ms SDK' s.description = <<-DESC A Flutter Project for 100ms SDK. @@ -13,6 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.dependency 'HMSSDK', '0.3.1' + s.dependency 'HMSBroadcastExtensionSDK', '0.0.1' s.platform = :ios, '12.0' s.ios.deployment_target = '12.0' # Flutter.framework does not contain a i386 slice. diff --git a/lib/hmssdk_flutter.dart b/lib/hmssdk_flutter.dart index 2479888f1..ee94dd0a7 100644 --- a/lib/hmssdk_flutter.dart +++ b/lib/hmssdk_flutter.dart @@ -72,3 +72,4 @@ export 'src/model/hms_rtc_stats.dart'; export 'src/enum/hms_video_scale_type.dart'; export 'src/model/hms_network_quality.dart'; export 'src/model/hms_stats_listener.dart'; +export 'src/model/hms_hls_recording_config.dart'; diff --git a/lib/src/enum/hms_room_update.dart b/lib/src/enum/hms_room_update.dart index c90e0c008..218d48924 100644 --- a/lib/src/enum/hms_room_update.dart +++ b/lib/src/enum/hms_room_update.dart @@ -1,11 +1,29 @@ enum HMSRoomUpdate { + ///When room is muted roomMuted, + + ///When room is unmuted roomUnmuted, + + ///When server recording state is updated serverRecordingStateUpdated, - browserRecordingStateUpdated, + + ///When RTMP is started or stopped rtmpStreamingStateUpdated, + + ///When HLS is started or stopped hlsStreamingStateUpdated, + + ///When hls recording state is updated + hlsRecordingStateUpdated, + + ///When browser recording state is changed + browserRecordingStateUpdated, + + ///When room name changed RoomNameUpdated, + + ///Default Update defaultUpdate } @@ -30,6 +48,9 @@ extension HMSRoomUpdateValues on HMSRoomUpdate { case 'hls_streaming_state_updated': return HMSRoomUpdate.hlsStreamingStateUpdated; + case 'hls_recording_state_updated': + return HMSRoomUpdate.hlsRecordingStateUpdated; + case "room_name_updated": return HMSRoomUpdate.RoomNameUpdated; @@ -58,6 +79,9 @@ extension HMSRoomUpdateValues on HMSRoomUpdate { case HMSRoomUpdate.hlsStreamingStateUpdated: return 'hls_streaming_state_updated'; + case HMSRoomUpdate.hlsRecordingStateUpdated: + return 'hls_recording_state_updated'; + case HMSRoomUpdate.RoomNameUpdated: return "room_name_updated"; diff --git a/lib/src/enum/hms_track_update.dart b/lib/src/enum/hms_track_update.dart index 3958d342f..d0b488427 100644 --- a/lib/src/enum/hms_track_update.dart +++ b/lib/src/enum/hms_track_update.dart @@ -11,6 +11,8 @@ enum HMSTrackUpdate { ///when track is unmuted could be audio,video or both. trackUnMuted, + + ///when track description is changed trackDescriptionChanged, ///when video track is degraded diff --git a/lib/src/hmssdk.dart b/lib/src/hmssdk.dart index 14f5a72b5..bb8bc075d 100644 --- a/lib/src/hmssdk.dart +++ b/lib/src/hmssdk.dart @@ -1,11 +1,3 @@ -///HMSSDK will contain all the functionalities related to meeting and preview. -/// -///Just create instance of [HMSSDK] and use the functionality which is present. -/// -///All methods related to meeting, preview and their listeners are present here. - -// Project imports: -import 'package:flutter/widgets.dart'; import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:hmssdk_flutter/src/manager/hms_sdk_manager.dart'; import 'package:hmssdk_flutter/src/service/platform_service.dart'; @@ -24,18 +16,16 @@ import '../hmssdk_flutter.dart'; /// **Broadcast** - A local peer can send any message/data to all remote peers in the room /// /// HMSSDK has other methods which the client app can use to get more info about the Room, Peer and Tracks -class HMSSDK with WidgetsBindingObserver { - ///join meeting by passing HMSConfig instance to it. - - HMSTrackSetting? hmsTrackSetting; - bool previewState = false; +/// - HMSSDK({this.hmsTrackSetting}); +class HMSSDK { + HMSSDK({this.hmsTrackSetting, this.appGroup, this.preferredExtension}); /// The build function should be called after creating an instance of the [HMSSDK]. /// Await the result & if true then create [HMSConfig] object to join or preview a room. Future build() async { - return await HmsSdkManager().createHMSSdk(hmsTrackSetting); + return await HmsSdkManager() + .createHMSSdk(hmsTrackSetting, appGroup, preferredExtension); } ///add MeetingListener it will add all the listeners. @@ -63,7 +53,6 @@ class HMSSDK with WidgetsBindingObserver { isTerminal: false, params: {...config.getJson()}); } - WidgetsBinding.instance!.addObserver(this); return await PlatformService.invokeMethod(PlatformMethod.join, arguments: {...config.getJson()}); } @@ -100,7 +89,6 @@ class HMSSDK with WidgetsBindingObserver { hmsException: HMSException.fromMap(result["error"])); } } - WidgetsBinding.instance!.removeObserver(this); } /// To switch local peer's audio on/off. @@ -653,20 +641,33 @@ class HMSSDK with WidgetsBindingObserver { /// API to start screen share of your android device. Note: This API is not available on iOS. /// [hmsActionResultListener] is a callback instance on which [HMSActionResultListener.onSuccess] /// and [HMSActionResultListener.onException] will be called + /// [preferredExtension] is only used for screen share (broadcast screen) in iOS. void startScreenShare( {HMSActionResultListener? hmsActionResultListener}) async { - var result = await PlatformService.invokeMethod( - PlatformMethod.startScreenShare, - ); - - if (hmsActionResultListener != null) { - if (result == null) { - hmsActionResultListener.onSuccess( - methodType: HMSActionResultListenerMethod.startScreenShare); - } else { + HMSLocalPeer? localPeer = await getLocalPeer(); + if (localPeer?.role.publishSettings?.allowed.contains("screen") ?? false) { + var result = + await PlatformService.invokeMethod(PlatformMethod.startScreenShare); + + if (hmsActionResultListener != null) { + if (result == null) { + hmsActionResultListener.onSuccess( + methodType: HMSActionResultListenerMethod.startScreenShare); + } else { + hmsActionResultListener.onException( + methodType: HMSActionResultListenerMethod.startScreenShare, + hmsException: HMSException.fromMap(result["error"])); + } + } + } else { + if (hmsActionResultListener != null) { hmsActionResultListener.onException( methodType: HMSActionResultListenerMethod.startScreenShare, - hmsException: HMSException.fromMap(result["error"])); + hmsException: HMSException( + message: "Permission denied", + description: "Screen share is not included in publish settings", + action: "Enable screen share from dashboard for current role", + isTerminal: false)); } } } @@ -683,9 +684,8 @@ class HMSSDK with WidgetsBindingObserver { /// and [HMSActionResultListener.onException] will be called void stopScreenShare( {HMSActionResultListener? hmsActionResultListener}) async { - var result = await PlatformService.invokeMethod( - PlatformMethod.stopScreenShare, - ); + var result = + await PlatformService.invokeMethod(PlatformMethod.stopScreenShare); if (hmsActionResultListener != null) { if (result == null) { hmsActionResultListener.onSuccess( @@ -733,40 +733,14 @@ class HMSSDK with WidgetsBindingObserver { PlatformService.removeLogsListener(hmsLogListener); } - ///To maintain the app state in background and foreground state - bool isLocalVideoOn = false; - @override - void didChangeAppLifecycleState(AppLifecycleState state) async { - super.didChangeAppLifecycleState(state); + /// To modify local peer's audio & video track settings use the [hmsTrackSetting]. Only required for advanced use-cases. + HMSTrackSetting? hmsTrackSetting; - if (state == AppLifecycleState.resumed) { - List? peersList = await getPeers(); + /// [appGroup] is only used for screen share (broadcast screen) in iOS. + String? appGroup; - peersList?.forEach((element) { - if (!element.isLocal) { - (element.audioTrack as HMSRemoteAudioTrack?)?.setVolume(10.0); - element.auxiliaryTracks?.forEach((element) { - if (element.kind == HMSTrackKind.kHMSTrackKindAudio) { - (element as HMSRemoteAudioTrack?)?.setVolume(10.0); - } - }); - } else { - if ((element.videoTrack != null && isLocalVideoOn)) startCapturing(); - isLocalVideoOn = false; - } - }); - } else if (state == AppLifecycleState.paused) { - HMSLocalPeer? localPeer = await getLocalPeer(); - if (localPeer != null && !(localPeer.videoTrack?.isMute ?? true)) { - isLocalVideoOn = true; - stopCapturing(); - } - } else if (state == AppLifecycleState.inactive) { - HMSLocalPeer? localPeer = await getLocalPeer(); - if (localPeer != null && !(localPeer.videoTrack?.isMute ?? true)) { - isLocalVideoOn = true; - stopCapturing(); - } - } - } + /// [preferredExtension] is only used for screen share (broadcast screen) in iOS. + String? preferredExtension; + + bool previewState = false; } diff --git a/lib/src/manager/hms_sdk_manager.dart b/lib/src/manager/hms_sdk_manager.dart index 33e8c9922..1e289982c 100644 --- a/lib/src/manager/hms_sdk_manager.dart +++ b/lib/src/manager/hms_sdk_manager.dart @@ -4,13 +4,19 @@ import '../../hmssdk_flutter.dart'; ///100ms HmsSdkManager class HmsSdkManager { - Future createInstance(HMSTrackSetting? hmsTrackSetting) async { - bool isCreated = await createHMSSdk(hmsTrackSetting); + Future createInstance(HMSTrackSetting? hmsTrackSetting, + String? appGroup, String? preferredExtension) async { + bool isCreated = + await createHMSSdk(hmsTrackSetting, appGroup, preferredExtension); return isCreated; } - Future createHMSSdk(HMSTrackSetting? hmsTrackSetting) async { - return await PlatformService.invokeMethod(PlatformMethod.build, - arguments: {"hms_track_setting": hmsTrackSetting?.toMap()}); + Future createHMSSdk(HMSTrackSetting? hmsTrackSetting, String? appGroup, + String? preferredExtension) async { + return await PlatformService.invokeMethod(PlatformMethod.build, arguments: { + "hms_track_setting": hmsTrackSetting?.toMap(), + "app_group": appGroup, + "preferred_extension": preferredExtension + }); } } diff --git a/lib/src/model/hms_hls_config.dart b/lib/src/model/hms_hls_config.dart index d8ef17453..28d733d13 100644 --- a/lib/src/model/hms_hls_config.dart +++ b/lib/src/model/hms_hls_config.dart @@ -1,22 +1,27 @@ -import 'package:hmssdk_flutter/src/model/hms_hls_meeting_url_variant.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; ///100ms HMSHLSConfig /// ///[HMSHLSConfig] contains a list of [HMSHLSMeetingURLVariant]. class HMSHLSConfig { - List variants; - - HMSHLSConfig(this.variants); + List meetingURLVariant; + HMSHLSRecordingConfig? hmsHLSRecordingConfig; + HMSHLSConfig({required this.meetingURLVariant, this.hmsHLSRecordingConfig}); Map toMap() { List> list = []; - variants.forEach((element) { + meetingURLVariant.forEach((element) { list.add(element.toMap()); }); - - return { - 'meeting_url_variants': list, - }; + if (hmsHLSRecordingConfig != null) { + Map recordingConfig = hmsHLSRecordingConfig!.toMap(); + return { + 'meeting_url_variants': list, + 'recording_config': recordingConfig + }; + } else { + return {'meeting_url_variants': list}; + } } } diff --git a/lib/src/model/hms_hls_recording_config.dart b/lib/src/model/hms_hls_recording_config.dart new file mode 100644 index 000000000..ac68168ef --- /dev/null +++ b/lib/src/model/hms_hls_recording_config.dart @@ -0,0 +1,17 @@ +///100ms HMSHLSRecording +/// +///[HMSHLSRecordingConfig] contains a singleFilePerLayer and VOD info. +class HMSHLSRecordingConfig { + bool singleFilePerLayer; + bool videoOnDemand; + + HMSHLSRecordingConfig( + {this.singleFilePerLayer = false, this.videoOnDemand = false}); + + Map toMap() { + return { + 'single_file_per_layer': singleFilePerLayer, + 'video_on_demand': videoOnDemand + }; + } +} diff --git a/lib/src/model/hms_hls_recording_state.dart b/lib/src/model/hms_hls_recording_state.dart new file mode 100644 index 000000000..a82e01fe5 --- /dev/null +++ b/lib/src/model/hms_hls_recording_state.dart @@ -0,0 +1,25 @@ +// Project imports: +import 'package:hmssdk_flutter/src/exceptions/hms_exception.dart'; +import 'package:hmssdk_flutter/src/model/hms_date_extension.dart'; + +///100ms HMSHLSRecordingState +/// +///[HMSHLSRecordingState] contains information about the hls recording status. +class HMSHLSRecordingState { + final HMSException? error; + final bool running; + DateTime? startedAt; + HMSHLSRecordingState( + {required this.error, required this.running, this.startedAt}); + + factory HMSHLSRecordingState.fromMap(Map map) { + return HMSHLSRecordingState( + error: (map.containsKey('error') && map['error'] != null) + ? HMSException.fromMap(map) + : null, + running: map['running'], + startedAt: map['started_at'] != null + ? HMSDateExtension.convertDate(map['started_at']) + : null); + } +} diff --git a/lib/src/model/hms_local_video_stats.dart b/lib/src/model/hms_local_video_stats.dart index e1c1d49e7..d5468e8a6 100644 --- a/lib/src/model/hms_local_video_stats.dart +++ b/lib/src/model/hms_local_video_stats.dart @@ -17,7 +17,7 @@ class HMSLocalVideoStats { double frameRate; /// Frame rate of video frames being sent (FPS). - HMSVideoResolution resolution; + HMSResolution resolution; HMSLocalVideoStats({ required this.roundTripTime, @@ -34,7 +34,7 @@ class HMSLocalVideoStats { bitrate: map["bitrate"] ?? 0.00, frameRate: map["frame_rate"] ?? 0.0, resolution: map['resolution'] == null - ? HMSVideoResolution(height: 0, width: 0) - : HMSVideoResolution.fromMap(map['resolution'])); + ? HMSResolution(height: 0, width: 0) + : HMSResolution.fromMap(map['resolution'])); } } diff --git a/lib/src/model/hms_preview_listener.dart b/lib/src/model/hms_preview_listener.dart index c689819a0..9f0d7eba5 100644 --- a/lib/src/model/hms_preview_listener.dart +++ b/lib/src/model/hms_preview_listener.dart @@ -12,7 +12,7 @@ abstract class HMSPreviewListener { /// /// - Parameters: /// - error: error which you get. - void onError({required HMSException error}); + void onHMSError({required HMSException error}); ///when you want to preview listen to this callback /// diff --git a/lib/src/model/hms_recording_config.dart b/lib/src/model/hms_recording_config.dart index 521c1793f..0664236d8 100644 --- a/lib/src/model/hms_recording_config.dart +++ b/lib/src/model/hms_recording_config.dart @@ -1,19 +1,38 @@ +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + ///100ms HMSRecordingConfg /// -///[HMSRecordingConfig] contains meeting Url, record status anf rtmp Urls list. +///[HMSRecordingConfig] contains meeting Url, record status, rtmp Urls list and video resolution. class HMSRecordingConfig { + ///Single click meeting url to start/stop recording/streaming. final String meetingUrl; + + ///true if recording/streaming should be started. false if recording/streaming should be stoppped. final bool toRecord; + // The RTMP ingest url or urls where the meeting will be streamed. List? rtmpUrls; + ///The resolution that rtmp stream should be. + /// + ///Width Range:500-1280. If height>720 them max width=720. + ///Default 1280. + /// + ///Height Range:480-1280. If width>720 them max height=720. + ///Default 720. + HMSResolution? resolution; + HMSRecordingConfig( - {required this.meetingUrl, required this.toRecord, this.rtmpUrls}); + {required this.meetingUrl, + required this.toRecord, + this.rtmpUrls, + this.resolution}); Map getJson() { return { "meeting_url": meetingUrl, "to_record": toRecord, - "rtmp_urls": rtmpUrls + "rtmp_urls": rtmpUrls, + "resolution": resolution?.toMap() ?? null }; } } diff --git a/lib/src/model/hms_remote_video_stats.dart b/lib/src/model/hms_remote_video_stats.dart index b9d2cb223..c95965c4a 100644 --- a/lib/src/model/hms_remote_video_stats.dart +++ b/lib/src/model/hms_remote_video_stats.dart @@ -20,7 +20,7 @@ class HMSRemoteVideoStats { int packetsLost; /// Resolution of video frames being received. - HMSVideoResolution resolution; + HMSResolution resolution; /// Frame rate of video frames being received (FPS). double frameRate; @@ -43,7 +43,7 @@ class HMSRemoteVideoStats { packetsReceived: map['packets_received'] ?? 0, frameRate: map["frame_rate"] ?? 0, resolution: map['resolution'] == null - ? HMSVideoResolution(height: 0, width: 0) - : HMSVideoResolution.fromMap(map['resolution'])); + ? HMSResolution(height: 0, width: 0) + : HMSResolution.fromMap(map['resolution'])); } } diff --git a/lib/src/model/hms_room.dart b/lib/src/model/hms_room.dart index 69476e83f..3c64c5c46 100644 --- a/lib/src/model/hms_room.dart +++ b/lib/src/model/hms_room.dart @@ -11,6 +11,7 @@ // Project imports: import 'package:hmssdk_flutter/hmssdk_flutter.dart'; import 'package:hmssdk_flutter/src/model/hms_browser_recording_state.dart'; +import 'package:hmssdk_flutter/src/model/hms_hls_recording_state.dart'; import 'package:hmssdk_flutter/src/model/hms_server_recording_state.dart'; import 'hms_hls_streaming_state.dart'; import 'hms_rtmp_streaming_state.dart'; @@ -24,6 +25,7 @@ class HMSRoom { HMSRtmpStreamingState? hmsRtmpStreamingState; HMSServerRecordingState? hmsServerRecordingState; HMSHLSStreamingState? hmshlsStreamingState; + HMSHLSRecordingState? hmshlsRecordingState; int peerCount; int startedAt; String sessionId; @@ -40,6 +42,7 @@ class HMSRoom { this.hmsRtmpStreamingState, this.hmsBrowserRecordingState, this.hmshlsStreamingState, + this.hmshlsRecordingState, this.peerCount = 0, this.startedAt = 0, required this.sessionId}); @@ -70,13 +73,16 @@ class HMSRoom { hmshlsStreamingState: map["hls_streaming_state"] != null ? HMSHLSStreamingState.fromMap(map["hls_streaming_state"] as Map) : null, + hmshlsRecordingState: map["hls_recording_state"] != null + ? HMSHLSRecordingState.fromMap(map["hls_recording_state"] as Map) + : null, id: map['id'], name: map['name'], peers: peers, metaData: map['meta_data'], peerCount: map["peer_count"] != null ? map["peer_count"] : 0, startedAt: map["started_at"] != null ? map["started_at"] : 0, - sessionId: map.containsKey("session_id") ? map["session_id"] : ""); + sessionId: map["session_id"] != null ? map["session_id"] : ""); } @override diff --git a/lib/src/model/hms_update_listener.dart b/lib/src/model/hms_update_listener.dart index c96f8b0c3..a8b13ffa8 100644 --- a/lib/src/model/hms_update_listener.dart +++ b/lib/src/model/hms_update_listener.dart @@ -51,7 +51,7 @@ abstract class HMSUpdateListener { /// /// and SDK has already retried to fix the error /// - Parameter error: the error that occurred - void onError({required HMSException error}); + void onHMSError({required HMSException error}); /// This is called when there is a new broadcast message from any other peer in the room /// diff --git a/lib/src/model/hms_video_resolution.dart b/lib/src/model/hms_video_resolution.dart index 46cf2bc53..4b5df5f76 100644 --- a/lib/src/model/hms_video_resolution.dart +++ b/lib/src/model/hms_video_resolution.dart @@ -1,14 +1,14 @@ ///100ms HMSVideoResolution /// -///[HMSVideoResolution] contains height and width of peer's video. -class HMSVideoResolution { +///[HMSResolution] contains height and width of peer's video. +class HMSResolution { final double height; final double width; - HMSVideoResolution({required this.height, required this.width}); + HMSResolution({required this.height, required this.width}); - factory HMSVideoResolution.fromMap(Map map) { - return HMSVideoResolution(height: map['height'], width: map['width']); + factory HMSResolution.fromMap(Map map) { + return HMSResolution(height: map['height'], width: map['width']); } Map toMap() { diff --git a/lib/src/model/hms_video_track_setting.dart b/lib/src/model/hms_video_track_setting.dart index 571ff836a..3ccd93ea9 100644 --- a/lib/src/model/hms_video_track_setting.dart +++ b/lib/src/model/hms_video_track_setting.dart @@ -3,7 +3,7 @@ import 'package:hmssdk_flutter/hmssdk_flutter.dart'; class HMSVideoTrackSetting { final HMSCodec? codec; - final HMSVideoResolution? resolution; + final HMSResolution? resolution; final int? maxBitrate; final int? maxFrameRate; final HMSCameraFacing? cameraFacing; @@ -17,9 +17,9 @@ class HMSVideoTrackSetting { }); factory HMSVideoTrackSetting.fromMap(Map map) { - HMSVideoResolution? resolution; + HMSResolution? resolution; if (map.containsKey('resolution')) { - resolution = HMSVideoResolution.fromMap(map['resolution']); + resolution = HMSResolution.fromMap(map['resolution']); } return HMSVideoTrackSetting( codec: HMSCodecValues.getHMSCodecFromName(map['video_codec']), diff --git a/lib/src/service/platform_service.dart b/lib/src/service/platform_service.dart index e3b803bb6..b8ac6ef70 100644 --- a/lib/src/service/platform_service.dart +++ b/lib/src/service/platform_service.dart @@ -406,7 +406,7 @@ class PlatformService { break; case HMSPreviewUpdateListenerMethod.onError: previewListeners.forEach((e) { - e.onError(error: arguments["error"]); + e.onHMSError(error: arguments["error"]); }); break; case HMSPreviewUpdateListenerMethod.unknown: @@ -482,7 +482,7 @@ class PlatformService { peer: arguments['peer'])); break; case HMSUpdateListenerMethod.onError: - updateListeners.forEach((e) => e.onError(error: arguments['error'])); + updateListeners.forEach((e) => e.onHMSError(error: arguments['error'])); break; case HMSUpdateListenerMethod.onMessage: updateListeners diff --git a/mobx-example-app/.gitignore b/mobx-example-app/.gitignore new file mode 100644 index 000000000..0fa6b675c --- /dev/null +++ b/mobx-example-app/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/mobx-example-app/.metadata b/mobx-example-app/.metadata new file mode 100644 index 000000000..a5584fc37 --- /dev/null +++ b/mobx-example-app/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 18116933e77adc82f80866c928266a5b4f1ed645 + channel: stable + +project_type: app diff --git a/mobx-example-app/README.md b/mobx-example-app/README.md new file mode 100644 index 000000000..b175af7be --- /dev/null +++ b/mobx-example-app/README.md @@ -0,0 +1,24 @@ +# mobx Clone + +This is a final version of the app for starter kit download starter branch in this repo. + +Building mobx clone in Flutter with 100ms SDK + +Step by Step guide available at : https://www.100ms.live/blog/zoom-clone-in-flutter + +mobx is the most popular video and audio conferencing app. From interacting with co-workers to organizing events like workshops and webinars, mobx is everywhere. + +This post will take you through a step by step guide on how to build a basic mobx like app using Flutter and 100ms' live audio-video SDK in the following way - + +* Add 100ms to a Flutter app +* Join a room +* Leave a room +* Show video tiles with the user’s name +* Show Screenshare tile +* hand Raised +* Mute/Unmute +* Camera off/on +* Toggle Front/Back camera +* Chatting with everyone in the room + +![alt-text](https://github.com/govindmaheshwari2/mobx-example-app/blob/master/final.gif) diff --git a/mobx-example-app/analysis_options.yaml b/mobx-example-app/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/mobx-example-app/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/mobx-example-app/android/.gitignore b/mobx-example-app/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/mobx-example-app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/mobx-example-app/android/app/build.gradle b/mobx-example-app/android/app/build.gradle new file mode 100644 index 000000000..d15ea2d79 --- /dev/null +++ b/mobx-example-app/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 32 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.mobx" + minSdkVersion 21 + targetSdkVersion 30 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/mobx-example-app/android/app/src/debug/AndroidManifest.xml b/mobx-example-app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..066245a60 --- /dev/null +++ b/mobx-example-app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobx-example-app/android/app/src/main/AndroidManifest.xml b/mobx-example-app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..eb7f2a8cf --- /dev/null +++ b/mobx-example-app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mobx-example-app/android/app/src/main/kotlin/com/example/zoom/MainActivity.kt b/mobx-example-app/android/app/src/main/kotlin/com/example/zoom/MainActivity.kt new file mode 100644 index 000000000..a8924c824 --- /dev/null +++ b/mobx-example-app/android/app/src/main/kotlin/com/example/zoom/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.mobx + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/mobx-example-app/android/app/src/main/res/drawable-v21/launch_background.xml b/mobx-example-app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/mobx-example-app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobx-example-app/android/app/src/main/res/drawable/launch_background.xml b/mobx-example-app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/mobx-example-app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobx-example-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobx-example-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/mobx-example-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/mobx-example-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobx-example-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/mobx-example-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/mobx-example-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobx-example-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/mobx-example-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/mobx-example-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobx-example-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/mobx-example-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/mobx-example-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobx-example-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/mobx-example-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/mobx-example-app/android/app/src/main/res/values-night/styles.xml b/mobx-example-app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..449a9f930 --- /dev/null +++ b/mobx-example-app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobx-example-app/android/app/src/main/res/values/styles.xml b/mobx-example-app/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..d74aa35c2 --- /dev/null +++ b/mobx-example-app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobx-example-app/android/app/src/profile/AndroidManifest.xml b/mobx-example-app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..066245a60 --- /dev/null +++ b/mobx-example-app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobx-example-app/android/build.gradle b/mobx-example-app/android/build.gradle new file mode 100644 index 000000000..f51b365c0 --- /dev/null +++ b/mobx-example-app/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/mobx-example-app/android/gradle.properties b/mobx-example-app/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/mobx-example-app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/mobx-example-app/android/gradle/wrapper/gradle-wrapper.properties b/mobx-example-app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..595fb867a --- /dev/null +++ b/mobx-example-app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip diff --git a/mobx-example-app/android/settings.gradle b/mobx-example-app/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/mobx-example-app/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/mobx-example-app/assets/raise_hand.png b/mobx-example-app/assets/raise_hand.png new file mode 100644 index 000000000..8290ad46d Binary files /dev/null and b/mobx-example-app/assets/raise_hand.png differ diff --git a/mobx-example-app/final.gif b/mobx-example-app/final.gif new file mode 100644 index 000000000..8fa52e320 Binary files /dev/null and b/mobx-example-app/final.gif differ diff --git a/mobx-example-app/ios/.gitignore b/mobx-example-app/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/mobx-example-app/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/mobx-example-app/ios/Flutter/AppFrameworkInfo.plist b/mobx-example-app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..8d4492f97 --- /dev/null +++ b/mobx-example-app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/mobx-example-app/ios/Flutter/Debug.xcconfig b/mobx-example-app/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/mobx-example-app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/mobx-example-app/ios/Flutter/Release.xcconfig b/mobx-example-app/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/mobx-example-app/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/mobx-example-app/ios/Podfile b/mobx-example-app/ios/Podfile new file mode 100644 index 000000000..2c068c404 --- /dev/null +++ b/mobx-example-app/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/mobx-example-app/ios/Podfile.lock b/mobx-example-app/ios/Podfile.lock new file mode 100644 index 000000000..6f9e6970b --- /dev/null +++ b/mobx-example-app/ios/Podfile.lock @@ -0,0 +1,33 @@ +PODS: + - Flutter (1.0.0) + - HMSSDK (0.3.1): + - HMSWebRTC (= 1.0.4897) + - hmssdk_flutter (0.7.2): + - Flutter + - HMSSDK (= 0.3.1) + - HMSWebRTC (1.0.4897) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - hmssdk_flutter (from `.symlinks/plugins/hmssdk_flutter/ios`) + +SPEC REPOS: + trunk: + - HMSSDK + - HMSWebRTC + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + hmssdk_flutter: + :path: ".symlinks/plugins/hmssdk_flutter/ios" + +SPEC CHECKSUMS: + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + HMSSDK: 9780df180a4c8bd3446fd2981b338856fd02b3a4 + hmssdk_flutter: 3fed63723ebebd88699728f9a6fd93984bf51c74 + HMSWebRTC: aa317dbbf56a191463a90491e5e37f298f8fa29c + +PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048 + +COCOAPODS: 1.11.3 diff --git a/mobx-example-app/ios/Runner.xcodeproj/project.pbxproj b/mobx-example-app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..b15005029 --- /dev/null +++ b/mobx-example-app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,551 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + ABF1B06C2DDDF39F78AE01C6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F091D1EEC2EEA33E658A593F /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2CB742DD2098388A1D58CB1E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 468287E1809784DBBA2DBEC4 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CFA5CE12C847F74F5CAD8504 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + F091D1EEC2EEA33E658A593F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ABF1B06C2DDDF39F78AE01C6 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 429FD9728E4A434511936ADF /* Pods */ = { + isa = PBXGroup; + children = ( + CFA5CE12C847F74F5CAD8504 /* Pods-Runner.debug.xcconfig */, + 468287E1809784DBBA2DBEC4 /* Pods-Runner.release.xcconfig */, + 2CB742DD2098388A1D58CB1E /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 429FD9728E4A434511936ADF /* Pods */, + C9FA8232B306B6A8F61F4103 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + C9FA8232B306B6A8F61F4103 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F091D1EEC2EEA33E658A593F /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 661CBA4853EB60FA923E2C99 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 3D9385206667969E4FF68B6F /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 3D9385206667969E4FF68B6F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 661CBA4853EB60FA923E2C99 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = BDBP9B69MD; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.govind.mobx; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = BDBP9B69MD; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.govind.mobx; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = BDBP9B69MD; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.govind.mobx; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/mobx-example-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobx-example-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobx-example-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..3db53b6e1 --- /dev/null +++ b/mobx-example-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobx-example-app/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobx-example-app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/mobx-example-app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/mobx-example-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobx-example-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/mobx-example-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobx-example-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobx-example-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/mobx-example-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobx-example-app/ios/Runner/AppDelegate.swift b/mobx-example-app/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/mobx-example-app/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/mobx-example-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/mobx-example-app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobx-example-app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/mobx-example-app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobx-example-app/ios/Runner/Base.lproj/Main.storyboard b/mobx-example-app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/mobx-example-app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobx-example-app/ios/Runner/Info.plist b/mobx-example-app/ios/Runner/Info.plist new file mode 100644 index 000000000..63e91c734 --- /dev/null +++ b/mobx-example-app/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + mobx + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSCameraUsageDescription + mobx wants to use your camera + NSLocalNetworkUsageDescription + mobx App wants to use your local network + NSMicrophoneUsageDescription + mobx wants to use your microphone + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/mobx-example-app/ios/Runner/Runner-Bridging-Header.h b/mobx-example-app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/mobx-example-app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/mobx-example-app/lib/main.dart b/mobx-example-app/lib/main.dart new file mode 100644 index 000000000..802002f3d --- /dev/null +++ b/mobx-example-app/lib/main.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:mobx_example/meeting.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: '100ms mobx', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + debugShowCheckedModeBanner: false, + home: const HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + TextEditingController txtName = TextEditingController(text: ""); + TextEditingController txtId = TextEditingController(text: ""); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("mobx Clone"), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Name", + style: TextStyle(fontSize: 20), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TextField( + controller: txtName, + decoration: const InputDecoration(hintText: 'Enter Your Name'), + ), + ), + const SizedBox( + height: 10, + ), + const Text( + "Enter Id", + style: TextStyle(fontSize: 20), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TextField( + controller: txtId, + decoration: const InputDecoration( + border: InputBorder.none, hintText: 'Enter Room Link'), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Meeting( + name: txtName.text, + roomLink: txtId.text, + )), + ); + }, + child: const Text( + "Join", + style: TextStyle(fontSize: 20), + )) + ], + ), + ); + } +} diff --git a/mobx-example-app/lib/meeting.dart b/mobx-example-app/lib/meeting.dart new file mode 100644 index 000000000..1a2717c8d --- /dev/null +++ b/mobx-example-app/lib/meeting.dart @@ -0,0 +1,358 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:mobx/mobx.dart'; +import 'package:mobx_example/message.dart'; +import 'package:mobx_example/setup/meeting_store.dart'; +import 'package:mobx_example/setup/peer_track_node.dart'; + +class Meeting extends StatefulWidget { + final String name, roomLink; + + const Meeting({Key? key, required this.name, required this.roomLink}) + : super(key: key); + + @override + _MeetingState createState() => _MeetingState(); +} + +class _MeetingState extends State with WidgetsBindingObserver { + late MeetingStore _meetingStore; + bool raisedHand = false; + bool selfLeave = false; + + initMeeting() async { + bool ans = await _meetingStore.join(widget.name, widget.roomLink); + if (!ans) { + const SnackBar(content: Text("Unable to Join")); + Navigator.of(context).pop(); + } + _meetingStore.addUpdateListener(); + } + + @override + void initState() { + super.initState(); + WidgetsBinding.instance!.addObserver(this); + _meetingStore = MeetingStore(); + initMeeting(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + child: Scaffold( + backgroundColor: Colors.grey, + appBar: AppBar( + title: const Text("100ms mobx"), + automaticallyImplyLeading: false, + actions: [ + IconButton( + onPressed: () { + _meetingStore.switchCamera(); + }, + icon: const Icon(Icons.camera_front)), + IconButton( + onPressed: () { + chatMessages(context, _meetingStore); + }, + icon: const Icon(Icons.message)) + ], + ), + body: Stack( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(vertical: 5.0, horizontal: 5.0), + child: Center( + child: SizedBox( + width: double.infinity, + child: Column( + children: [ + Flexible( + child: Observer( + builder: (_) { + if (_meetingStore.isRoomEnded && !selfLeave) { + Navigator.popUntil( + context, ModalRoute.withName('/main')); + } + if (_meetingStore.peerTracks.isEmpty) { + return const Center( + child: Text('Waiting for others to join!')); + } + ObservableList peerFilteredList = + _meetingStore.peerTracks; + + return videoPageView( + filteredList: peerFilteredList, + ); + }, + ), + ), + ], + ), + ), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + margin: + const EdgeInsets.symmetric(horizontal: 20, vertical: 40), + width: double.infinity, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Observer(builder: (context) { + return CircleAvatar( + backgroundColor: Colors.black, + child: IconButton( + icon: _meetingStore.isMicOn + ? const Icon(Icons.mic) + : const Icon(Icons.mic_off), + onPressed: () { + _meetingStore.switchAudio(); + }, + color: Colors.blue, + ), + ); + }), + Observer(builder: (context) { + return CircleAvatar( + backgroundColor: Colors.black, + child: IconButton( + icon: _meetingStore.isVideoOn + ? const Icon(Icons.videocam) + : const Icon(Icons.videocam_off), + onPressed: () { + _meetingStore.switchVideo(); + }, + color: Colors.blue, + ), + ); + }), + CircleAvatar( + backgroundColor: Colors.black, + child: IconButton( + icon: Image.asset( + 'assets/raise_hand.png', + color: + raisedHand ? Colors.amber.shade300 : Colors.grey, + ), + onPressed: () { + setState(() { + raisedHand = !raisedHand; + }); + _meetingStore.changeMetadata(); + }, + color: Colors.blue, + ), + ), + CircleAvatar( + backgroundColor: Colors.black, + child: IconButton( + icon: const Icon(Icons.call_end), + onPressed: () { + _meetingStore.leave(); + selfLeave = true; + Navigator.pop(context); + }, + color: Colors.red, + ), + ), + ], + ), + ), + ), + ], + ), + ), + onWillPop: () async { + bool ans = await _onBackPressed(); + return ans; + }, + ); + } + + Future _onBackPressed() { + return showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Leave the Meeting?', + style: TextStyle(fontSize: 24)), + actions: [ + ElevatedButton( + style: ElevatedButton.styleFrom(primary: Colors.red), + onPressed: () => { + _meetingStore.leave(), + Navigator.pop(context, true), + }, + child: const Text('Yes', style: TextStyle(fontSize: 24))), + ElevatedButton( + onPressed: () => Navigator.pop(context, false), + child: + const Text('Cancel', style: TextStyle(fontSize: 24))), + ], + )); + } + + Widget videoPageView({required List filteredList}) { + List pageChild = []; + if (_meetingStore.curentScreenShareTrack != null) { + pageChild.add(RotatedBox( + quarterTurns: 1, + child: Container( + margin: + const EdgeInsets.only(bottom: 0, left: 0, right: 100, top: 0), + child: Observer(builder: (context) { + return HMSVideoView( + track: _meetingStore.curentScreenShareTrack as HMSVideoTrack); + })), + )); + } + for (int i = 0; i < filteredList.length; i = i + 6) { + if (filteredList.length - i > 5) { + Widget temp = singleVideoPageView(6, i, filteredList); + pageChild.add(temp); + } else { + Widget temp = + singleVideoPageView(filteredList.length - i, i, filteredList); + pageChild.add(temp); + } + } + return PageView( + children: pageChild, + ); + } + + Widget singleVideoPageView(int count, int index, List tracks) { + return Align( + alignment: Alignment.center, + child: Container( + margin: const EdgeInsets.only( + bottom: 100, left: 10, right: 10, top: 10), + child: Observer(builder: (context) { + return videoViewGrid(count, index, tracks); + }))); + } + + Widget videoViewGrid(int count, int start, List tracks) { + ObservableMap trackUpdate = + _meetingStore.trackStatus; + return GridView.builder( + itemCount: count, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (itemBuilder, index) { + return Observer(builder: (context) { + return videoTile( + tracks[start + index], + !(tracks[start + index].peer.isLocal + ? !_meetingStore.isVideoOn + : (trackUpdate[tracks[start + index].peer.peerId]) == + HMSTrackUpdate.trackMuted), + MediaQuery.of(context).size.width / 2 - 25, + tracks[start + index].isRaiseHand); + }); + }, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 5, + crossAxisSpacing: 5, + childAspectRatio: 0.88), + ); + } + + Widget videoTile( + PeerTrackNode track, bool isVideoMuted, double size, bool isHandRaised) { + return Stack( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Align( + alignment: Alignment.center, + child: SizedBox( + width: size, + height: size, + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: (track.track != null && isVideoMuted) + ? HMSVideoView( + track: track.track as HMSVideoTrack, + scaleType: ScaleType.SCALE_ASPECT_FILL) + : Container( + width: 200, + height: 200, + color: Colors.black, + child: Center( + child: CircleAvatar( + radius: 50, + backgroundColor: Colors.green, + child: track.name.contains(" ") + ? Text( + (track.name.toString().substring(0, 1) + + track.name + .toString() + .split(" ")[1] + .substring(0, 1)) + .toUpperCase(), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700), + ) + : Text(track.name + .toString() + .substring(0, 1) + .toUpperCase()), + ), + ))), + ), + ), + const SizedBox( + height: 10, + ), + Align( + alignment: Alignment.bottomCenter, + child: Text( + track.name, + style: const TextStyle(fontWeight: FontWeight.w700), + ), + ) + ], + ), + Align( + alignment: Alignment.topLeft, + child: isHandRaised + ? Container( + margin: const EdgeInsets.all(10), + child: Image.asset( + 'assets/raise_hand.png', + scale: 2, + ), + ) + : Container(), + ), + ], + ); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + if (state == AppLifecycleState.resumed) { + if (_meetingStore.isVideoOn) { + _meetingStore.startCapturing(); + } else { + _meetingStore.stopCapturing(); + } + } else if (state == AppLifecycleState.paused) { + if (_meetingStore.isVideoOn) { + _meetingStore.stopCapturing(); + } + } else if (state == AppLifecycleState.inactive) { + if (_meetingStore.isVideoOn) { + _meetingStore.stopCapturing(); + } + } + } +} diff --git a/mobx-example-app/lib/message.dart b/mobx-example-app/lib/message.dart new file mode 100644 index 000000000..dcdc0ccce --- /dev/null +++ b/mobx-example-app/lib/message.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:intl/intl.dart'; +import 'package:mobx_example/setup/meeting_store.dart'; + +class Message extends StatefulWidget { + final MeetingStore meetingStore; + const Message({Key? key, required this.meetingStore}) : super(key: key); + + @override + _MessageState createState() => _MessageState(); +} + +class _MessageState extends State { + late MeetingStore _meetingStore; + late double widthOfScreen; + late List hmsRoles; + TextEditingController messageTextController = TextEditingController(); + @override + void initState() { + super.initState(); + _meetingStore = widget.meetingStore; + getRoles(); + } + + void getRoles() async { + hmsRoles = await _meetingStore.getRoles(); + } + + @override + Widget build(BuildContext context) { + widthOfScreen = MediaQuery.of(context).size.width; + final DateFormat formatter = DateFormat('yyyy-MM-dd hh:mm a'); + return FractionallySizedBox( + heightFactor: 0.8, + child: Container( + margin: + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(10.0), + color: Colors.blue, + child: Row( + children: [ + const Expanded( + child: Text( + "Message", + style: TextStyle(color: Colors.black, fontSize: 20.0), + ), + ), + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Icon( + Icons.clear, + size: 25.0, + ), + ) + ], + ), + ), + Expanded( + child: Observer( + builder: (_) { + if (!_meetingStore.isMeetingStarted) { + return const SizedBox(); + } + if (_meetingStore.messages.isEmpty) { + return const Text('No messages'); + } + return ListView.separated( + itemCount: _meetingStore.messages.length, + itemBuilder: (itemBuilder, index) { + return Container( + padding: const EdgeInsets.all(5.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: Text( + _meetingStore + .messages[index].sender?.name ?? + "", + style: const TextStyle( + fontSize: 10.0, + color: Colors.black, + fontWeight: FontWeight.bold), + ), + ), + Text( + formatter.format( + _meetingStore.messages[index].time), + style: const TextStyle( + fontSize: 10.0, + color: Colors.black, + fontWeight: FontWeight.w900), + ) + ], + ), + const SizedBox( + height: 10.0, + ), + Text( + _meetingStore.messages[index].message + .toString(), + style: const TextStyle( + fontSize: 14.0, + color: Colors.black, + fontWeight: FontWeight.w300), + ), + ], + ), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return const Divider(); + }, + ); + }, + ), + ), + Container( + color: Colors.grey, + margin: const EdgeInsets.only(top: 10.0), + child: Row( + children: [ + Container( + margin: const EdgeInsets.only(bottom: 5.0, left: 5.0), + child: TextField( + autofocus: true, + controller: messageTextController, + decoration: const InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + contentPadding: EdgeInsets.only( + left: 15, bottom: 11, top: 11, right: 15), + hintText: "Type a Message"), + ), + width: widthOfScreen - 45.0, + ), + GestureDetector( + onTap: () { + String message = messageTextController.text; + if (message.isEmpty) return; + _meetingStore.sendBroadcastMessage(message); + messageTextController.clear(); + }, + child: const Icon( + Icons.double_arrow, + size: 40.0, + ), + ) + ], + ), + ) + ], + ), + )), + ); + } +} + +void chatMessages(BuildContext context, MeetingStore meetingStore) { + showModalBottomSheet( + context: context, + builder: (ctx) => Message(meetingStore: meetingStore), + isScrollControlled: true); +} diff --git a/mobx-example-app/lib/service/room_service.dart b/mobx-example-app/lib/service/room_service.dart new file mode 100644 index 000000000..b432bcda4 --- /dev/null +++ b/mobx-example-app/lib/service/room_service.dart @@ -0,0 +1,57 @@ +//dart imports +import 'dart:convert'; + +//Package imports +import 'package:http/http.dart' as http; + +class RoomService { + Future?> getToken( + {required String user, required String room}) async { + List codeAndDomain = getCode(room) ?? []; + if (codeAndDomain.isEmpty) { + return null; + } + Uri endPoint = codeAndDomain[2] == "true" + ? Uri.parse("https://prod-in.100ms.live/hmsapi/get-token") + : Uri.parse("https://qa-in.100ms.live/hmsapi/get-token"); + http.Response response = await http.post(endPoint, body: { + 'code': (codeAndDomain[1] ?? "").trim(), + 'user_id': user, + }, headers: { + 'subdomain': (codeAndDomain[0] ?? "").trim() + }); + + var body = json.decode(response.body); + return [body['token'], codeAndDomain[2]!.trim()]; + } + + List? getCode(String roomUrl) { + String url = roomUrl; + if (url == "") return []; + url = url.trim(); + bool isProdM = url.contains(".app.100ms.live/meeting/"); + bool isProdP = url.contains(".app.100ms.live/preview/"); + bool isQaM = url.contains(".qa-app.100ms.live/meeting/"); + bool isQaP = url.contains(".qa-app.100ms.live/preview/"); + + if (!isProdM && !isQaM && isQaP && isProdP) return []; + + List codeAndDomain = []; + String code = ""; + String subDomain = ""; + if (isProdM || isProdP) { + codeAndDomain = isProdM + ? url.split(".app.100ms.live/meeting/") + : url.split(".app.100ms.live/preview/"); + code = codeAndDomain[1]; + subDomain = codeAndDomain[0].split("https://")[1] + ".app.100ms.live"; + } else if (isQaM || isQaP) { + codeAndDomain = isQaM + ? url.split(".qa-app.100ms.live/meeting/") + : url.split(".qa-app.100ms.live/preview/"); + code = codeAndDomain[1]; + subDomain = codeAndDomain[0].split("https://")[1] + ".qa-app.100ms.live"; + } + return [subDomain, code, isProdM || isProdP ? "true" : "false"]; + } +} diff --git a/mobx-example-app/lib/setup/hms_sdk_interactor.dart b/mobx-example-app/lib/setup/hms_sdk_interactor.dart new file mode 100644 index 000000000..84f9d2c45 --- /dev/null +++ b/mobx-example-app/lib/setup/hms_sdk_interactor.dart @@ -0,0 +1,239 @@ +//Project imports + +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; + +class HMSSDKInteractor { + late HMSConfig config; + late List messages; + late HMSSDK hmsSDK; + + HMSSDKInteractor() { + hmsSDK = HMSSDK(); + hmsSDK.build(); + } + + Future join({required HMSConfig config}) async { + this.config = config; + await hmsSDK.join(config: this.config); + } + + void leave({required HMSActionResultListener hmsActionResultListener}) async { + hmsSDK.leave(hmsActionResultListener: hmsActionResultListener); + } + + Future switchAudio({bool isOn = false}) async { + return await hmsSDK.switchAudio(isOn: isOn); + } + + Future switchVideo({bool isOn = false}) async { + return await hmsSDK.switchVideo(isOn: isOn); + } + + Future switchCamera() async { + return await hmsSDK.switchCamera(); + } + + Future isScreenShareActive() async { + return await hmsSDK.isScreenShareActive(); + } + + void sendBroadcastMessage( + String message, HMSActionResultListener hmsActionResultListener) { + hmsSDK.sendBroadcastMessage( + message: message, + type: "chat", + hmsActionResultListener: hmsActionResultListener); + } + + void sendDirectMessage(String message, HMSPeer peerTo, + HMSActionResultListener hmsActionResultListener) async { + hmsSDK.sendDirectMessage( + message: message, + peerTo: peerTo, + type: "chat", + hmsActionResultListener: hmsActionResultListener); + } + + void sendGroupMessage(String message, List hmsRolesTo, + HMSActionResultListener hmsActionResultListener) async { + hmsSDK.sendGroupMessage( + message: message, + hmsRolesTo: hmsRolesTo, + type: "chat", + hmsActionResultListener: hmsActionResultListener); + } + + Future preview({required HMSConfig config}) async { + this.config = config; + return hmsSDK.preview(config: config); + } + + void startHMSLogger(HMSLogLevel webRtclogLevel, HMSLogLevel logLevel) { + hmsSDK.startHMSLogger(webRtclogLevel: webRtclogLevel, logLevel: logLevel); + } + + void removeHMSLogger() { + hmsSDK.removeHMSLogger(); + } + + void addLogsListener(HMSLogListener hmsLogListener) { + hmsSDK.addLogListener(hmsLogListener: hmsLogListener); + } + + void removeLogsListener(HMSLogListener hmsLogListener) { + hmsSDK.removeLogListener(hmsLogListener: hmsLogListener); + } + + void addUpdateListener(HMSUpdateListener listener) { + hmsSDK.addUpdateListener(listener: listener); + } + + void removeUpdateListener(HMSUpdateListener listener) { + hmsSDK.removeUpdateListener(listener: listener); + } + + void addPreviewListener(HMSPreviewListener listener) { + hmsSDK.addPreviewListener(listener: listener); + } + + void removePreviewListener(HMSPreviewListener listener) { + hmsSDK.removePreviewListener(listener: listener); + } + + void acceptChangeRole(HMSRoleChangeRequest hmsRoleChangeRequest, + HMSActionResultListener hmsActionResultListener) { + hmsSDK.acceptChangeRole( + hmsRoleChangeRequest: hmsRoleChangeRequest, + hmsActionResultListener: hmsActionResultListener); + } + + void stopCapturing() { + hmsSDK.stopCapturing(); + } + + Future getLocalPeer() async { + return await hmsSDK.getLocalPeer(); + } + + Future startCapturing() async { + return await hmsSDK.startCapturing(); + } + + Future getPeer({required String peerId}) async { + List? peers = await hmsSDK.getPeers(); + + return peers?.firstWhere((element) => element.peerId == peerId); + } + + void changeTrackState(HMSTrack forRemoteTrack, bool mute, + HMSActionResultListener hmsActionResultListener) { + hmsSDK.changeTrackState( + forRemoteTrack: forRemoteTrack, + mute: mute, + hmsActionResultListener: hmsActionResultListener); + } + + void endRoom(bool lock, String reason, + HMSActionResultListener hmsActionResultListener) { + hmsSDK.endRoom( + lock: lock, + reason: reason, + hmsActionResultListener: hmsActionResultListener); + } + + void removePeer( + HMSPeer peer, HMSActionResultListener hmsActionResultListener) { + hmsSDK.removePeer( + peer: peer, + reason: "Removing Peer from Flutter", + hmsActionResultListener: hmsActionResultListener); + } + + void changeRole( + {required HMSPeer forPeer, + required HMSRole toRole, + bool force = false, + required HMSActionResultListener hmsActionResultListener}) { + hmsSDK.changeRole( + forPeer: forPeer, + toRole: toRole, + force: force, + hmsActionResultListener: hmsActionResultListener); + } + + Future> getRoles() async { + return await hmsSDK.getRoles(); + } + + Future isAudioMute(HMSPeer? peer) async { + return await hmsSDK.isAudioMute(peer: peer); + } + + Future isVideoMute(HMSPeer? peer) async { + return await hmsSDK.isVideoMute(peer: peer); + } + + void muteAll() { + hmsSDK.muteAll(); + } + + void startScreenShare({HMSActionResultListener? hmsActionResultListener}) { + hmsSDK.startScreenShare(hmsActionResultListener: hmsActionResultListener); + } + + void stopScreenShare({HMSActionResultListener? hmsActionResultListener}) { + hmsSDK.stopScreenShare(hmsActionResultListener: hmsActionResultListener); + } + + void unMuteAll() { + hmsSDK.unMuteAll(); + } + + void setPlayBackAllowed(bool allow) { + hmsSDK.setPlayBackAllowed(allow: allow); + } + + void startRtmpOrRecording(HMSRecordingConfig hmsRecordingConfig, + HMSActionResultListener hmsActionResultListener) { + hmsSDK.startRtmpOrRecording( + hmsRecordingConfig: hmsRecordingConfig, + hmsActionResultListener: hmsActionResultListener); + } + + void stopRtmpAndRecording(HMSActionResultListener hmsActionResultListener) { + hmsSDK.stopRtmpAndRecording( + hmsActionResultListener: hmsActionResultListener); + } + + Future getRoom() async { + return await hmsSDK.getRoom(); + } + + void changeMetadata( + {required String metadata, + required HMSActionResultListener hmsActionResultListener}) { + hmsSDK.changeMetadata( + metadata: metadata, hmsActionResultListener: hmsActionResultListener); + } + + void changeName( + {required String name, + required HMSActionResultListener hmsActionResultListener}) { + hmsSDK.changeName( + name: name, hmsActionResultListener: hmsActionResultListener); + } + + void changeTrackStateForRole(bool mute, HMSTrackKind? kind, String? source, + List? roles, HMSActionResultListener? hmsActionResultListener) { + hmsSDK.changeTrackStateForRole( + mute: mute, + kind: kind, + source: source, + roles: roles, + hmsActionResultListener: hmsActionResultListener); + } + + Future?> getPeers() async { + return await hmsSDK.getPeers(); + } +} diff --git a/mobx-example-app/lib/setup/meeting_store.dart b/mobx-example-app/lib/setup/meeting_store.dart new file mode 100644 index 000000000..ef880dcb5 --- /dev/null +++ b/mobx-example-app/lib/setup/meeting_store.dart @@ -0,0 +1,831 @@ +//Package imports +import 'package:flutter/cupertino.dart'; +import 'package:mobx/mobx.dart'; +import 'package:intl/intl.dart'; + +//Project imports +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:mobx_example/service/room_service.dart'; +import 'package:mobx_example/setup/hms_sdk_interactor.dart'; +import 'package:mobx_example/setup/peer_track_node.dart'; + +part 'meeting_store.g.dart'; + +class MeetingStore = MeetingStoreBase with _$MeetingStore; + +abstract class MeetingStoreBase extends ChangeNotifier + with Store + implements HMSUpdateListener, HMSActionResultListener { + late HMSSDKInteractor _hmsSDKInteractor; + + MeetingStoreBase() { + _hmsSDKInteractor = HMSSDKInteractor(); + } + + // HMSLogListener + @observable + bool isSpeakerOn = true; + @observable + String screenSharePeerId = ''; + @observable + HMSException? hmsException; + @observable + bool hasHlsStarted = false; + + String streamUrl = ""; + @observable + bool isHLSLink = false; + + @observable + HMSRoleChangeRequest? roleChangeRequest; + + @observable + bool isMeetingStarted = false; + @observable + bool isVideoOn = true; + @observable + bool isMicOn = true; + @observable + bool isScreenShareOn = false; + @observable + ObservableList screenShareTrack = ObservableList.of([]); + + @observable + HMSTrack? curentScreenShareTrack; + + @observable + bool reconnecting = false; + @observable + bool reconnected = false; + @observable + bool isRoomEnded = false; + @observable + bool isRecordingStarted = false; + @observable + String event = ''; + + @observable + HMSTrackChangeRequest? hmsTrackChangeRequest; + @observable + List roles = []; + + late int highestSpeakerIndex = -1; + + @observable + HMSPeer? localPeer; + @observable + HMSTrack? localTrack; + + @observable + HMSTrack? screenTrack; + + @observable + bool isActiveSpeakerMode = false; + + @observable + ObservableList activeSpeakerPeerTracksStore = + ObservableList.of([]); + + @observable + ObservableList tracks = ObservableList.of([]); + + @observable + ObservableList audioTracks = ObservableList.of([]); + + @observable + ObservableList messages = ObservableList.of([]); + + @observable + ObservableMap trackStatus = ObservableMap.of({}); + + @observable + ObservableMap audioTrackStatus = ObservableMap.of({}); + + @observable + ObservableList peerTracks = ObservableList.of([]); + + HMSRoom? hmsRoom; + + int firstTimeBuild = 0; + final DateFormat formatter = DateFormat('d MMM y h:mm:ss a'); + + @action + void addUpdateListener() { + _hmsSDKInteractor.addUpdateListener(this); + // startHMSLogger(HMSLogLevel.VERBOSE, HMSLogLevel.VERBOSE); + // addLogsListener(); + } + + @action + void removeUpdateListener() { + _hmsSDKInteractor.removeUpdateListener(this); + // removeLogsListener(); + } + + @action + Future join(String user, String roomUrl) async { + List? token = + await RoomService().getToken(user: user, room: roomUrl); + if (token == null) return false; + HMSConfig config = HMSConfig( + authToken: token[0]!, + userName: user, + endPoint: token[1] == "true" ? "" : "https://qa-init.100ms.live/init"); + + await _hmsSDKInteractor.join(config: config); + isMeetingStarted = true; + return true; + } + + void leave() async { + if (isScreenShareOn) { + isScreenShareOn = false; + _hmsSDKInteractor.stopScreenShare(); + } + _hmsSDKInteractor.leave(hmsActionResultListener: this); + isRoomEnded = true; + peerTracks.clear(); + } + + @action + Future switchAudio() async { + await _hmsSDKInteractor.switchAudio(isOn: isMicOn); + isMicOn = !isMicOn; + } + + @action + Future switchVideo() async { + await _hmsSDKInteractor.switchVideo(isOn: isVideoOn); + if (isVideoOn) { + trackStatus[localPeer!.peerId] = HMSTrackUpdate.trackMuted; + } else { + trackStatus[localPeer!.peerId] = HMSTrackUpdate.trackUnMuted; + } + + isVideoOn = !isVideoOn; + } + + @action + Future switchCamera() async { + await _hmsSDKInteractor.switchCamera(); + } + + @action + void sendBroadcastMessage(String message) { + _hmsSDKInteractor.sendBroadcastMessage(message, this); + } + + void sendDirectMessage(String message, HMSPeer peer) async { + _hmsSDKInteractor.sendDirectMessage(message, peer, this); + } + + void sendGroupMessage(String message, List roles) async { + _hmsSDKInteractor.sendGroupMessage(message, roles, this); + } + + @action + void toggleSpeaker() { + if (isSpeakerOn) { + muteAll(); + } else { + unMuteAll(); + } + isSpeakerOn = !isSpeakerOn; + } + + Future isAudioMute(HMSPeer? peer) async { + return await _hmsSDKInteractor.isAudioMute(peer); + } + + Future isVideoMute(HMSPeer? peer) async { + return await _hmsSDKInteractor.isVideoMute(peer); + } + + Future startCapturing() async { + return await _hmsSDKInteractor.startCapturing(); + } + + void stopCapturing() { + _hmsSDKInteractor.stopCapturing(); + } + + @action + void removeTrackWithTrackId(String trackId) { + tracks.removeWhere((eachTrack) => eachTrack.trackId == trackId); + } + + @action + void removeTrackWithPeerIdExtra(String trackId) { + var index = tracks.indexWhere((element) => trackId == element.trackId); + tracks.removeAt(index); + } + + @action + void updateRoleChangeRequest(HMSRoleChangeRequest roleChangeRequest) { + this.roleChangeRequest = roleChangeRequest; + } + + @action + void addMessage(HMSMessage message) { + messages.add(message); + } + + @action + void addTrackChangeRequestInstance( + HMSTrackChangeRequest hmsTrackChangeRequest) { + this.hmsTrackChangeRequest = hmsTrackChangeRequest; + } + + @action + Future isScreenShareActive() async { + isScreenShareOn = await _hmsSDKInteractor.isScreenShareActive(); + } + + @override + void onJoin({required HMSRoom room}) async { + hmsRoom = room; + + if (room.hmshlsStreamingState?.running ?? false) { + hasHlsStarted = true; + streamUrl = room.hmshlsStreamingState?.variants[0]?.hlsStreamUrl ?? ""; + } else { + hasHlsStarted = false; + } + if (room.hmsBrowserRecordingState?.running == true) { + isRecordingStarted = true; + } else { + isRecordingStarted = false; + } + + for (HMSPeer each in room.peers!) { + if (each.isLocal) { + int index = peerTracks + .indexWhere((element) => element.peer.peerId == each.peerId); + if (index == -1) { + peerTracks.add(PeerTrackNode(peer: each, name: each.name)); + } + localPeer = each; + if (each.videoTrack != null) { + if (each.videoTrack!.kind == HMSTrackKind.kHMSTrackKindVideo) { + int index = peerTracks + .indexWhere((element) => element.peer.peerId == each.peerId); + peerTracks[index].track = each.videoTrack!; + + localTrack = each.videoTrack; + if (each.videoTrack!.isMute) { + isVideoOn = false; + } + } + } + break; + } + } + + roles = await getRoles(); + } + + @override + void onRoomUpdate({required HMSRoom room, required HMSRoomUpdate update}) { + switch (update) { + case HMSRoomUpdate.browserRecordingStateUpdated: + isRecordingStarted = room.hmsBrowserRecordingState?.running ?? false; + break; + + case HMSRoomUpdate.serverRecordingStateUpdated: + isRecordingStarted = room.hmsServerRecordingState?.running ?? false; + break; + + case HMSRoomUpdate.rtmpStreamingStateUpdated: + isRecordingStarted = room.hmsRtmpStreamingState?.running ?? false; + break; + case HMSRoomUpdate.hlsStreamingStateUpdated: + hasHlsStarted = room.hmshlsStreamingState?.running ?? false; + + streamUrl = hasHlsStarted + ? room.hmshlsStreamingState?.variants[0]?.hlsStreamUrl ?? "" + : ""; + break; + default: + print('on room update ${update.toString()}'); + } + } + + @override + void onPeerUpdate({required HMSPeer peer, required HMSPeerUpdate update}) { + peerOperation(peer, update); + } + + @override + void onTrackUpdate( + {required HMSTrack track, + required HMSTrackUpdate trackUpdate, + required HMSPeer peer}) { + if (isSpeakerOn) { + unMuteAll(); + } else { + muteAll(); + } + + if (peer.isLocal && + trackUpdate == HMSTrackUpdate.trackMuted && + track.kind == HMSTrackKind.kHMSTrackKindAudio) { + isMicOn = false; + } + + if (peer.isLocal) { + localPeer = peer; + + if (track.kind == HMSTrackKind.kHMSTrackKindVideo) { + localTrack = track; + if (track.isMute) { + isVideoOn = false; + } + } + } + + if (track.kind == HMSTrackKind.kHMSTrackKindAudio) { + int index = peerTracks + .indexWhere((element) => element.peer.peerId == peer.peerId); + if (index != -1) peerTracks[index].audioTrack = track; + return; + } + + if (track.source == "REGULAR") { + int index = peerTracks + .indexWhere((element) => element.peer.peerId == peer.peerId); + if (index != -1) peerTracks[index].track = track as HMSVideoTrack; + } + + peerOperationWithTrack(peer, trackUpdate, track); + } + + @override + void onHMSError({required HMSException error}) { + hmsException = hmsException; + } + + @override + void onMessage({required HMSMessage message}) { + addMessage(message); + } + + @override + void onRoleChangeRequest({required HMSRoleChangeRequest roleChangeRequest}) { + updateRoleChangeRequest(roleChangeRequest); + } + + HMSTrack? previousHighestVideoTrack; + int previousHighestIndex = -1; + @observable + ObservableMap observableMap = ObservableMap.of({}); + + @override + void onUpdateSpeakers({required List updateSpeakers}) { + //Highest Speaker Update is currently Off + // if (!isActiveSpeakerMode) { + // if (updateSpeakers.length == 0) { + // peerTracks.removeAt(highestSpeakerIndex); + // peerTracks.insert(highestSpeakerIndex, highestSpeaker); + // highestSpeaker = PeerTracKNode(peerId: "-1"); + // return; + // } + // + // highestSpeakerIndex = peerTracks.indexWhere((element) => + // element.peerId.trim() == updateSpeakers[0].peer.peerId.trim()); + // + // print("index is $highestSpeakerIndex"); + // if (highestSpeakerIndex != -1) { + // highestSpeaker = peerTracks[highestSpeakerIndex]; + // peerTracks.removeAt(highestSpeakerIndex); + // peerTracks.insert(highestSpeakerIndex, highestSpeaker); + // } else { + // highestSpeaker = PeerTracKNode(peerId: "-1"); + // } + // } else { + // if (updateSpeakers.length == 0) { + // activeSpeakerPeerTracksStore.removeAt(0); + // activeSpeakerPeerTracksStore.insert(0, highestSpeaker); + // highestSpeaker = PeerTracKNode(peerId: "-1"); + // return; + // } + // highestSpeakerIndex = activeSpeakerPeerTracksStore.indexWhere((element) => + // element.peerId.trim() == updateSpeakers[0].peer.peerId.trim()); + // + // print("index is $highestSpeakerIndex"); + // if (highestSpeakerIndex != -1) { + // highestSpeaker = activeSpeakerPeerTracksStore[highestSpeakerIndex]; + // activeSpeakerPeerTracksStore.removeAt(highestSpeakerIndex); + // activeSpeakerPeerTracksStore.insert(0, highestSpeaker); + // } else { + // highestSpeaker = PeerTracKNode(peerId: "-1"); + // } + // } + } + + @override + void onReconnecting() { + reconnected = false; + reconnecting = true; + } + + @override + void onReconnected() { + reconnecting = false; + reconnected = true; + } + + int trackChange = -1; + + @override + void onChangeTrackStateRequest( + {required HMSTrackChangeRequest hmsTrackChangeRequest}) { + if (!hmsTrackChangeRequest.mute) { + addTrackChangeRequestInstance(hmsTrackChangeRequest); + } + } + + void changeTracks(HMSTrackChangeRequest hmsTrackChangeRequest) { + if (hmsTrackChangeRequest.track.kind == HMSTrackKind.kHMSTrackKindVideo) { + switchVideo(); + } else { + switchAudio(); + } + } + + @override + void onRemovedFromRoom( + {required HMSPeerRemovedFromPeer hmsPeerRemovedFromPeer}) { + peerTracks.clear(); + isRoomEnded = true; + } + + void changeRole( + {required HMSPeer peer, + required HMSRole roleName, + bool forceChange = false}) { + _hmsSDKInteractor.changeRole( + toRole: roleName, + forPeer: peer, + force: forceChange, + hmsActionResultListener: this); + } + + Future> getRoles() async { + return await _hmsSDKInteractor.getRoles(); + } + + void changeTrackState(HMSTrack track, bool mute) { + return _hmsSDKInteractor.changeTrackState(track, mute, this); + } + + @action + void peerOperation(HMSPeer peer, HMSPeerUpdate update) { + switch (update) { + case HMSPeerUpdate.peerJoined: + if (peer.role.name.contains("hls-") == false) { + int index = peerTracks + .indexWhere((element) => element.peer.peerId == peer.peerId); + if (index == -1) { + peerTracks.add(PeerTrackNode(peer: peer, name: peer.name)); + } + } + break; + case HMSPeerUpdate.peerLeft: + peerTracks.removeWhere((element) => element.peer.peerId == peer.peerId); + break; + case HMSPeerUpdate.roleUpdated: + if (peer.isLocal) { + localPeer = peer; + if (!peer.role.name.contains("hls-")) { + isHLSLink = false; + } + } + if (peer.role.name.contains("hls-") == false) { + int index = peerTracks + .indexWhere((element) => element.peer.peerId == peer.peerId); + //if (index != -1) peerTracks[index].track = track; + if (index == -1) { + peerTracks.add(PeerTrackNode(peer: peer, name: peer.name)); + } + } + break; + case HMSPeerUpdate.metadataChanged: + int index = peerTracks + .indexWhere((element) => element.peer.peerId == peer.peerId); + if (index != -1) { + peerTracks[index].isRaiseHand = + (peer.metadata?.contains("\"isHandRaised\":true") ?? false); + } + if (peer.isLocal) { + localPeer = peer; + } + break; + case HMSPeerUpdate.nameChanged: + if (peer.isLocal) { + int localPeerIndex = peerTracks.indexWhere( + (element) => element.peer.peerId == localPeer!.peerId); + if (localPeerIndex != -1) { + peerTracks[localPeerIndex].name = peer.name; + localPeer = peer; + } + } else { + int remotePeerIndex = peerTracks + .indexWhere((element) => element.peer.peerId == peer.peerId); + if (remotePeerIndex != -1) { + peerTracks[remotePeerIndex].name = peer.name; + } + } + break; + case HMSPeerUpdate.defaultUpdate: + print("Some default update or untouched case"); + break; + default: + print("Some default update or untouched case"); + } + } + + @action + void peerOperationWithTrack( + HMSPeer peer, HMSTrackUpdate update, HMSTrack track) { + switch (update) { + case HMSTrackUpdate.trackAdded: + if (track.source == "REGULAR") { + trackStatus[peer.peerId] = track.isMute + ? HMSTrackUpdate.trackMuted + : HMSTrackUpdate.trackUnMuted; + } else { + screenShareTrack.add(track); + curentScreenShareTrack = screenShareTrack.first; + screenSharePeerId = peer.peerId; + isScreenShareActive(); + } + break; + case HMSTrackUpdate.trackRemoved: + if (track.source != "REGULAR") { + screenShareTrack + .removeWhere((element) => element?.trackId == track.trackId); + if (screenShareTrack.isNotEmpty) { + curentScreenShareTrack = screenShareTrack.first; + screenSharePeerId = peer.peerId; + } else { + curentScreenShareTrack = null; + screenSharePeerId = ""; + } + } else { + peerTracks + .removeWhere((element) => element.peer.peerId == peer.peerId); + isScreenShareActive(); + } + break; + case HMSTrackUpdate.trackMuted: + trackStatus[peer.peerId] = HMSTrackUpdate.trackMuted; + break; + case HMSTrackUpdate.trackUnMuted: + trackStatus[peer.peerId] = HMSTrackUpdate.trackUnMuted; + break; + case HMSTrackUpdate.trackDescriptionChanged: + break; + case HMSTrackUpdate.trackDegraded: + break; + case HMSTrackUpdate.trackRestored: + break; + case HMSTrackUpdate.defaultUpdate: + break; + default: + print("Some default update or untouched case"); + } + } + + void endRoom(bool lock, String? reason) { + _hmsSDKInteractor.endRoom(lock, reason ?? "", this); + } + + void removePeerFromRoom(HMSPeer peer) { + _hmsSDKInteractor.removePeer(peer, this); + } + + void startScreenShare() { + _hmsSDKInteractor.startScreenShare(hmsActionResultListener: this); + } + + void stopScreenShare() { + _hmsSDKInteractor.stopScreenShare(hmsActionResultListener: this); + } + + void muteAll() { + _hmsSDKInteractor.muteAll(); + } + + void unMuteAll() { + _hmsSDKInteractor.unMuteAll(); + } + + Future getLocalPeer() async { + return await _hmsSDKInteractor.getLocalPeer(); + } + + void startRtmpOrRecording( + {required String meetingUrl, + required bool toRecord, + List? rtmpUrls}) async { + HMSRecordingConfig hmsRecordingConfig = HMSRecordingConfig( + meetingUrl: meetingUrl, toRecord: toRecord, rtmpUrls: rtmpUrls); + + _hmsSDKInteractor.startRtmpOrRecording(hmsRecordingConfig, this); + } + + void stopRtmpAndRecording() async { + _hmsSDKInteractor.stopRtmpAndRecording(this); + } + + Future getRoom() async { + HMSRoom? room = await _hmsSDKInteractor.getRoom(); + return room; + } + + Future getPeer({required String peerId}) async { + return await _hmsSDKInteractor.getPeer(peerId: peerId); + } + + bool isRaisedHand = false; + + void changeMetadata() { + isRaisedHand = !isRaisedHand; + String value = isRaisedHand ? "true" : "false"; + _hmsSDKInteractor.changeMetadata( + metadata: "{\"isHandRaised\":$value}", hmsActionResultListener: this); + } + + void setPlayBackAllowed(bool allow) { + _hmsSDKInteractor.setPlayBackAllowed(allow); + } + + void acceptChangeRole(HMSRoleChangeRequest hmsRoleChangeRequest) { + _hmsSDKInteractor.acceptChangeRole(hmsRoleChangeRequest, this); + } + + void changeName({required String name}) { + _hmsSDKInteractor.changeName(name: name, hmsActionResultListener: this); + } + + void changeTrackStateForRole(bool mute, List? roles) { + _hmsSDKInteractor.changeTrackStateForRole( + true, HMSTrackKind.kHMSTrackKindAudio, "regular", roles, this); + } + + @override + void onSuccess( + {HMSActionResultListenerMethod methodType = + HMSActionResultListenerMethod.unknown, + Map? arguments}) { + switch (methodType) { + case HMSActionResultListenerMethod.leave: + isRoomEnded = true; + break; + case HMSActionResultListenerMethod.changeTrackState: + break; + case HMSActionResultListenerMethod.changeMetadata: + print("raised hand"); + break; + case HMSActionResultListenerMethod.endRoom: + isRoomEnded = true; + break; + case HMSActionResultListenerMethod.removePeer: + break; + case HMSActionResultListenerMethod.acceptChangeRole: + break; + case HMSActionResultListenerMethod.changeRole: + break; + case HMSActionResultListenerMethod.changeTrackStateForRole: + event = arguments!['roles'] == null + ? "Successfully Muted All" + : "Successfully Muted Role"; + break; + case HMSActionResultListenerMethod.startRtmpOrRecording: + break; + case HMSActionResultListenerMethod.stopRtmpAndRecording: + break; + case HMSActionResultListenerMethod.unknown: + break; + case HMSActionResultListenerMethod.changeName: + event = "Name Changed to ${localPeer!.name}"; + break; + case HMSActionResultListenerMethod.sendBroadcastMessage: + var message = HMSMessage( + sender: localPeer, + message: arguments!['message'], + type: arguments['type'], + time: DateTime.now(), + hmsMessageRecipient: HMSMessageRecipient( + recipientPeer: null, + recipientRoles: null, + hmsMessageRecipientType: HMSMessageRecipientType.BROADCAST)); + addMessage(message); + break; + case HMSActionResultListenerMethod.sendGroupMessage: + var message = HMSMessage( + sender: localPeer, + message: arguments!['message'], + type: arguments['type'], + time: DateTime.now(), + hmsMessageRecipient: HMSMessageRecipient( + recipientPeer: null, + recipientRoles: arguments['roles'], + hmsMessageRecipientType: HMSMessageRecipientType.GROUP)); + addMessage(message); + break; + case HMSActionResultListenerMethod.sendDirectMessage: + var message = HMSMessage( + sender: localPeer, + message: arguments!['message'], + type: arguments['type'], + time: DateTime.now(), + hmsMessageRecipient: HMSMessageRecipient( + recipientPeer: arguments['peer'], + recipientRoles: null, + hmsMessageRecipientType: HMSMessageRecipientType.DIRECT)); + addMessage(message); + break; + case HMSActionResultListenerMethod.hlsStreamingStarted: + break; + case HMSActionResultListenerMethod.hlsStreamingStopped: + break; + + case HMSActionResultListenerMethod.startScreenShare: + print("startScreenShare success"); + isScreenShareActive(); + break; + + case HMSActionResultListenerMethod.stopScreenShare: + print("stopScreenShare success"); + isScreenShareActive(); + break; + } + } + + @override + void onException( + {HMSActionResultListenerMethod methodType = + HMSActionResultListenerMethod.unknown, + Map? arguments, + required HMSException hmsException}) { + this.hmsException = hmsException; + switch (methodType) { + case HMSActionResultListenerMethod.leave: + break; + case HMSActionResultListenerMethod.changeTrackState: + break; + case HMSActionResultListenerMethod.changeMetadata: + break; + case HMSActionResultListenerMethod.endRoom: + print("HMSException ${hmsException.message}"); + break; + case HMSActionResultListenerMethod.removePeer: + break; + case HMSActionResultListenerMethod.acceptChangeRole: + break; + case HMSActionResultListenerMethod.changeRole: + break; + case HMSActionResultListenerMethod.changeTrackStateForRole: + event = "Failed to Mute"; + break; + case HMSActionResultListenerMethod.startRtmpOrRecording: + if (hmsException.code?.errorCode == "400") { + isRecordingStarted = true; + } + break; + case HMSActionResultListenerMethod.stopRtmpAndRecording: + break; + case HMSActionResultListenerMethod.unknown: + print("Unknown Method Called"); + break; + case HMSActionResultListenerMethod.changeName: + break; + case HMSActionResultListenerMethod.sendBroadcastMessage: + print("sendBroadcastMessage failure"); + break; + case HMSActionResultListenerMethod.sendGroupMessage: + break; + case HMSActionResultListenerMethod.sendDirectMessage: + break; + case HMSActionResultListenerMethod.hlsStreamingStarted: + break; + case HMSActionResultListenerMethod.hlsStreamingStopped: + break; + + case HMSActionResultListenerMethod.startScreenShare: + print("startScreenShare exception"); + isScreenShareActive(); + break; + + case HMSActionResultListenerMethod.stopScreenShare: + print("stopScreenShare exception"); + isScreenShareActive(); + break; + } + } + + Future?> getPeers() async { + return await _hmsSDKInteractor.getPeers(); + } +} diff --git a/mobx-example-app/lib/setup/peer_track_node.dart b/mobx-example-app/lib/setup/peer_track_node.dart new file mode 100644 index 000000000..12c3d3825 --- /dev/null +++ b/mobx-example-app/lib/setup/peer_track_node.dart @@ -0,0 +1,33 @@ +import 'package:hmssdk_flutter/hmssdk_flutter.dart'; +import 'package:mobx/mobx.dart'; + +@observable +class PeerTrackNode { + HMSPeer peer; + String name; + bool isRaiseHand; + @observable + HMSVideoTrack? track; + HMSTrack? audioTrack; + PeerTrackNode( + {required this.peer, + this.track, + this.name = "", + this.audioTrack, + this.isRaiseHand = false}); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PeerTrackNode && + runtimeType == other.runtimeType && + peer.peerId == other.peer.peerId; + + @override + String toString() { + return 'PeerTracKNode{peerId: ${peer.peerId}, name: $name, track: $track}'; + } + + @override + int get hashCode => peer.peerId.hashCode; +} diff --git a/mobx-example-app/pubspec.lock b/mobx-example-app/pubspec.lock new file mode 100644 index 000000000..ef42fb446 --- /dev/null +++ b/mobx-example-app/pubspec.lock @@ -0,0 +1,497 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "40.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.11" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.3" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.3.2" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + flutter_mobx: + dependency: "direct main" + description: + name: flutter_mobx + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6+1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + hmssdk_flutter: + dependency: "direct main" + description: + name: hmssdk_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.2" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.5.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + mobx: + dependency: "direct main" + description: + name: mobx + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + mobx_codegen: + dependency: "direct main" + description: + name: mobx_codegen + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.2" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.8" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" +sdks: + dart: ">=2.16.0 <3.0.0" + flutter: ">=1.20.0" diff --git a/mobx-example-app/pubspec.yaml b/mobx-example-app/pubspec.yaml new file mode 100644 index 000000000..dab3908cc --- /dev/null +++ b/mobx-example-app/pubspec.yaml @@ -0,0 +1,96 @@ +name: mobx_example +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + hmssdk_flutter: + path: ../ + + cupertino_icons: + mobx: + flutter_mobx: + mobx_codegen: + http: + intl: + + + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.0.5 + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/mobx-example-app/test/widget_test.dart b/mobx-example-app/test/widget_test.dart new file mode 100644 index 000000000..d468d7268 --- /dev/null +++ b/mobx-example-app/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:mobx_example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/mobx-example-app/web/favicon.png b/mobx-example-app/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/mobx-example-app/web/favicon.png differ diff --git a/mobx-example-app/web/icons/Icon-192.png b/mobx-example-app/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/mobx-example-app/web/icons/Icon-192.png differ diff --git a/mobx-example-app/web/icons/Icon-512.png b/mobx-example-app/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/mobx-example-app/web/icons/Icon-512.png differ diff --git a/mobx-example-app/web/icons/Icon-maskable-192.png b/mobx-example-app/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/mobx-example-app/web/icons/Icon-maskable-192.png differ diff --git a/mobx-example-app/web/icons/Icon-maskable-512.png b/mobx-example-app/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/mobx-example-app/web/icons/Icon-maskable-512.png differ diff --git a/mobx-example-app/web/index.html b/mobx-example-app/web/index.html new file mode 100644 index 000000000..acfff8bcc --- /dev/null +++ b/mobx-example-app/web/index.html @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + mobx + + + + + + + diff --git a/mobx-example-app/web/manifest.json b/mobx-example-app/web/manifest.json new file mode 100644 index 000000000..9b7685138 --- /dev/null +++ b/mobx-example-app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "mobx", + "short_name": "mobx", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/pubspec.yaml b/pubspec.yaml index 4d5af23f1..2901c6bba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: hmssdk_flutter description: The Flutter package for 100ms SDK. Video Conferencing infrastructure for a Video first world. -version: 0.7.2 +version: 0.7.3 homepage: https://www.100ms.live/ repository: https://github.com/100mslive/100ms-flutter issue_tracker: https://github.com/100mslive/100ms-flutter/issues