diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index b94ffc11..f14dd823 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -2,18 +2,20 @@ package com.hmg.hmgDr import android.app.Activity import android.content.ComponentName +import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.Bundle import android.os.IBinder import android.util.Log +import android.view.inputmethod.InputMethodManager +import android.widget.EditText import androidx.annotation.NonNull import com.google.gson.GsonBuilder import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel import com.hmg.hmgDr.Service.VideoStreamContainerService import com.hmg.hmgDr.ui.VideoCallResponseListener -import com.hmg.hmgDr.ui.fragment.VideoCallFragment import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall @@ -21,7 +23,8 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant -class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, VideoCallResponseListener { +class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, + VideoCallResponseListener { private val CHANNEL = "Dr.cloudSolution/videoCall" private lateinit var methodChannel: MethodChannel @@ -33,8 +36,6 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, private var videoStreamService: VideoStreamContainerService? = null private var bound = false - private var dialogFragment: VideoCallFragment? = null - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) @@ -62,14 +63,15 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, val doctorId = call.argument("DoctorId") val patientName = call.argument("patientName") - val sessionStatusModel = GetSessionStatusModel(VC_ID, tokenID, generalId, doctorId, patientName) + val sessionStatusModel = + GetSessionStatusModel(VC_ID, tokenID, generalId, doctorId, patientName) openVideoCall(apiKey, sessionId, token, appLang, baseUrl, sessionStatusModel) } "closeVideoCall" -> { - dialogFragment?.onCallClicked() + videoStreamService?.closeVideoCall() } "onCallConnected" -> { @@ -80,7 +82,14 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, } } - private fun openVideoCall(apiKey: String?, sessionId: String?, token: String?, appLang: String?, baseUrl: String?, sessionStatusModel: GetSessionStatusModel) { + private fun openVideoCall( + apiKey: String?, + sessionId: String?, + token: String?, + appLang: String?, + baseUrl: String?, + sessionStatusModel: GetSessionStatusModel + ) { val arguments = Bundle() arguments.putString("apiKey", apiKey) arguments.putString("sessionId", sessionId) @@ -89,31 +98,14 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, arguments.putString("baseUrl", baseUrl) arguments.putParcelable("sessionStatusModel", sessionStatusModel) +// showSoftKeyBoard(null) // start service serviceIntent = Intent(this@MainActivity, VideoStreamContainerService::class.java) serviceIntent?.run { putExtras(arguments) startService(this) } - - VideoStreamContainerService.videoCallResponseListener = this - - if (dialogFragment == null) { - val transaction = supportFragmentManager.beginTransaction() - dialogFragment = VideoCallFragment.newInstance(arguments) - dialogFragment?.let { - it.setCallListener(this) - it.isCancelable = true - if (it.isAdded){ - it.dismiss() - }else { - it.show(transaction, "dialog") - } - } - } else if (!dialogFragment!!.isVisible) { - val transaction = supportFragmentManager.beginTransaction() - dialogFragment!!.show(transaction, "dialog") - } + bindService() } /* override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -143,9 +135,9 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, }*/ override fun onCallFinished(resultCode: Int, intent: Intent?) { - dialogFragment = null + // TODO uncomment it - if (resultCode == Activity.RESULT_OK) { + /*if (resultCode == Activity.RESULT_OK) { val result: SessionStatusModel? = intent?.getParcelableExtra("sessionStatusNotRespond") val callResponse: HashMap = HashMap() @@ -158,7 +150,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, try { this.result?.success(callResponse) - } catch (e : Exception){ + } catch (e: Exception) { Log.e("onVideoCallFinished", "${e.message}.") } } else if (resultCode == Activity.RESULT_CANCELED) { @@ -166,15 +158,14 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, callResponse["callResponse"] = "CallEnd" try { result?.success(callResponse) - } catch (e : Exception){ + } catch (e: Exception) { Log.e("onVideoCallFinished", "${e.message}.") } } - } - - override fun errorHandle(message: String) { - dialogFragment = null -// Toast.makeText(this, message, Toast.LENGTH_LONG).show() + */ + stopService(serviceIntent) + unbindService() + videoStreamService!!.serviceRunning = false } override fun minimizeVideoEvent(isMinimize: Boolean) { @@ -188,23 +179,30 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, super.onBackPressed() } - override fun onStart() { - super.onStart() - bindService() - } - - override fun onStop() { - super.onStop() - unbindService() - } +// override fun onStart() { +// super.onStart() +// bindService() +// } +// +// override fun onStop() { +// super.onStop() +// unbindService() +// } private fun bindService() { - bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE) + serviceIntent?.run { + if (videoStreamService != null && !videoStreamService!!.serviceRunning){ + startService(this) + } + bindService(this, serviceConnection, Context.BIND_AUTO_CREATE) + videoStreamService?.serviceRunning = true + } } private fun unbindService() { if (bound) { - gpsService.registerCallBack(null) // unregister + videoStreamService!!.videoCallResponseListener = null // unregister + videoStreamService!!.mActivity = null unbindService(serviceConnection) bound = false } @@ -212,10 +210,12 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, private val serviceConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - val binder: VideoStreamContainerService.GPSBinder = service as GPSService.GPSBinder - gpsService = binder.getService() + val binder: VideoStreamContainerService.VideoStreamBinder = + service as VideoStreamContainerService.VideoStreamBinder + videoStreamService = binder.service bound = true - gpsService.registerCallBack(this@YourActivity) // register + videoStreamService!!.videoCallResponseListener = this@MainActivity // register + videoStreamService!!.mActivity = this@MainActivity // register } override fun onServiceDisconnected(name: ComponentName?) { @@ -224,4 +224,18 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler, } + // code to hide soft keyboard + fun hideSoftKeyBoard(editBox: EditText?) { + val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(editBox?.windowToken, 0) + } + + + // code to show soft keyboard + private fun showSoftKeyBoard(editBox: EditText?) { + val inputMethodManager = this.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager + editBox?.requestFocus() + inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) + } + } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/Service/VideoStreamContainerService.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/Service/VideoStreamContainerService.kt index cbb811c1..add401ff 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/Service/VideoStreamContainerService.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/Service/VideoStreamContainerService.kt @@ -2,29 +2,90 @@ package com.hmg.hmgDr.Service import android.app.Service import android.content.Intent +import android.os.Binder +import android.os.Bundle import android.os.IBinder -import android.widget.Toast +import com.hmg.hmgDr.MainActivity import com.hmg.hmgDr.ui.VideoCallResponseListener +import com.hmg.hmgDr.ui.fragment.VideoCallFragment -object VideoStreamContainerService : Service() { +class VideoStreamContainerService : Service(), VideoCallResponseListener { - // https://stackoverflow.com/questions/2463175/how-to-have-android-service-communicate-with-activity var videoCallResponseListener: VideoCallResponseListener? = null + var mActivity: MainActivity? = null + set(value) { + field = value + if (field != null) { + setDialogFragment() + } + } + var arguments: Bundle? = null + var serviceRunning: Boolean = false + + + private val serviceBinder: IBinder = VideoStreamBinder() + private var dialogFragment: VideoCallFragment? = null - override fun onBind(intent: Intent?): IBinder? { - return null + override fun onBind(intent: Intent?): IBinder { + return serviceBinder } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - if (intent != null){ + private fun setDialogFragment() { + mActivity?.run { + if (dialogFragment == null) { + val transaction = supportFragmentManager.beginTransaction() + dialogFragment = VideoCallFragment.newInstance(arguments!!) + dialogFragment?.let { + it.setCallListener(this@VideoStreamContainerService) + it.isCancelable = true + if (it.isAdded) { + it.dismiss() + } else { + it.show(transaction, "dialog") + } + } + } else if (!dialogFragment!!.isVisible) { + val transaction = supportFragmentManager.beginTransaction() + dialogFragment!!.show(transaction, "dialog") + } else { + // don't do anything + } + } + } + + fun closeVideoCall(){ + dialogFragment?.onCallClicked() + } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent != null && intent.extras != null) { + arguments = intent.extras } - Toast.makeText(this, "Service started by user.", Toast.LENGTH_LONG).show() +// Toast.makeText(this, "Service started by user.", Toast.LENGTH_LONG).show() return START_STICKY } override fun onDestroy() { super.onDestroy() - Toast.makeText(this, "Service destroyed by user.", Toast.LENGTH_LONG).show() +// Toast.makeText(this, "Service destroyed by user.", Toast.LENGTH_LONG).show() + } + + inner class VideoStreamBinder : Binder() { + val service: VideoStreamContainerService + get() = this@VideoStreamContainerService + } + + override fun onCallFinished(resultCode: Int, intent: Intent?) { + dialogFragment = null + videoCallResponseListener?.onCallFinished(resultCode, intent) + } + + override fun errorHandle(message: String) { + dialogFragment = null +// Toast.makeText(this, message, Toast.LENGTH_LONG).show() + } + + override fun minimizeVideoEvent(isMinimize: Boolean) { + videoCallResponseListener?.minimizeVideoEvent(isMinimize) } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt index 204568a4..7c3780d7 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt @@ -6,7 +6,7 @@ interface VideoCallResponseListener { fun onCallFinished(resultCode : Int, intent: Intent? = null) - fun errorHandle(message: String) + fun errorHandle(message: String){} fun minimizeVideoEvent(isMinimize : Boolean) diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt index a54a5f78..6b1b1a33 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -15,6 +15,7 @@ import android.util.Log import android.view.* import android.widget.* import androidx.annotation.Nullable +import androidx.appcompat.app.AlertDialog import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.view.GestureDetectorCompat @@ -28,16 +29,18 @@ import com.hmg.hmgDr.ui.VideoCallContract.VideoCallView import com.hmg.hmgDr.ui.VideoCallPresenterImpl import com.hmg.hmgDr.ui.VideoCallResponseListener import com.hmg.hmgDr.util.DynamicVideoRenderer -import com.hmg.hmgDr.util.ThumbnailCircleVideoRenderer import com.opentok.android.* import com.opentok.android.PublisherKit.PublisherListener import pub.devrel.easypermissions.AfterPermissionGranted import pub.devrel.easypermissions.AppSettingsDialog import pub.devrel.easypermissions.EasyPermissions import pub.devrel.easypermissions.EasyPermissions.PermissionCallbacks +import java.text.DecimalFormat +import java.text.NumberFormat import kotlin.math.ceil - +// check this if it works to solve keyboard not work when dialog is opened +// https://stackoverflow.com/questions/55066977/how-to-prevent-custom-dialogfragment-from-hiding-keyboard-when-being-shown class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.SessionListener, PublisherListener, SubscriberKit.VideoListener, VideoCallView { @@ -120,6 +123,11 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT ) + val parentLayoutParam: FrameLayout.LayoutParams = /*parentView.layoutParams as*/ FrameLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + parentView.layoutParams = parentLayoutParam } override fun getTheme(): Int { @@ -127,9 +135,25 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } override fun onCreateDialog(@Nullable savedInstanceState: Bundle?): Dialog { - val dialog: Dialog = super.onCreateDialog(savedInstanceState) + val layoutInflater = activity!!.layoutInflater + this.parentView = onCreateView(layoutInflater, null) + + val alertDialogBuilder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) +// .setTitle(android.R.string.select_a_color) + .setView(this.parentView) +// .setPositiveButton(android.R.string.ok, { dialogInterface, i -> }) + alertDialogBuilder.setOnKeyListener{ _, keyCode, keyEvent -> + // getAction to make sure this doesn't double fire + if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == KeyEvent.ACTION_UP) { + videoCallResponseListener?.onBackHandle() + false // Capture onKey + } else true + // Don't capture + } + return alertDialogBuilder.create() +/* - // Add back button listener + val dialog: Dialog = super.onCreateDialog(savedInstanceState) // Add back button listener dialog.setOnKeyListener { _, keyCode, keyEvent -> // getAction to make sure this doesn't double fire @@ -141,6 +165,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session } return dialog +*/ } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -168,11 +193,14 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session this.videoCallResponseListener = videoCallResponseListener } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { - - parentView = inflater.inflate(R.layout.activity_video_call, container, false) + private fun onCreateView(inflater: LayoutInflater, container: ViewGroup?): View { + val view = inflater.inflate(R.layout.activity_video_call, container, false) +// val parentViewLayoutParam: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams( +// ConstraintLayout.LayoutParams.MATCH_PARENT, +// ConstraintLayout.LayoutParams.MATCH_PARENT +// ) +// parentView.layoutParams = parentViewLayoutParam // Objects.requireNonNull(requireActivity().actionBar)!!.hide() arguments?.run { apiKey = getString("apiKey") @@ -182,15 +210,19 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session baseUrl = getString("baseUrl") sessionStatusModel = getParcelable("sessionStatusModel") } - initUI(parentView) + initUI(view) requestPermissions() handleDragDialog() mDetector = GestureDetectorCompat(context, MyGestureListener({ showControlPanelTemporarily() }, { miniCircleDoubleTap() })) - return parentView + return view } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + return this.parentView + } override fun onPause() { super.onPause() @@ -232,9 +264,11 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session patientName = view.findViewById(R.id.patient_name) patientName.text = sessionStatusModel!!.patientName - cmTimer = view.findViewById(R.id.cmTimer) + cmTimer = view.findViewById(R.id.cmTimer) cmTimer.format = "mm:ss" cmTimer.onChronometerTickListener = Chronometer.OnChronometerTickListener { arg0: Chronometer? -> +// val f: NumberFormat = DecimalFormat("00") +// f.format(minutes) val minutes: Long val seconds: Long if (!resume) { @@ -246,8 +280,10 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session seconds = (elapsedTime - cmTimer.base) / 1000 % 60 elapsedTime += 1000 } - arg0?.text = "$minutes:$seconds" - Log.d(VideoCallFragment.TAG, "onChronometerTick: $minutes : $seconds") + val format = "%1$02d:%2$02d" // two digits + + arg0?.text = String.format(format, minutes, seconds) + Log.d(TAG, "onChronometerTick: $minutes : $seconds") } icMini.setOnClickListener { @@ -284,7 +320,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session // progressBar=findViewById(R.id.progress_bar); // progressBarTextView=findViewById(R.id.progress_bar_text); // progressBar.setVisibility(View.GONE); - hiddenButtons() +// hiddenButtons() checkClientConnected() if (appLang == "ar") { @@ -628,6 +664,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session // val localPreviewIconSmall: Int = context!!.resources.getDimension(R.dimen.local_back_icon_size_small).toInt() // val localPreviewLayoutIconParam : FrameLayout.LayoutParams val localPreviewLayoutParam: RelativeLayout.LayoutParams = mPublisherViewContainer.layoutParams as RelativeLayout.LayoutParams + val miniLayoutParam: ConstraintLayout.LayoutParams = layoutMini.layoutParams as ConstraintLayout.LayoutParams val remotePreviewIconSize: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size).toInt() val remotePreviewIconSizeSmall: Int = context!!.resources.getDimension(R.dimen.remote_back_icon_size_small).toInt() @@ -889,7 +926,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session private class MyGestureListener(val onTabCall: () -> Unit, val miniCircleDoubleTap: () -> Unit) : GestureDetector.SimpleOnGestureListener() { override fun onSingleTapConfirmed(event: MotionEvent): Boolean { - onTabCall() +// onTabCall() return true } diff --git a/android/app/src/main/res/layout/activity_video_call.xml b/android/app/src/main/res/layout/activity_video_call.xml index a54e57b3..b67a5bed 100644 --- a/android/app/src/main/res/layout/activity_video_call.xml +++ b/android/app/src/main/res/layout/activity_video_call.xml @@ -47,38 +47,38 @@ + + + + + - - - - - + app:layout_constraintTop_toBottomOf="@+id/layout_mini"> { child: AppLoaderWidget( containerColor: Colors.transparent, )), + AppButton( + fontWeight: FontWeight.w700, + color:Colors.green[600], + title: TranslationBase.of(context).initiateCall, + disabled: model.state == ViewState.BusyLocal, + onPressed: () async { + AppPermissionsUtils.requestVideoCallPermission(context: context,onTapGrant: (){ + locator().openVideo(model.startCallRes, PatiantInformtion( + vcId: 454353, + fullName: "test mosa" + ), callConnected, callDisconnected); + }); + + }, + ), ], ), ), ); } + + callConnected(){ + + } + + callDisconnected(){ + } }