diff --git a/android/app/build.gradle b/android/app/build.gradle
index e18e8ae5..96428f49 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -70,6 +70,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0e71249f..bf0d3766 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -27,7 +27,6 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="HMG Doctor">
-
("kApiKey")
- val sessionId = call.argument("kSessionId")
- val token = call.argument("kToken")
- val appLang = call.argument("appLang")
- val baseUrl = call.argument("baseUrl")
+ when (call.method) {
+ "openVideoCall" -> {
+ val apiKey = call.argument("kApiKey")
+ val sessionId = call.argument("kSessionId")
+ val token = call.argument("kToken")
+ val appLang = call.argument("appLang")
+ val baseUrl = call.argument("baseUrl")
+
+ // Session Status model
+ val VC_ID = call.argument("VC_ID")
+ val tokenID = call.argument("TokenID")
+ val generalId = call.argument("generalId")
+ val doctorId = call.argument("DoctorId")
+ val patientName = call.argument("patientName")
- // Session Status model
- val VC_ID = call.argument("VC_ID")
- val tokenID = call.argument("TokenID")
- val generalId = call.argument("generalId")
- 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)
- openVideoCall(apiKey, sessionId, token, appLang, baseUrl, sessionStatusModel)
+ }
+ "closeVideoCall" -> {
+ dialogFragment?.onCallClicked()
+ }
+ "onCallConnected" -> {
- } else {
- result.notImplemented()
+ }
+ else -> {
+ result.notImplemented()
+ }
}
}
private fun openVideoCall(apiKey: String?, sessionId: String?, token: String?, appLang: String?, baseUrl: String?, sessionStatusModel: GetSessionStatusModel) {
- // val videoCallActivity = VideoCallActivity()
+ if (dialogFragment == null) {
+ val arguments = Bundle()
+ arguments.putString("apiKey", apiKey)
+ arguments.putString("sessionId", sessionId)
+ arguments.putString("token", token)
+ arguments.putString("appLang", appLang)
+ arguments.putString("baseUrl", baseUrl)
+ arguments.putParcelable("sessionStatusModel", sessionStatusModel)
+
+ 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")
+ }
- val intent = Intent(this, VideoCallActivity::class.java)
- intent.putExtra("apiKey", apiKey)
- intent.putExtra("sessionId", sessionId)
- intent.putExtra("token", token)
- intent.putExtra("appLang", appLang)
- intent.putExtra("baseUrl", baseUrl)
- intent.putExtra("sessionStatusModel", sessionStatusModel)
- startActivityForResult(intent, LAUNCH_VIDEO)
+ }
+ } else if (!dialogFragment!!.isVisible) {
+ val transaction = supportFragmentManager.beginTransaction()
+ dialogFragment!!.show(transaction, "dialog")
+ }
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- var asd = "";
- if (requestCode == LAUNCH_VIDEO) {
- if (resultCode == Activity.RESULT_OK) {
- val result : SessionStatusModel? = data?.getParcelableExtra("sessionStatusNotRespond")
- val callResponse : HashMap = HashMap()
+ /* override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ var asd = "";
+ if (requestCode == LAUNCH_VIDEO) {
+ if (resultCode == Activity.RESULT_OK) {
+ val result : SessionStatusModel? = data?.getParcelableExtra("sessionStatusNotRespond")
+ val callResponse : HashMap = HashMap()
+
+ val sessionStatus : HashMap = HashMap()
+ val gson = GsonBuilder().serializeNulls().create()
- val sessionStatus : HashMap = HashMap()
- val gson = GsonBuilder().serializeNulls().create()
+ callResponse["callResponse"] = "CallNotRespond"
+ val jsonRes = gson.toJson(result)
+ callResponse["sessionStatus"] = jsonRes
- callResponse["callResponse"] = "CallNotRespond"
- val jsonRes = gson.toJson(result)
- callResponse["sessionStatus"] = jsonRes
+ this.result?.success(callResponse)
+ }
+ if (resultCode == Activity.RESULT_CANCELED) {
+ val callResponse : HashMap = HashMap()
+ callResponse["callResponse"] = "CallEnd"
+ result?.success(callResponse)
+ }
+ }
+ }*/
+
+ override fun onCallFinished(resultCode: Int, intent: Intent?) {
+ dialogFragment = null
+
+ if (resultCode == Activity.RESULT_OK) {
+ val result: SessionStatusModel? = intent?.getParcelableExtra("sessionStatusNotRespond")
+ val callResponse: HashMap = HashMap()
+
+ val sessionStatus: HashMap = HashMap()
+ val gson = GsonBuilder().serializeNulls().create()
+
+ callResponse["callResponse"] = "CallNotRespond"
+ val jsonRes = gson.toJson(result)
+ callResponse["sessionStatus"] = jsonRes
+
+ try {
this.result?.success(callResponse)
+ } catch (e : Exception){
+ Log.e("onVideoCallFinished", "${e.message}.")
}
- if (resultCode == Activity.RESULT_CANCELED) {
- val callResponse : HashMap = HashMap()
- callResponse["callResponse"] = "CallEnd"
-
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+ val callResponse: HashMap = HashMap()
+ callResponse["callResponse"] = "CallEnd"
+ try {
result?.success(callResponse)
+ } catch (e : Exception){
+ Log.e("onVideoCallFinished", "${e.message}.")
}
}
}
+ override fun errorHandle(message: String) {
+ dialogFragment = null
+// Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+ }
+
+ override fun minimizeVideoEvent(isMinimize: Boolean) {
+ if (isMinimize)
+ methodChannel.invokeMethod("onCallConnected", null)
+ else
+ methodChannel.invokeMethod("onCallDisconnected", null)
+ }
+
+ override fun onBackPressed() {
+ super.onBackPressed()
+ }
}
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java
deleted file mode 100644
index be4ebfb8..00000000
--- a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallActivity.java
+++ /dev/null
@@ -1,482 +0,0 @@
-package com.hmg.hmgDr.ui;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatActivity;
-
-import android.Manifest;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.Intent;
-import android.opengl.GLSurfaceView;
-import android.os.Bundle;
-import android.os.CountDownTimer;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.View;
-import android.widget.Chronometer;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel;
-import com.hmg.hmgDr.Model.GetSessionStatusModel;
-import com.hmg.hmgDr.Model.SessionStatusModel;
-import com.hmg.hmgDr.R;
-import com.opentok.android.Session;
-import com.opentok.android.Stream;
-import com.opentok.android.Publisher;
-import com.opentok.android.PublisherKit;
-import com.opentok.android.Subscriber;
-import com.opentok.android.BaseVideoRenderer;
-import com.opentok.android.OpentokError;
-import com.opentok.android.SubscriberKit;
-
-import java.util.List;
-import java.util.Objects;
-
-import pub.devrel.easypermissions.AfterPermissionGranted;
-import pub.devrel.easypermissions.AppSettingsDialog;
-import pub.devrel.easypermissions.EasyPermissions;
-
-public class VideoCallActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks,
- Session.SessionListener,
- Publisher.PublisherListener,
- Subscriber.VideoListener, VideoCallContract.VideoCallView {
-
- private static final String TAG = VideoCallActivity.class.getSimpleName();
-
- VideoCallContract.VideoCallPresenter videoCallPresenter;
-
- private static final int RC_SETTINGS_SCREEN_PERM = 123;
- private static final int RC_VIDEO_APP_PERM = 124;
-
-
- private Session mSession;
- private Publisher mPublisher;
- private Subscriber mSubscriber;
-
- private Handler mVolHandler, mConnectedHandler;
- private Runnable mVolRunnable, mConnectedRunnable;
-
- private FrameLayout mPublisherViewContainer;
- private RelativeLayout mSubscriberViewContainer;
- private RelativeLayout controlPanel;
-
- private String apiKey;
- private String sessionId;
- private String token;
- private String appLang;
- private String baseUrl;
-
- private boolean isSwitchCameraClicked;
- private boolean isCameraClicked;
- private boolean isSpeckerClicked;
- private boolean isMicClicked;
-
- private TextView patientName;
- private Chronometer cmTimer;
- long elapsedTime;
- Boolean resume = false;
-
- private ImageView mCallBtn;
- private ImageView btnMinimize;
- private ImageView mCameraBtn;
- private ImageView mSwitchCameraBtn;
- private ImageView mspeckerBtn;
- private ImageView mMicBtn;
-
- private ProgressBar progressBar;
- private CountDownTimer countDownTimer;
- private TextView progressBarTextView;
- private RelativeLayout progressBarLayout;
-
- private boolean isConnected = false;
-
- private GetSessionStatusModel sessionStatusModel;
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(R.style.AppTheme);
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_video_call);
- Objects.requireNonNull(getSupportActionBar()).hide();
- initUI();
- requestPermissions();
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- if (mSession == null) {
- return;
- }
- mSession.onPause();
-
- if (isFinishing()) {
- disconnectSession();
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- if (mSession == null) {
- return;
- }
- mSession.onResume();
- }
-
- @Override
- protected void onDestroy() {
- disconnectSession();
- cmTimer.stop();
- super.onDestroy();
- }
-
- @SuppressLint("ClickableViewAccessibility")
- private void initUI() {
- mPublisherViewContainer = findViewById(R.id.local_video_view_container);
- mSubscriberViewContainer = findViewById(R.id.remote_video_view_container);
-
- apiKey = getIntent().getStringExtra("apiKey");
- sessionId = getIntent().getStringExtra("sessionId");
- token = getIntent().getStringExtra("token");
- appLang = getIntent().getStringExtra("appLang");
- baseUrl = getIntent().getStringExtra("baseUrl");
- sessionStatusModel = getIntent().getParcelableExtra("sessionStatusModel");
-
- controlPanel = findViewById(R.id.control_panel);
-
- videoCallPresenter = new VideoCallPresenterImpl(this, baseUrl);
-
- patientName = findViewById(R.id.patient_name);
- patientName.setText(sessionStatusModel.getPatientName());
-
- cmTimer = findViewById(R.id.cmTimer);
- cmTimer.setFormat("mm:ss");
- cmTimer.setOnChronometerTickListener(arg0 -> {
- long minutes;
- long seconds;
- if (!resume) {
- minutes = ((SystemClock.elapsedRealtime() - cmTimer.getBase()) / 1000) / 60;
- seconds = ((SystemClock.elapsedRealtime() - cmTimer.getBase()) / 1000) % 60;
- elapsedTime = SystemClock.elapsedRealtime();
- } else {
- minutes = ((elapsedTime - cmTimer.getBase()) / 1000) / 60;
- seconds = ((elapsedTime - cmTimer.getBase()) / 1000) % 60;
- elapsedTime = elapsedTime + 1000;
- }
- Log.d(TAG, "onChronometerTick: " + minutes + " : " + seconds);
- });
-
- mCallBtn = findViewById(R.id.btn_call);
- btnMinimize = findViewById(R.id.btn_minimize);
- mCameraBtn = findViewById(R.id.btn_camera);
- mSwitchCameraBtn = findViewById(R.id.btn_switch_camera);
- mspeckerBtn = findViewById(R.id.btn_specker);
- mMicBtn = findViewById(R.id.btn_mic);
-
- // progressBarLayout=findViewById(R.id.progressBar);
- // progressBar=findViewById(R.id.progress_bar);
-// progressBarTextView=findViewById(R.id.progress_bar_text);
-// progressBar.setVisibility(View.GONE);
-
- hiddenButtons();
-
- checkClientConnected();
-
- mSubscriberViewContainer.setOnTouchListener((v, event) -> {
- controlPanel.setVisibility(View.VISIBLE);
- mVolHandler.removeCallbacks(mVolRunnable);
- mVolHandler.postDelayed(mVolRunnable, 5 * 1000);
- return true;
- });
-
- if (appLang.equals("ar")) {
- progressBarLayout.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
- }
-
- }
-
- private void checkClientConnected() {
- mConnectedHandler = new Handler();
- mConnectedRunnable = () -> {
- if (!isConnected) {
- videoCallPresenter.callClintConnected(sessionStatusModel);
- }
- };
- mConnectedHandler.postDelayed(mConnectedRunnable, 55 * 1000);
-
- }
-
- private void hiddenButtons() {
- mVolHandler = new Handler();
- mVolRunnable = () -> controlPanel.setVisibility(View.GONE);
- mVolHandler.postDelayed(mVolRunnable, 5 * 1000);
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-
- EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
- }
-
- @Override
- public void onPermissionsGranted(int requestCode, List perms) {
- Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
- }
-
- @Override
- public void onPermissionsDenied(int requestCode, List perms) {
- Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());
-
- if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
- new AppSettingsDialog.Builder(this)
- .setTitle(getString(R.string.title_settings_dialog))
- .setRationale(getString(R.string.rationale_ask_again))
- .setPositiveButton(getString(R.string.setting))
- .setNegativeButton(getString(R.string.cancel))
- .setRequestCode(RC_SETTINGS_SCREEN_PERM)
- .build()
- .show();
- }
- }
-
- @AfterPermissionGranted(RC_VIDEO_APP_PERM)
- private void requestPermissions() {
- String[] perms = {Manifest.permission.INTERNET, Manifest.permission.CAMERA,};
- if (EasyPermissions.hasPermissions(this, perms)) {
- try {
- mSession = new Session.Builder(this, apiKey, sessionId).build();
- mSession.setSessionListener(this);
- mSession.connect(token);
- } catch (Exception e) {
- e.printStackTrace();
- }
- } else {
- EasyPermissions.requestPermissions(this, getString(R.string.remaining_ar), RC_VIDEO_APP_PERM, perms);
- }
- }
-
- @Override
- public void onConnected(Session session) {
- Log.i(TAG, "Session Connected");
-
- mPublisher = new Publisher.Builder(this).build();
- mPublisher.setPublisherListener(this);
-
- mPublisherViewContainer.addView(mPublisher.getView());
-
- if (mPublisher.getView() instanceof GLSurfaceView) {
- ((GLSurfaceView) mPublisher.getView()).setZOrderOnTop(true);
- }
-
- mSession.publish(mPublisher);
-
- if (!resume) {
- cmTimer.setBase(SystemClock.elapsedRealtime());
- }
- cmTimer.start();
- }
-
- @Override
- public void onDisconnected(Session session) {
- Log.d(TAG, "onDisconnected: disconnected from session " + session.getSessionId());
-
- mSession = null;
- cmTimer.stop();
- }
-
- @Override
- public void onError(Session session, OpentokError opentokError) {
- Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in session " + session.getSessionId());
-
- // Toast.makeText(this, "Session error. See the logcat please.", Toast.LENGTH_LONG).show();
- //finish();
- }
-
- @Override
- public void onStreamReceived(Session session, Stream stream) {
- Log.d(TAG, "onStreamReceived: New stream " + stream.getStreamId() + " in session " + session.getSessionId());
- if (mSubscriber != null) {
- isConnected = true;
- return;
- }
- isConnected = true;
- subscribeToStream(stream);
- if(mConnectedHandler!=null && mConnectedRunnable!=null)
- mConnectedHandler.removeCallbacks(mConnectedRunnable);
- videoCallPresenter.callChangeCallStatus(new ChangeCallStatusRequestModel(3,sessionStatusModel.getDoctorId(), sessionStatusModel.getGeneralid(),token,sessionStatusModel.getVCID()));
- }
-
- @Override
- public void onStreamDropped(Session session, Stream stream) {
- Log.d(TAG, "onStreamDropped: Stream " + stream.getStreamId() + " dropped from session " + session.getSessionId());
-
- if (mSubscriber == null) {
- return;
- }
-
- if (mSubscriber.getStream().equals(stream)) {
- mSubscriberViewContainer.removeView(mSubscriber.getView());
- mSubscriber.destroy();
- mSubscriber = null;
- }
- disconnectSession();
- }
-
- @Override
- public void onStreamCreated(PublisherKit publisherKit, Stream stream) {
- Log.d(TAG, "onStreamCreated: Own stream " + stream.getStreamId() + " created");
- }
-
- @Override
- public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) {
- Log.d(TAG, "onStreamDestroyed: Own stream " + stream.getStreamId() + " destroyed");
- }
-
- @Override
- public void onError(PublisherKit publisherKit, OpentokError opentokError) {
- Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in publisher");
-
- // Toast.makeText(this, "onError: Error (" + opentokError.getMessage() + ") in publisher", Toast.LENGTH_LONG).show();
- // finish();
- }
-
- @Override
- public void onVideoDataReceived(SubscriberKit subscriberKit) {
- mSubscriber.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL);
- mSubscriberViewContainer.addView(mSubscriber.getView());
- }
-
- @Override
- public void onVideoDisabled(SubscriberKit subscriberKit, String s) {
-
- }
-
- @Override
- public void onVideoEnabled(SubscriberKit subscriberKit, String s) {
-
- }
-
- @Override
- public void onVideoDisableWarning(SubscriberKit subscriberKit) {
-
- }
-
- @Override
- public void onVideoDisableWarningLifted(SubscriberKit subscriberKit) {
-
- }
-
- private void subscribeToStream(Stream stream) {
- mSubscriber = new Subscriber.Builder(VideoCallActivity.this, stream).build();
- mSubscriber.setVideoListener(this);
- mSession.subscribe(mSubscriber);
- }
-
- private void disconnectSession() {
- if (mSession == null) {
- setResult(Activity.RESULT_CANCELED);
- finish();
- return;
- }
-
- if (mSubscriber != null) {
- mSubscriberViewContainer.removeView(mSubscriber.getView());
- mSession.unsubscribe(mSubscriber);
- mSubscriber.destroy();
- mSubscriber = null;
- }
-
- if (mPublisher != null) {
- mPublisherViewContainer.removeView(mPublisher.getView());
- mSession.unpublish(mPublisher);
- mPublisher.destroy();
- mPublisher = null;
- }
- mSession.disconnect();
- if (countDownTimer != null) {
- countDownTimer.cancel();
- }
- videoCallPresenter.callChangeCallStatus(new ChangeCallStatusRequestModel(16,sessionStatusModel.getDoctorId(), sessionStatusModel.getGeneralid(),token,sessionStatusModel.getVCID()));
- finish();
- }
-
- public void onMinimizedClicked(View view) {
-
- }
-
- public void onSwitchCameraClicked(View view) {
- if (mPublisher != null) {
- isSwitchCameraClicked = !isSwitchCameraClicked;
- mPublisher.cycleCamera();
- int res = isSwitchCameraClicked ? R.drawable.flip_disapled : R.drawable.flip_enabled;
- mSwitchCameraBtn.setImageResource(res);
- }
- }
-
- public void onCameraClicked(View view) {
- if (mPublisher != null) {
- isCameraClicked = !isCameraClicked;
- mPublisher.setPublishVideo(!isCameraClicked);
- int res = isCameraClicked ? R.drawable.video_disanabled : R.drawable.video_enabled;
- mCameraBtn.setImageResource(res);
- }
- }
-
- public void onSpeckerClicked(View view) {
- if (mSubscriber != null) {
- isSpeckerClicked = !isSpeckerClicked;
- mSubscriber.setSubscribeToAudio(!isSpeckerClicked);
- int res = isSpeckerClicked ? R.drawable.audio_disabled : R.drawable.audio_enabled;
- mspeckerBtn.setImageResource(res);
- }
- }
-
- public void onMicClicked(View view) {
-
- if (mPublisher != null) {
- isMicClicked = !isMicClicked;
- mPublisher.setPublishAudio(!isMicClicked);
- int res = isMicClicked ? R.drawable.mic_disabled : R.drawable.mic_enabled;
- mMicBtn.setImageResource(res);
- }
- }
-
- public void onCallClicked(View view) {
- disconnectSession();
- }
-
- @Override
- public void onCallSuccessful(SessionStatusModel sessionStatusModel) {
- if (sessionStatusModel.getSessionStatus() == 2 || sessionStatusModel.getSessionStatus() == 3) {
- Intent returnIntent = new Intent();
- returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel);
- setResult(Activity.RESULT_OK, returnIntent);
- finish();
- } else if( sessionStatusModel.getSessionStatus() == 4 ){
- isConnected = true;
- if(mConnectedHandler!=null && mConnectedRunnable!=null)
- mConnectedHandler.removeCallbacks(mConnectedRunnable);
- }
- }
-
- @Override
- public void onCallChangeCallStatusSuccessful(SessionStatusModel sessionStatusModel) {
-
- }
-
- @Override
- public void onFailure() {
-
- }
-}
\ 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
new file mode 100644
index 00000000..204568a4
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/VideoCallResponseListener.kt
@@ -0,0 +1,14 @@
+package com.hmg.hmgDr.ui
+
+import android.content.Intent
+
+interface VideoCallResponseListener {
+
+ fun onCallFinished(resultCode : Int, intent: Intent? = null)
+
+ fun errorHandle(message: String)
+
+ fun minimizeVideoEvent(isMinimize : Boolean)
+
+ fun onBackHandle(){}
+}
\ No newline at end of file
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
new file mode 100644
index 00000000..35e28b23
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt
@@ -0,0 +1,893 @@
+package com.hmg.hmgDr.ui.fragment
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.Dialog
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.Point
+import android.graphics.drawable.ColorDrawable
+import android.opengl.GLSurfaceView
+import android.os.*
+import android.util.Log
+import android.view.*
+import android.widget.*
+import androidx.annotation.Nullable
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
+import androidx.core.view.GestureDetectorCompat
+import androidx.fragment.app.DialogFragment
+import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel
+import com.hmg.hmgDr.Model.GetSessionStatusModel
+import com.hmg.hmgDr.Model.SessionStatusModel
+import com.hmg.hmgDr.R
+import com.hmg.hmgDr.ui.VideoCallContract.VideoCallPresenter
+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 kotlin.math.ceil
+
+
+class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.SessionListener, PublisherListener,
+ SubscriberKit.VideoListener, VideoCallView {
+
+ private var isFullScreen: Boolean = true
+ private var isCircle: Boolean = false
+ private var x_init_cord = 0
+ private var y_init_cord: Int = 0
+ private var x_init_margin: Int = 0
+ private var y_init_margin: Int = 0
+ private val szWindow: Point = Point()
+ private lateinit var mWindowManager: WindowManager
+ private var isLeft = true
+
+ private lateinit var videoCallPresenter: VideoCallPresenter
+
+ private var mSession: Session? = null
+ private var mPublisher: Publisher? = null
+ private var mSubscriber: Subscriber? = null
+
+ private var mVolHandler: Handler? = null
+ private var mConnectedHandler: Handler? = null
+ private var mVolRunnable: Runnable? = null
+ private var mConnectedRunnable: Runnable? = null
+
+ private lateinit var thumbnail_container: FrameLayout
+ private lateinit var mPublisherViewContainer: FrameLayout
+ private lateinit var mPublisherViewIcon: View
+ private lateinit var mSubscriberViewContainer: FrameLayout
+ private lateinit var mSubscriberViewIcon: ImageView
+ private lateinit var controlPanel: ConstraintLayout
+
+ private var apiKey: String? = null
+ private var sessionId: String? = null
+ private var token: String? = null
+ private var appLang: String? = null
+ private var baseUrl: String? = null
+
+ private var isSwitchCameraClicked = false
+ private var isCameraClicked = false
+ private var isSpeckerClicked = false
+ private var isMicClicked = false
+
+ private lateinit var parentView: View
+ private lateinit var videoCallContainer: ConstraintLayout
+ private lateinit var layoutName: RelativeLayout
+ private lateinit var layoutMini: RelativeLayout
+ private lateinit var icMini: ImageButton
+ private lateinit var mCallBtn: ImageView
+ private lateinit var btnMinimize: ImageView
+ private lateinit var mCameraBtn: ImageView
+ private lateinit var mSwitchCameraBtn: ImageView
+ private lateinit var mspeckerBtn: ImageView
+ private lateinit var mMicBtn: ImageView
+
+ private lateinit var patientName: TextView
+ private lateinit var cmTimer: Chronometer
+ private var elapsedTime: Long = 0
+ private var resume = false
+
+ private val progressBar: ProgressBar? = null
+ private val countDownTimer: CountDownTimer? = null
+ private val progressBarTextView: TextView? = null
+ private val progressBarLayout: RelativeLayout? = null
+
+ private var isConnected = false
+
+ private var sessionStatusModel: GetSessionStatusModel? = null
+ private var videoCallResponseListener: VideoCallResponseListener? = null
+ private lateinit var mDetector: GestureDetectorCompat
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ requireActivity().setTheme(R.style.AppTheme)
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onStart() {
+ super.onStart()
+
+ dialog?.window?.setLayout(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ )
+ }
+
+ override fun getTheme(): Int {
+ return R.style.dialogTheme
+ }
+
+ override fun onCreateDialog(@Nullable savedInstanceState: Bundle?): Dialog {
+ val dialog: Dialog = super.onCreateDialog(savedInstanceState)
+
+ // Add back button listener
+ // Add back button listener
+ dialog.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 dialog
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ // This is done in a post() since the dialog must be drawn before locating.
+ requireView().post {
+ val dialogWindow = dialog!!.window
+
+ if (dialog != null && dialogWindow != null) {
+ dialogWindow.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT));
+ }
+
+ // Make the dialog possible to be outside touch
+ dialogWindow!!.setFlags(
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ )
+ dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ requireView().invalidate()
+ }
+ }
+
+ fun setCallListener(videoCallResponseListener: VideoCallResponseListener) {
+ this.videoCallResponseListener = videoCallResponseListener
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?): View {
+
+ parentView = inflater.inflate(R.layout.activity_video_call, container, false)
+
+ // Objects.requireNonNull(requireActivity().actionBar)!!.hide()
+ arguments?.run {
+ apiKey = getString("apiKey")
+ sessionId = getString("sessionId")
+ token = getString("token")
+ appLang = getString("appLang")
+ baseUrl = getString("baseUrl")
+ sessionStatusModel = getParcelable("sessionStatusModel")
+ }
+ initUI(parentView)
+ requestPermissions()
+
+ handleDragDialog()
+ mDetector = GestureDetectorCompat(context, MyGestureListener({ showControlPanelTemporarily() }, { miniCircleDoubleTap() }))
+
+ return parentView
+ }
+
+
+ override fun onPause() {
+ super.onPause()
+ if (mSession == null) {
+ return
+ }
+ mSession!!.onPause()
+ if (requireActivity().isFinishing) {
+ disconnectSession()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (mSession == null) {
+ return
+ }
+ mSession!!.onResume()
+ }
+
+ override fun onDestroy() {
+ disconnectSession()
+ cmTimer.stop()
+ super.onDestroy()
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun initUI(view: View) {
+ videoCallContainer = view.findViewById(R.id.video_call_ll)
+ layoutName = view.findViewById(R.id.layout_name)
+ layoutMini = view.findViewById(R.id.layout_mini)
+ icMini = view.findViewById(R.id.ic_mini)
+ thumbnail_container = view.findViewById(R.id.thumbnail_container)
+ mPublisherViewContainer = view.findViewById(R.id.local_video_view_container)
+ mPublisherViewIcon = view.findViewById(R.id.local_video_view_icon)
+ mSubscriberViewIcon = view.findViewById(R.id.remote_video_view_icon)
+ mSubscriberViewContainer = view.findViewById(R.id.remote_video_view_container)
+
+ patientName = view.findViewById(R.id.patient_name)
+ patientName.text = sessionStatusModel!!.patientName
+
+ cmTimer = view.findViewById(R.id.cmTimer)
+ cmTimer.format = "mm:ss"
+ cmTimer.onChronometerTickListener = Chronometer.OnChronometerTickListener { arg0: Chronometer? ->
+ val minutes: Long
+ val seconds: Long
+ if (!resume) {
+ minutes = (SystemClock.elapsedRealtime() - cmTimer.base) / 1000 / 60
+ seconds = (SystemClock.elapsedRealtime() - cmTimer.base) / 1000 % 60
+ elapsedTime = SystemClock.elapsedRealtime()
+ } else {
+ minutes = (elapsedTime - cmTimer.base) / 1000 / 60
+ seconds = (elapsedTime - cmTimer.base) / 1000 % 60
+ elapsedTime += 1000
+ }
+ arg0?.text = "$minutes:$seconds"
+ Log.d(VideoCallFragment.TAG, "onChronometerTick: $minutes : $seconds")
+ }
+
+ icMini.setOnClickListener {
+ onMiniCircleClicked()
+ }
+
+ controlPanel = view.findViewById(R.id.control_panel)
+ videoCallPresenter = VideoCallPresenterImpl(this, baseUrl)
+ mCallBtn = view.findViewById(R.id.btn_call)
+ mCallBtn.setOnClickListener {
+ onCallClicked()
+ }
+ btnMinimize = view.findViewById(R.id.btn_minimize)
+ btnMinimize.setOnClickListener {
+ onMinimizedClicked(it)
+ }
+ mCameraBtn = view.findViewById(R.id.btn_camera)
+ mCameraBtn.setOnClickListener {
+ onCameraClicked(it)
+ }
+ mSwitchCameraBtn = view.findViewById(R.id.btn_switch_camera)
+ mSwitchCameraBtn.setOnClickListener {
+ onSwitchCameraClicked(it)
+ }
+ mspeckerBtn = view.findViewById(R.id.btn_specker)
+ mspeckerBtn.setOnClickListener {
+ onSpeckerClicked(it)
+ }
+ mMicBtn = view.findViewById(R.id.btn_mic)
+ mMicBtn.setOnClickListener {
+ onMicClicked(it)
+ }
+ // progressBarLayout=findViewById(R.id.progressBar);
+ // progressBar=findViewById(R.id.progress_bar);
+// progressBarTextView=findViewById(R.id.progress_bar_text);
+// progressBar.setVisibility(View.GONE);
+ hiddenButtons()
+ checkClientConnected()
+
+ if (appLang == "ar") {
+ progressBarLayout!!.layoutDirection = View.LAYOUT_DIRECTION_RTL
+ }
+ }
+
+ private fun checkClientConnected() {
+ mConnectedHandler = Handler((Looper.getMainLooper()))
+ mConnectedRunnable = Runnable {
+ if (!isConnected) {
+ videoCallPresenter.callClintConnected(sessionStatusModel)
+ }
+ }
+ mConnectedHandler!!.postDelayed(mConnectedRunnable!!, (55 * 1000).toLong())
+ }
+
+ private fun hiddenButtons() {
+ mVolHandler = Handler()
+ mVolRunnable = Runnable { controlPanel.visibility = View.GONE }
+ mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong())
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
+ }
+
+ override fun onPermissionsGranted(requestCode: Int, perms: List) {
+ Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size)
+ }
+
+ override fun onPermissionsDenied(requestCode: Int, perms: List) {
+ Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size)
+ if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
+ AppSettingsDialog.Builder(this)
+ .setTitle(getString(R.string.title_settings_dialog))
+ .setRationale(getString(R.string.rationale_ask_again))
+ .setPositiveButton(getString(R.string.setting))
+ .setNegativeButton(getString(R.string.cancel))
+ .setRequestCode(RC_SETTINGS_SCREEN_PERM)
+ .build()
+ .show()
+ }
+ }
+
+ @AfterPermissionGranted(RC_VIDEO_APP_PERM)
+ private fun requestPermissions() {
+ val perms = arrayOf(Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.CALL_PHONE)
+ if (EasyPermissions.hasPermissions(requireContext(), *perms)) {
+ try {
+ mSession = Session.Builder(context, apiKey, sessionId).build()
+ mSession!!.setSessionListener(this)
+ mSession!!.connect(token)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ } else {
+ EasyPermissions.requestPermissions(this, getString(R.string.remaining_ar), RC_VIDEO_APP_PERM, *perms)
+ }
+ }
+
+ override fun onConnected(session: Session?) {
+ Log.i(TAG, "Session Connected")
+ mPublisher = Publisher.Builder(requireContext())
+// .name("publisher")
+// .renderer(ThumbnailCircleVideoRenderer(requireContext()))
+ .build()
+ mPublisher!!.setPublisherListener(this)
+ if (mPublisher!!.view is GLSurfaceView) {
+ (mPublisher!!.view as GLSurfaceView).setZOrderOnTop(true)
+ }
+
+ mPublisherViewContainer.addView(mPublisher!!.view)
+ mSession!!.publish(mPublisher)
+
+ if (!resume) {
+ cmTimer.base = SystemClock.elapsedRealtime()
+ }
+ cmTimer.start()
+ videoCallResponseListener?.minimizeVideoEvent(true)
+ }
+
+ override fun onDisconnected(session: Session) {
+ Log.d(TAG, "onDisconnected: disconnected from session " + session.sessionId)
+ mSession = null
+ cmTimer.stop()
+ disconnectSession()
+ videoCallResponseListener?.minimizeVideoEvent(false)
+ }
+
+ override fun onError(session: Session, opentokError: OpentokError) {
+ Log.d(TAG, "onError: Error (" + opentokError.message + ") in session " + session.sessionId)
+
+ // videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in session ")
+// dialog?.dismiss()
+ }
+
+ override fun onStreamReceived(session: Session, stream: Stream) {
+ Log.d(TAG, "onStreamReceived: New stream " + stream.streamId + " in session " + session.sessionId)
+ if (mSubscriber != null) {
+ isConnected = true
+ return
+ }
+ isConnected = true
+ subscribeToStream(stream)
+ if (mConnectedHandler != null && mConnectedRunnable != null)
+ mConnectedHandler!!.removeCallbacks(mConnectedRunnable!!)
+ videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(3, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid))
+ }
+
+ override fun onStreamDropped(session: Session, stream: Stream) {
+ Log.d(TAG, "onStreamDropped: Stream " + stream.streamId + " dropped from session " + session.sessionId)
+ if (mSubscriber == null) {
+ return
+ }
+ if (mSubscriber!!.stream == stream) {
+ mSubscriberViewContainer.removeView(mSubscriber!!.view)
+ mSubscriber!!.destroy()
+ mSubscriber = null
+ }
+ disconnectSession()
+ }
+
+ override fun onStreamCreated(publisherKit: PublisherKit?, stream: Stream) {
+ Log.d(TAG, "onStreamCreated: Own stream " + stream.streamId + " created")
+ }
+
+ override fun onStreamDestroyed(publisherKit: PublisherKit?, stream: Stream) {
+ Log.d(TAG, "onStreamDestroyed: Own stream " + stream.streamId + " destroyed")
+ }
+
+ override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError) {
+ Log.d(VideoCallFragment.TAG, "onError: Error (" + opentokError.message + ") in publisher")
+ // videoCallResponseListener?.errorHandle("Error (" + opentokError.message + ") in publisher")
+// dialog?.dismiss()
+ }
+
+ override fun onVideoDataReceived(subscriberKit: SubscriberKit?) {
+ mSubscriber!!.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL)
+ (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(false)
+ mSubscriberViewContainer.addView(mSubscriber!!.view)
+// switchToThumbnailCircle()
+ }
+
+ fun switchToThumbnailCircle() {
+ thumbnail_container.postDelayed({
+ val view = mSubscriber!!.view
+ if (view.parent != null) {
+ (view.parent as ViewGroup).removeView(view)
+ }
+ if (view is GLSurfaceView) {
+ view.setZOrderOnTop(true)
+ if (mSubscriber!!.renderer is DynamicVideoRenderer) {
+ (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(true)
+ thumbnail_container.addView(view)
+ }
+ }
+ switchToFullScreenView()
+ }, 4000)
+ }
+
+ fun switchToFullScreenView() {
+ mSubscriberViewContainer.postDelayed({
+ val view = mSubscriber!!.view
+ if (view.parent != null) {
+ (view.parent as ViewGroup).removeView(view)
+ }
+ if (view is GLSurfaceView) {
+ view.setZOrderOnTop(false)
+ if (mSubscriber!!.renderer is DynamicVideoRenderer) {
+ (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(false)
+ mSubscriberViewContainer.addView(view)
+ }
+ }
+ switchToThumbnailCircle()
+ }, 4000)
+ }
+
+ override fun onVideoDisabled(subscriberKit: SubscriberKit?, s: String?) {}
+
+ override fun onVideoEnabled(subscriberKit: SubscriberKit?, s: String?) {}
+
+ override fun onVideoDisableWarning(subscriberKit: SubscriberKit?) {}
+
+ override fun onVideoDisableWarningLifted(subscriberKit: SubscriberKit?) {}
+
+ private fun subscribeToStream(stream: Stream) {
+ mSubscriber = Subscriber.Builder(requireContext(), stream)
+ .renderer(DynamicVideoRenderer(requireContext()))
+ .build()
+ mSubscriber!!.setVideoListener(this)
+ mSession!!.subscribe(mSubscriber)
+ }
+
+ private fun disconnectSession() {
+ if (mSession == null) {
+ videoCallResponseListener?.onCallFinished(Activity.RESULT_CANCELED)
+// requireActivity().setResult(Activity.RESULT_CANCELED)
+ dialog?.dismiss()
+ return
+ }
+
+ if (mSubscriber != null) {
+ mSubscriberViewContainer.removeView(mSubscriber!!.view)
+ mSession!!.unsubscribe(mSubscriber)
+ mSubscriber!!.destroy()
+ mSubscriber = null
+ }
+ if (mPublisher != null) {
+ mPublisherViewContainer.removeView(mPublisher!!.view)
+ mSession!!.unpublish(mPublisher)
+ mPublisher!!.destroy()
+ mPublisher = null
+ }
+ mSession!!.disconnect()
+ countDownTimer?.cancel()
+
+ videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(16, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid))
+ dialog?.dismiss()
+ }
+
+ override fun onCallSuccessful(sessionStatusModel: SessionStatusModel) {
+ if (sessionStatusModel.sessionStatus == 2 || sessionStatusModel.sessionStatus == 3) {
+ val returnIntent = Intent()
+ returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel)
+ videoCallResponseListener?.onCallFinished(Activity.RESULT_OK, returnIntent)
+// requireActivity().setResult(Activity.RESULT_OK, returnIntent)
+ dialog?.dismiss()
+ }
+ }
+
+ override fun onCallChangeCallStatusSuccessful(sessionStatusModel: SessionStatusModel?) {}
+
+ override fun onFailure() {}
+
+ private fun onSwitchCameraClicked(view: View?) {
+ if (mPublisher != null) {
+ isSwitchCameraClicked = !isSwitchCameraClicked
+ mPublisher!!.cycleCamera()
+ val res = if (isSwitchCameraClicked) R.drawable.camera_front else R.drawable.camera_back
+ mSwitchCameraBtn.setImageResource(res)
+ }
+ }
+
+ fun onCallClicked() {
+ disconnectSession()
+ }
+
+ private fun miniCircleDoubleTap() {
+ if (isCircle) {
+ onMiniCircleClicked()
+ }
+ }
+
+ private fun onMiniCircleClicked() {
+ if (isCircle) {
+ dialog?.window?.setLayout(
+ 400,
+ 600
+ )
+ } else {
+ dialog?.window?.setLayout(
+ 300,
+ 300
+ )
+ }
+ isCircle = !isCircle
+
+ if (mSubscriber != null) {
+ (mSubscriber!!.renderer as DynamicVideoRenderer).enableThumbnailCircle(isCircle)
+ } else {
+ if (isCircle) {
+ videoCallContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape)
+ mSubscriberViewContainer.background = ContextCompat.getDrawable(requireContext(), R.drawable.circle_shape)
+ } else {
+ videoCallContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color))
+ mSubscriberViewContainer.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.text_color))
+ }
+
+ }
+
+ if (isCircle) {
+ controlPanel.visibility = View.GONE
+ layoutMini.visibility = View.GONE
+ } else {
+ controlPanel.visibility = View.VISIBLE
+ layoutMini.visibility = View.VISIBLE
+ }
+ }
+
+ private fun onMinimizedClicked(view: View?) {
+ if (isFullScreen) {
+ dialog?.window?.setLayout(
+ 400,
+ 600
+ )
+ } else {
+ dialog?.window?.setLayout(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ )
+ }
+ isFullScreen = !isFullScreen
+
+ val res = if (isFullScreen) R.drawable.reducing else R.drawable.expand
+ btnMinimize.setImageResource(res)
+ setViewsVisibility()
+
+// videoCallResponseListener?.minimizeVideoEvent(!isFullScreen)
+ }
+
+ private fun setViewsVisibility() {
+ val iconSize: Int = context!!.resources.getDimension(R.dimen.video_icon_size).toInt()
+ val iconSizeSmall: Int = context!!.resources.getDimension(R.dimen.video_icon_size_small).toInt()
+ val btnMinimizeLayoutParam: ConstraintLayout.LayoutParams = btnMinimize.layoutParams as ConstraintLayout.LayoutParams
+ val mCallBtnLayoutParam: ConstraintLayout.LayoutParams = mCallBtn.layoutParams as ConstraintLayout.LayoutParams
+
+ val localPreviewMargin: Int = context!!.resources.getDimension(R.dimen.local_preview_margin_top).toInt()
+ val localPreviewWidth: Int = context!!.resources.getDimension(R.dimen.local_preview_width).toInt()
+ val localPreviewHeight: Int = context!!.resources.getDimension(R.dimen.local_preview_height).toInt()
+// val localPreviewIconSize: Int = context!!.resources.getDimension(R.dimen.local_back_icon_size).toInt()
+// val localPreviewMarginSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_margin_small).toInt()
+// val localPreviewWidthSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_width_small).toInt()
+// val localPreviewHeightSmall : Int = context!!.resources.getDimension(R.dimen.local_preview_height_small).toInt()
+// 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 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()
+ val remotePreviewLayoutParam: FrameLayout.LayoutParams = mSubscriberViewIcon.layoutParams as FrameLayout.LayoutParams
+
+ if (isFullScreen) {
+ layoutName.visibility = View.VISIBLE
+ layoutMini.visibility = View.GONE
+ mCameraBtn.visibility = View.VISIBLE
+ mSwitchCameraBtn.visibility = View.VISIBLE
+// mspeckerBtn.visibility = View.VISIBLE
+ mMicBtn.visibility = View.VISIBLE
+
+ btnMinimizeLayoutParam.width = iconSize
+ btnMinimizeLayoutParam.height = iconSize
+ mCallBtnLayoutParam.width = iconSize
+ mCallBtnLayoutParam.height = iconSize
+// localPreviewLayoutIconParam = FrameLayout.LayoutParams(localPreviewIconSize, localPreviewIconSize)
+//// localPreviewLayoutParam = RelativeLayout.LayoutParams(localPreviewWidth, localPreviewHeight)
+ localPreviewLayoutParam.width = localPreviewWidth
+ localPreviewLayoutParam.height = localPreviewHeight
+ localPreviewLayoutParam.setMargins(0, localPreviewMargin, localPreviewMargin, 0)
+// remotePreviewLayoutParam = FrameLayout.LayoutParams(remotePreviewIconSize, remotePreviewIconSize)
+ remotePreviewLayoutParam.width = remotePreviewIconSize
+ remotePreviewLayoutParam.height = remotePreviewIconSize
+ } else {
+ layoutName.visibility = View.GONE
+ layoutMini.visibility = View.VISIBLE
+ mCameraBtn.visibility = View.GONE
+ mSwitchCameraBtn.visibility = View.GONE
+// mspeckerBtn.visibility = View.GONE
+ mMicBtn.visibility = View.GONE
+// mPublisherViewContainer.visibility = View.GONE
+// mPublisherViewIcon.visibility = View.GONE
+
+// layoutParam = ConstraintLayout.LayoutParams(iconSizeSmall, iconSizeSmall)
+ btnMinimizeLayoutParam.width = iconSizeSmall
+ btnMinimizeLayoutParam.height = iconSizeSmall
+ mCallBtnLayoutParam.width = iconSizeSmall
+ mCallBtnLayoutParam.height = iconSizeSmall
+
+ localPreviewLayoutParam.width = 0
+ localPreviewLayoutParam.height = 0
+ localPreviewLayoutParam.setMargins(0, localPreviewMargin / 2, localPreviewMargin / 2, 0)
+// localPreviewLayoutIconParam = FrameLayout.LayoutParams(localPreviewIconSmall, localPreviewIconSmall)
+//// localPreviewLayoutParam = RelativeLayout.LayoutParams(localPreviewWidthSmall, localPreviewHeightSmall)
+// localPreviewLayoutParam.width = localPreviewWidthSmall
+// localPreviewLayoutParam.height = localPreviewWidthSmall
+// localPreviewLayoutParam.setMargins(0,localPreviewMarginSmall, localPreviewMarginSmall, 0)
+// remotePreviewLayoutParam = FrameLayout.LayoutParams(remotePreviewIconSizeSmall, remotePreviewIconSizeSmall)
+ remotePreviewLayoutParam.width = remotePreviewIconSizeSmall
+ remotePreviewLayoutParam.height = remotePreviewIconSizeSmall
+
+ if (isCircle) {
+ controlPanel.visibility = View.GONE
+ layoutMini.visibility = View.GONE
+ } else {
+ controlPanel.visibility = View.VISIBLE
+ layoutMini.visibility = View.VISIBLE
+ }
+ }
+
+ mPublisherViewContainer.layoutParams = localPreviewLayoutParam
+// mPublisherViewIcon.layoutParams = localPreviewLayoutIconParam
+ mSubscriberViewIcon.layoutParams = remotePreviewLayoutParam
+
+ btnMinimize.layoutParams = btnMinimizeLayoutParam
+ mCallBtn.layoutParams = mCallBtnLayoutParam
+ }
+
+ private fun onCameraClicked(view: View?) {
+ if (mPublisher != null) {
+ isCameraClicked = !isCameraClicked
+ mPublisher!!.publishVideo = !isCameraClicked
+ val res = if (isCameraClicked) R.drawable.video_disabled else R.drawable.video_enabled
+ mCameraBtn!!.setImageResource(res)
+ }
+ }
+
+ private fun onMicClicked(view: View?) {
+ if (mPublisher != null) {
+ isMicClicked = !isMicClicked
+ mPublisher!!.publishAudio = !isMicClicked
+ val res = if (isMicClicked) R.drawable.mic_disabled else R.drawable.mic_enabled
+ mMicBtn!!.setImageResource(res)
+ }
+ }
+
+ private fun onSpeckerClicked(view: View?) {
+ if (mSubscriber != null) {
+ isSpeckerClicked = !isSpeckerClicked
+ mSubscriber!!.subscribeToAudio = !isSpeckerClicked
+ val res = if (isSpeckerClicked) R.drawable.audio_disabled else R.drawable.audio_enabled
+ mspeckerBtn.setImageResource(res)
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun handleDragDialog() {
+ mWindowManager = requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ getWindowManagerDefaultDisplay()
+
+ videoCallContainer.setOnTouchListener(dragListener)
+ mSubscriberViewContainer.setOnTouchListener(dragListener)
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private val dragListener: View.OnTouchListener = View.OnTouchListener { _, event ->
+ mDetector.onTouchEvent(event)
+
+ //Get Floating widget view params
+ val layoutParams: WindowManager.LayoutParams = dialog!!.window!!.attributes
+ //get the touch location coordinates
+ val x_cord = event.rawX.toInt()
+ val y_cord = event.rawY.toInt()
+ val x_cord_Destination: Int
+ var y_cord_Destination: Int
+
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ x_init_cord = x_cord
+ y_init_cord = y_cord
+
+ //remember the initial position.
+ x_init_margin = layoutParams.x
+ y_init_margin = layoutParams.y
+ }
+ MotionEvent.ACTION_UP -> {
+ //Get the difference between initial coordinate and current coordinate
+ val x_diff: Int = x_cord - x_init_cord
+ val y_diff: Int = y_cord - y_init_cord
+
+ y_cord_Destination = y_init_margin + y_diff
+ val barHeight: Int = getStatusBarHeight()
+ if (y_cord_Destination < 0) {
+// y_cord_Destination = 0
+// y_cord_Destination =
+// -(szWindow.y - (videoCallContainer.height /*+ barHeight*/))
+ y_cord_Destination = -(szWindow.y / 2)
+ } else if (y_cord_Destination + (videoCallContainer.height + barHeight) > szWindow.y) {
+// y_cord_Destination = szWindow.y - (videoCallContainer.height + barHeight)
+ y_cord_Destination = (szWindow.y / 2)
+ }
+ layoutParams.y = y_cord_Destination
+
+ //reset position if user drags the floating view
+ resetPosition(x_cord)
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val x_diff_move: Int = x_cord - x_init_cord
+ val y_diff_move: Int = y_cord - y_init_cord
+ x_cord_Destination = x_init_margin + x_diff_move
+ y_cord_Destination = y_init_margin + y_diff_move
+
+ layoutParams.x = x_cord_Destination
+ layoutParams.y = y_cord_Destination
+
+ dialog!!.window!!.attributes = layoutParams
+ }
+ }
+ true
+ }
+
+ private fun showControlPanelTemporarily() {
+ if (!isCircle) {
+ controlPanel.visibility = View.VISIBLE
+ mVolHandler!!.removeCallbacks(mVolRunnable!!)
+ mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong())
+ }
+ }
+
+ /* Reset position of Floating Widget view on dragging */
+ private fun resetPosition(x_cord_now: Int) {
+ if (x_cord_now <= szWindow.x / 2) {
+ isLeft = true
+ moveToLeft(x_cord_now)
+ } else {
+ isLeft = false
+ moveToRight(x_cord_now)
+ }
+ }
+
+ /* Method to move the Floating widget view to Left */
+ private fun moveToLeft(current_x_cord: Int) {
+
+ var mParams: WindowManager.LayoutParams = dialog!!.window!!.attributes
+
+ mParams.x =
+ (szWindow.x - current_x_cord * current_x_cord - videoCallContainer.width).toInt()
+
+ dialog!!.window!!.attributes = mParams
+ val x = szWindow.x - current_x_cord
+ object : CountDownTimer(500, 5) {
+ //get params of Floating Widget view
+ var mParams: WindowManager.LayoutParams = dialog!!.window!!.attributes
+ override fun onTick(t: Long) {
+ val step = (500 - t) / 5
+ // mParams.x = 0 - (current_x_cord * current_x_cord * step).toInt()
+ mParams.x =
+ (szWindow.x - current_x_cord * current_x_cord * step - videoCallContainer.width).toInt()
+
+ dialog!!.window!!.attributes = mParams
+ }
+
+ override fun onFinish() {
+ mParams.x = -(szWindow.x - videoCallContainer.width)
+
+ dialog!!.window!!.attributes = mParams
+ }
+ }.start()
+ }
+
+ /* Method to move the Floating widget view to Right */
+ private fun moveToRight(current_x_cord: Int) {
+// var mParams : WindowManager.LayoutParams = dialog!!.window!!.attributes
+// mParams.x =
+// (szWindow.x + current_x_cord * current_x_cord - videoCallContainer.width).toInt()
+//
+// dialog!!.window!!.attributes = mParams
+ object : CountDownTimer(500, 5) {
+ //get params of Floating Widget view
+ var mParams: WindowManager.LayoutParams = dialog!!.window!!.attributes
+ override fun onTick(t: Long) {
+ val step = (500 - t) / 5
+ mParams.x =
+ (szWindow.x + current_x_cord * current_x_cord * step - videoCallContainer.width).toInt()
+
+ dialog!!.window!!.attributes = mParams
+ }
+
+ override fun onFinish() {
+ mParams.x = szWindow.x - videoCallContainer.width
+
+ dialog!!.window!!.attributes = mParams
+ }
+ }.start()
+ }
+
+ private fun getWindowManagerDefaultDisplay() {
+ mWindowManager.getDefaultDisplay()
+ .getSize(szWindow)
+ }
+
+ /* return status bar height on basis of device display metrics */
+ private fun getStatusBarHeight(): Int {
+ return ceil(
+ (25 * requireActivity().applicationContext.resources.displayMetrics.density).toDouble()
+ ).toInt()
+ }
+
+ private class MyGestureListener(val onTabCall: () -> Unit, val miniCircleDoubleTap: () -> Unit) : GestureDetector.SimpleOnGestureListener() {
+
+ override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
+ onTabCall()
+ return true
+ }
+
+ override fun onDoubleTap(e: MotionEvent?): Boolean {
+ miniCircleDoubleTap()
+ return super.onDoubleTap(e)
+ }
+
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance(args: Bundle) =
+ VideoCallFragment().apply {
+ arguments = args
+ }
+
+ private val TAG = VideoCallFragment::class.java.simpleName
+
+ private const val RC_SETTINGS_SCREEN_PERM = 123
+ private const val RC_VIDEO_APP_PERM = 124
+
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/util/DynamicVideoRenderer.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/util/DynamicVideoRenderer.kt
new file mode 100644
index 00000000..1a307eb5
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/util/DynamicVideoRenderer.kt
@@ -0,0 +1,379 @@
+package com.hmg.hmgDr.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PixelFormat
+import android.opengl.GLES20
+import android.opengl.GLSurfaceView
+import android.opengl.Matrix
+import android.view.View
+import com.opentok.android.BaseVideoRenderer
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.nio.FloatBuffer
+import java.nio.ShortBuffer
+import java.util.concurrent.locks.ReentrantLock
+import javax.microedition.khronos.egl.EGLConfig
+import javax.microedition.khronos.opengles.GL10
+
+/*
+* https://nhancv.medium.com/android-how-to-make-a-circular-view-as-a-thumbnail-of-opentok-27992aee15c9
+* to solve make circle video stream
+* */
+
+class DynamicVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
+ private val mView: GLSurfaceView = GLSurfaceView(mContext)
+ private val mRenderer: MyRenderer
+
+ interface DynamicVideoRendererMetadataListener {
+ fun onMetadataReady(metadata: ByteArray?)
+ }
+
+ fun setDynamicVideoRendererMetadataListener(metadataListener: DynamicVideoRendererMetadataListener?) {
+ mRenderer.metadataListener = metadataListener
+ }
+
+ fun enableThumbnailCircle(enable: Boolean) {
+ mRenderer.requestEnableThumbnailCircle = enable
+ }
+
+ internal class MyRenderer : GLSurfaceView.Renderer {
+ var mTextureIds = IntArray(3)
+ var mScaleMatrix = FloatArray(16)
+ private val mVertexBuffer: FloatBuffer
+ private val mTextureBuffer: FloatBuffer
+ private val mDrawListBuffer: ShortBuffer
+ var requestEnableThumbnailCircle = false
+ var mVideoFitEnabled = true
+ var mVideoDisabled = false
+ private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
+
+ // vertices
+ private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
+attribute vec2 aTextureCoord;
+varying vec2 vTextureCoord;
+void main() {
+ gl_Position = uMVPMatrix * aPosition;
+ vTextureCoord = aTextureCoord;
+}
+"""
+ private val fragmentShaderCode = """precision mediump float;
+uniform sampler2D Ytex;
+uniform sampler2D Utex,Vtex;
+uniform int enableCircle;
+uniform vec2 radiusDp;
+varying vec2 vTextureCoord;
+void main(void) {
+ float nx,ny,r,g,b,y,u,v;
+ mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
+ ny=vTextureCoord[1];
+ y=texture2D(Ytex,vec2(nx,ny)).r;
+ u=texture2D(Utex,vec2(nx,ny)).r;
+ v=texture2D(Vtex,vec2(nx,ny)).r;
+ y=1.1643*(y-0.0625);
+ u=u-0.5;
+ v=v-0.5;
+ r=y+1.5958*v;
+ g=y-0.39173*u-0.81290*v;
+ b=y+2.017*u;
+ if (enableCircle > 0) {
+ float radius = 0.5;
+ vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
+ vec4 color1 = vec4(r, g, b, 1.0);
+ vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
+ float t = 1.0;
+ if (dist < 0.0) t = 0.0;
+ gl_FragColor = mix(color0, color1, t);
+ }
+ else {
+ gl_FragColor = vec4(r, g, b, 1.0);
+ }
+}
+"""
+ var mFrameLock = ReentrantLock()
+ var mCurrentFrame: Frame? = null
+ private var mProgram = 0
+ private var mTextureWidth = 0
+ private var mTextureHeight = 0
+ private var mViewportWidth = 0
+ private var mViewportHeight = 0
+ override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
+ gl.glClearColor(0f, 0f, 0f, 1f)
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+ val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
+ vertexShaderCode)
+ val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
+ fragmentShaderCode)
+ mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
+ // Program
+ GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
+ // shader to program
+ GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
+ // shader to
+ // program
+ GLES20.glLinkProgram(mProgram)
+ val positionHandle = GLES20.glGetAttribLocation(mProgram,
+ "aPosition")
+ val textureHandle = GLES20.glGetAttribLocation(mProgram,
+ "aTextureCoord")
+ GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
+ GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
+ mVertexBuffer)
+ GLES20.glEnableVertexAttribArray(positionHandle)
+ GLES20.glVertexAttribPointer(textureHandle,
+ TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
+ TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
+ GLES20.glEnableVertexAttribArray(textureHandle)
+ GLES20.glUseProgram(mProgram)
+ var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
+ GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
+ i = GLES20.glGetUniformLocation(mProgram, "Utex")
+ GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
+ i = GLES20.glGetUniformLocation(mProgram, "Vtex")
+ GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
+ val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
+ val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
+ GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
+ mTextureWidth = 0
+ mTextureHeight = 0
+ }
+
+ fun enableThumbnailCircle(enable: Boolean) {
+ GLES20.glUseProgram(mProgram)
+ val enableCircleLocation = GLES20.glGetUniformLocation(mProgram, "enableCircle")
+ GLES20.glUniform1i(enableCircleLocation, if (enable) 1 else 0)
+ }
+
+ fun setupTextures(frame: Frame) {
+ if (mTextureIds[0] != 0) {
+ GLES20.glDeleteTextures(3, mTextureIds, 0)
+ }
+ GLES20.glGenTextures(3, mTextureIds, 0)
+ val w = frame.width
+ val h = frame.height
+ val hw = w + 1 shr 1
+ val hh = h + 1 shr 1
+ initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
+ initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
+ initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
+ mTextureWidth = frame.width
+ mTextureHeight = frame.height
+ }
+
+ fun updateTextures(frame: Frame) {
+ val width = frame.width
+ val height = frame.height
+ val half_width = width + 1 shr 1
+ val half_height = height + 1 shr 1
+ val y_size = width * height
+ val uv_size = half_width * half_height
+ val bb = frame.buffer
+ // If we are reusing this frame, make sure we reset position and
+ // limit
+ bb.clear()
+ if (bb.remaining() == y_size + uv_size * 2) {
+ bb.position(0)
+ GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
+ GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
+ height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
+ bb)
+ bb.position(y_size)
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
+ half_width, half_height, GLES20.GL_LUMINANCE,
+ GLES20.GL_UNSIGNED_BYTE, bb)
+ bb.position(y_size + uv_size)
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
+ half_width, half_height, GLES20.GL_LUMINANCE,
+ GLES20.GL_UNSIGNED_BYTE, bb)
+ } else {
+ mTextureWidth = 0
+ mTextureHeight = 0
+ }
+ }
+
+ override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
+ GLES20.glViewport(0, 0, width, height)
+ mViewportWidth = width
+ mViewportHeight = height
+ }
+
+ var metadataListener: DynamicVideoRendererMetadataListener? = null
+ override fun onDrawFrame(gl: GL10) {
+ gl.glClearColor(0f, 0f, 0f, 0f)
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+ mFrameLock.lock()
+ if (mCurrentFrame != null && !mVideoDisabled) {
+ GLES20.glUseProgram(mProgram)
+ if (mTextureWidth != mCurrentFrame!!.width
+ || mTextureHeight != mCurrentFrame!!.height) {
+ setupTextures(mCurrentFrame!!)
+ }
+ updateTextures(mCurrentFrame!!)
+ Matrix.setIdentityM(mScaleMatrix, 0)
+ var scaleX = 1.0f
+ var scaleY = 1.0f
+ val ratio = (mCurrentFrame!!.width.toFloat()
+ / mCurrentFrame!!.height)
+ val vratio = mViewportWidth.toFloat() / mViewportHeight
+ if (mVideoFitEnabled) {
+ if (ratio > vratio) {
+ scaleY = vratio / ratio
+ } else {
+ scaleX = ratio / vratio
+ }
+ } else {
+ if (ratio < vratio) {
+ scaleY = vratio / ratio
+ } else {
+ scaleX = ratio / vratio
+ }
+ }
+ Matrix.scaleM(mScaleMatrix, 0,
+ scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
+ scaleY, 1f)
+ metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
+ val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
+ "uMVPMatrix")
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
+ mScaleMatrix, 0)
+ enableThumbnailCircle(requestEnableThumbnailCircle)
+ GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
+ GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
+ } else {
+ //black frame when video is disabled
+ gl.glClearColor(0f, 0f, 0f, 1f)
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+ }
+ mFrameLock.unlock()
+ }
+
+ fun displayFrame(frame: Frame?) {
+ mFrameLock.lock()
+ if (mCurrentFrame != null) {
+ mCurrentFrame!!.recycle()
+ }
+ mCurrentFrame = frame
+ mFrameLock.unlock()
+ }
+
+ fun disableVideo(b: Boolean) {
+ mFrameLock.lock()
+ mVideoDisabled = b
+ if (mVideoDisabled) {
+ if (mCurrentFrame != null) {
+ mCurrentFrame!!.recycle()
+ }
+ mCurrentFrame = null
+ }
+ mFrameLock.unlock()
+ }
+
+ fun enableVideoFit(enableVideoFit: Boolean) {
+ mVideoFitEnabled = enableVideoFit
+ }
+
+ companion object {
+ // number of coordinates per vertex in this array
+ const val COORDS_PER_VERTEX = 3
+ const val TEXTURECOORDS_PER_VERTEX = 2
+ var mXYZCoords = floatArrayOf(
+ -1.0f, 1.0f, 0.0f, // top left
+ -1.0f, -1.0f, 0.0f, // bottom left
+ 1.0f, -1.0f, 0.0f, // bottom right
+ 1.0f, 1.0f, 0.0f // top right
+ )
+ var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
+ fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
+ GLES20.glActiveTexture(name)
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
+ GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
+ width, height, 0, GLES20.GL_LUMINANCE,
+ GLES20.GL_UNSIGNED_BYTE, null)
+ }
+
+ fun loadShader(type: Int, shaderCode: String?): Int {
+ val shader = GLES20.glCreateShader(type)
+ GLES20.glShaderSource(shader, shaderCode)
+ GLES20.glCompileShader(shader)
+ return shader
+ }
+ }
+
+ init {
+ val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
+ bb.order(ByteOrder.nativeOrder())
+ mVertexBuffer = bb.asFloatBuffer()
+ mVertexBuffer.put(mXYZCoords)
+ mVertexBuffer.position(0)
+ val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
+ tb.order(ByteOrder.nativeOrder())
+ mTextureBuffer = tb.asFloatBuffer()
+ mTextureBuffer.put(mUVCoords)
+ mTextureBuffer.position(0)
+ val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
+ dlb.order(ByteOrder.nativeOrder())
+ mDrawListBuffer = dlb.asShortBuffer()
+ mDrawListBuffer.put(mVertexIndex)
+ mDrawListBuffer.position(0)
+ }
+ }
+
+ override fun onFrame(frame: Frame) {
+ mRenderer.displayFrame(frame)
+ mView.requestRender()
+ }
+
+ override fun setStyle(key: String, value: String) {
+ if (STYLE_VIDEO_SCALE == key) {
+ if (STYLE_VIDEO_FIT == value) {
+ mRenderer.enableVideoFit(true)
+ } else if (STYLE_VIDEO_FILL == value) {
+ mRenderer.enableVideoFit(false)
+ }
+ }
+ }
+
+ override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
+ mRenderer.disableVideo(!videoEnabled)
+ }
+
+ override fun getView(): View {
+ return mView
+ }
+
+ override fun onPause() {
+ mView.onPause()
+ }
+
+ override fun onResume() {
+ mView.onResume()
+ }
+
+ companion object {
+ private const val THUMBNAIL_SIZE = 90 //in dp
+ }
+
+ init {
+ mView.setEGLContextClientVersion(2)
+ mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
+ mView.holder.setFormat(PixelFormat.TRANSLUCENT)
+ mRenderer = MyRenderer()
+ mView.setRenderer(mRenderer)
+ mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/util/ThumbnailCircleVideoRenderer.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/util/ThumbnailCircleVideoRenderer.kt
new file mode 100644
index 00000000..b9b5a245
--- /dev/null
+++ b/android/app/src/main/kotlin/com/hmg/hmgDr/util/ThumbnailCircleVideoRenderer.kt
@@ -0,0 +1,357 @@
+package com.hmg.hmgDr.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PixelFormat
+import android.opengl.GLES20
+import android.opengl.GLSurfaceView
+import android.opengl.Matrix
+import android.view.View
+import com.opentok.android.BaseVideoRenderer
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.nio.FloatBuffer
+import java.nio.ShortBuffer
+import java.util.concurrent.locks.ReentrantLock
+import javax.microedition.khronos.egl.EGLConfig
+import javax.microedition.khronos.opengles.GL10
+
+
+class ThumbnailCircleVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
+ private val mView: GLSurfaceView = GLSurfaceView(mContext)
+ private val mRenderer: MyRenderer
+
+ interface ThumbnailCircleVideoRendererMetadataListener {
+ fun onMetadataReady(metadata: ByteArray?)
+ }
+
+ fun setThumbnailCircleVideoRendererMetadataListener(metadataListener: ThumbnailCircleVideoRendererMetadataListener?) {
+ mRenderer.metadataListener = metadataListener
+ }
+
+ internal class MyRenderer : GLSurfaceView.Renderer {
+ var mTextureIds = IntArray(3)
+ var mScaleMatrix = FloatArray(16)
+ private val mVertexBuffer: FloatBuffer
+ private val mTextureBuffer: FloatBuffer
+ private val mDrawListBuffer: ShortBuffer
+ var mVideoFitEnabled = true
+ var mVideoDisabled = false
+ private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
+
+ // vertices
+ private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
+attribute vec2 aTextureCoord;
+varying vec2 vTextureCoord;
+void main() {
+ gl_Position = uMVPMatrix * aPosition;
+ vTextureCoord = aTextureCoord;
+}
+"""
+ private val fragmentShaderCode = """precision mediump float;
+uniform sampler2D Ytex;
+uniform sampler2D Utex,Vtex;
+uniform vec2 radiusDp;
+varying vec2 vTextureCoord;
+void main(void) {
+ float nx,ny,r,g,b,y,u,v;
+ mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
+ ny=vTextureCoord[1];
+ y=texture2D(Ytex,vec2(nx,ny)).r;
+ u=texture2D(Utex,vec2(nx,ny)).r;
+ v=texture2D(Vtex,vec2(nx,ny)).r;
+ y=1.1643*(y-0.0625);
+ u=u-0.5;
+ v=v-0.5;
+ r=y+1.5958*v;
+ g=y-0.39173*u-0.81290*v;
+ b=y+2.017*u;
+ float radius = 0.5;
+ vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
+ vec4 color1 = vec4(r, g, b, 1.0);
+ vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
+ float t = 1.0;
+ if (dist < 0.0) t = 0.0;
+ gl_FragColor = mix(color0, color1, t);
+}
+"""
+ var mFrameLock = ReentrantLock()
+ var mCurrentFrame: Frame? = null
+ private var mProgram = 0
+ private var mTextureWidth = 0
+ private var mTextureHeight = 0
+ private var mViewportWidth = 0
+ private var mViewportHeight = 0
+ override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
+ gl.glClearColor(0f, 0f, 0f, 1f)
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+ val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
+ vertexShaderCode)
+ val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
+ fragmentShaderCode)
+ mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
+ // Program
+ GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
+ // shader to program
+ GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
+ // shader to
+ // program
+ GLES20.glLinkProgram(mProgram)
+ val positionHandle = GLES20.glGetAttribLocation(mProgram,
+ "aPosition")
+ val textureHandle = GLES20.glGetAttribLocation(mProgram,
+ "aTextureCoord")
+ GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
+ GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
+ mVertexBuffer)
+ GLES20.glEnableVertexAttribArray(positionHandle)
+ GLES20.glVertexAttribPointer(textureHandle,
+ TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
+ TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
+ GLES20.glEnableVertexAttribArray(textureHandle)
+ GLES20.glUseProgram(mProgram)
+ var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
+ GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
+ i = GLES20.glGetUniformLocation(mProgram, "Utex")
+ GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
+ i = GLES20.glGetUniformLocation(mProgram, "Vtex")
+ GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
+ val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
+ val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
+ GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
+ mTextureWidth = 0
+ mTextureHeight = 0
+ }
+
+ fun setupTextures(frame: Frame) {
+ if (mTextureIds[0] != 0) {
+ GLES20.glDeleteTextures(3, mTextureIds, 0)
+ }
+ GLES20.glGenTextures(3, mTextureIds, 0)
+ val w = frame.width
+ val h = frame.height
+ val hw = w + 1 shr 1
+ val hh = h + 1 shr 1
+ initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
+ initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
+ initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
+ mTextureWidth = frame.width
+ mTextureHeight = frame.height
+ }
+
+ fun updateTextures(frame: Frame) {
+ val width = frame.width
+ val height = frame.height
+ val half_width = width + 1 shr 1
+ val half_height = height + 1 shr 1
+ val y_size = width * height
+ val uv_size = half_width * half_height
+ val bb = frame.buffer
+ // If we are reusing this frame, make sure we reset position and
+ // limit
+ bb.clear()
+ if (bb.remaining() == y_size + uv_size * 2) {
+ bb.position(0)
+ GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
+ GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
+ height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
+ bb)
+ bb.position(y_size)
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
+ half_width, half_height, GLES20.GL_LUMINANCE,
+ GLES20.GL_UNSIGNED_BYTE, bb)
+ bb.position(y_size + uv_size)
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
+ half_width, half_height, GLES20.GL_LUMINANCE,
+ GLES20.GL_UNSIGNED_BYTE, bb)
+ } else {
+ mTextureWidth = 0
+ mTextureHeight = 0
+ }
+ }
+
+ override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
+ GLES20.glViewport(0, 0, width, height)
+ mViewportWidth = width
+ mViewportHeight = height
+ }
+
+ var metadataListener: ThumbnailCircleVideoRendererMetadataListener? = null
+ override fun onDrawFrame(gl: GL10) {
+ gl.glClearColor(0f, 0f, 0f, 0f)
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+ mFrameLock.lock()
+ if (mCurrentFrame != null && !mVideoDisabled) {
+ GLES20.glUseProgram(mProgram)
+ if (mTextureWidth != mCurrentFrame!!.width
+ || mTextureHeight != mCurrentFrame!!.height) {
+ setupTextures(mCurrentFrame!!)
+ }
+ updateTextures(mCurrentFrame!!)
+ Matrix.setIdentityM(mScaleMatrix, 0)
+ var scaleX = 1.0f
+ var scaleY = 1.0f
+ val ratio = (mCurrentFrame!!.width.toFloat()
+ / mCurrentFrame!!.height)
+ val vratio = mViewportWidth.toFloat() / mViewportHeight
+ if (mVideoFitEnabled) {
+ if (ratio > vratio) {
+ scaleY = vratio / ratio
+ } else {
+ scaleX = ratio / vratio
+ }
+ } else {
+ if (ratio < vratio) {
+ scaleY = vratio / ratio
+ } else {
+ scaleX = ratio / vratio
+ }
+ }
+ Matrix.scaleM(mScaleMatrix, 0,
+ scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
+ scaleY, 1f)
+ metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
+ val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
+ "uMVPMatrix")
+ GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
+ mScaleMatrix, 0)
+ GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
+ GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
+ } else {
+ //black frame when video is disabled
+ gl.glClearColor(0f, 0f, 0f, 1f)
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+ }
+ mFrameLock.unlock()
+ }
+
+ fun displayFrame(frame: Frame?) {
+ mFrameLock.lock()
+ if (mCurrentFrame != null) {
+ mCurrentFrame!!.recycle()
+ }
+ mCurrentFrame = frame
+ mFrameLock.unlock()
+ }
+
+ fun disableVideo(b: Boolean) {
+ mFrameLock.lock()
+ mVideoDisabled = b
+ if (mVideoDisabled) {
+ if (mCurrentFrame != null) {
+ mCurrentFrame!!.recycle()
+ }
+ mCurrentFrame = null
+ }
+ mFrameLock.unlock()
+ }
+
+ fun enableVideoFit(enableVideoFit: Boolean) {
+ mVideoFitEnabled = enableVideoFit
+ }
+
+ companion object {
+ // number of coordinates per vertex in this array
+ const val COORDS_PER_VERTEX = 3
+ const val TEXTURECOORDS_PER_VERTEX = 2
+ var mXYZCoords = floatArrayOf(
+ -1.0f, 1.0f, 0.0f, // top left
+ -1.0f, -1.0f, 0.0f, // bottom left
+ 1.0f, -1.0f, 0.0f, // bottom right
+ 1.0f, 1.0f, 0.0f // top right
+ )
+ var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
+ fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
+ GLES20.glActiveTexture(name)
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
+ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
+ GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
+ GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
+ width, height, 0, GLES20.GL_LUMINANCE,
+ GLES20.GL_UNSIGNED_BYTE, null)
+ }
+
+ fun loadShader(type: Int, shaderCode: String?): Int {
+ val shader = GLES20.glCreateShader(type)
+ GLES20.glShaderSource(shader, shaderCode)
+ GLES20.glCompileShader(shader)
+ return shader
+ }
+ }
+
+ init {
+ val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
+ bb.order(ByteOrder.nativeOrder())
+ mVertexBuffer = bb.asFloatBuffer()
+ mVertexBuffer.put(mXYZCoords)
+ mVertexBuffer.position(0)
+ val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
+ tb.order(ByteOrder.nativeOrder())
+ mTextureBuffer = tb.asFloatBuffer()
+ mTextureBuffer.put(mUVCoords)
+ mTextureBuffer.position(0)
+ val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
+ dlb.order(ByteOrder.nativeOrder())
+ mDrawListBuffer = dlb.asShortBuffer()
+ mDrawListBuffer.put(mVertexIndex)
+ mDrawListBuffer.position(0)
+ }
+ }
+
+ override fun onFrame(frame: Frame) {
+ mRenderer.displayFrame(frame)
+ mView.requestRender()
+ }
+
+ override fun setStyle(key: String, value: String) {
+ if (STYLE_VIDEO_SCALE == key) {
+ if (STYLE_VIDEO_FIT == value) {
+ mRenderer.enableVideoFit(true)
+ } else if (STYLE_VIDEO_FILL == value) {
+ mRenderer.enableVideoFit(false)
+ }
+ }
+ }
+
+ override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
+ mRenderer.disableVideo(!videoEnabled)
+ }
+
+ override fun getView(): View {
+ return mView
+ }
+
+ override fun onPause() {
+ mView.onPause()
+ }
+
+ override fun onResume() {
+ mView.onResume()
+ }
+
+ companion object {
+ private const val THUMBNAIL_SIZE = 90 //in dp
+ }
+
+ init {
+ mView.setEGLContextClientVersion(2)
+ mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
+ mView.holder.setFormat(PixelFormat.TRANSLUCENT)
+ mRenderer = MyRenderer()
+ mView.setRenderer(mRenderer)
+ mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
+ }
+}
diff --git a/android/app/src/main/res/drawable/call.png b/android/app/src/main/res/drawable/call.png
index fc00f4f9..52f8e267 100644
Binary files a/android/app/src/main/res/drawable/call.png and b/android/app/src/main/res/drawable/call.png differ
diff --git a/android/app/src/main/res/drawable/camera_back.png b/android/app/src/main/res/drawable/camera_back.png
new file mode 100644
index 00000000..c3d63ac3
Binary files /dev/null and b/android/app/src/main/res/drawable/camera_back.png differ
diff --git a/android/app/src/main/res/drawable/camera_front.png b/android/app/src/main/res/drawable/camera_front.png
new file mode 100644
index 00000000..332ab0f5
Binary files /dev/null and b/android/app/src/main/res/drawable/camera_front.png differ
diff --git a/android/app/src/main/res/drawable/circle_shape.xml b/android/app/src/main/res/drawable/circle_shape.xml
new file mode 100644
index 00000000..27b49f9b
--- /dev/null
+++ b/android/app/src/main/res/drawable/circle_shape.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/expand.png b/android/app/src/main/res/drawable/expand.png
new file mode 100644
index 00000000..7020dc2e
Binary files /dev/null and b/android/app/src/main/res/drawable/expand.png differ
diff --git a/android/app/src/main/res/drawable/flip_disapled.png b/android/app/src/main/res/drawable/flip_disapled.png
deleted file mode 100644
index 5226029e..00000000
Binary files a/android/app/src/main/res/drawable/flip_disapled.png and /dev/null differ
diff --git a/android/app/src/main/res/drawable/flip_enabled.png b/android/app/src/main/res/drawable/flip_enabled.png
deleted file mode 100644
index 152dc10e..00000000
Binary files a/android/app/src/main/res/drawable/flip_enabled.png and /dev/null differ
diff --git a/android/app/src/main/res/drawable/ic_mini.xml b/android/app/src/main/res/drawable/ic_mini.xml
index 29df8da2..128a7430 100644
--- a/android/app/src/main/res/drawable/ic_mini.xml
+++ b/android/app/src/main/res/drawable/ic_mini.xml
@@ -1,16 +1,5 @@
-
-
-
-
+
+
diff --git a/android/app/src/main/res/drawable/layout_rounded_bg.xml b/android/app/src/main/res/drawable/layout_rounded_bg.xml
new file mode 100644
index 00000000..ce73e763
--- /dev/null
+++ b/android/app/src/main/res/drawable/layout_rounded_bg.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/mic_disabled.png b/android/app/src/main/res/drawable/mic_disabled.png
index 3603df75..266cb6c7 100644
Binary files a/android/app/src/main/res/drawable/mic_disabled.png and b/android/app/src/main/res/drawable/mic_disabled.png differ
diff --git a/android/app/src/main/res/drawable/mic_enabled.png b/android/app/src/main/res/drawable/mic_enabled.png
index 5d9aa677..ef7617f7 100644
Binary files a/android/app/src/main/res/drawable/mic_enabled.png and b/android/app/src/main/res/drawable/mic_enabled.png differ
diff --git a/android/app/src/main/res/drawable/reducing.png b/android/app/src/main/res/drawable/reducing.png
new file mode 100644
index 00000000..59f2c9e6
Binary files /dev/null and b/android/app/src/main/res/drawable/reducing.png differ
diff --git a/android/app/src/main/res/drawable/video_disabled.png b/android/app/src/main/res/drawable/video_disabled.png
new file mode 100644
index 00000000..d6ccbe7a
Binary files /dev/null and b/android/app/src/main/res/drawable/video_disabled.png differ
diff --git a/android/app/src/main/res/drawable/video_disanabled.png b/android/app/src/main/res/drawable/video_disanabled.png
deleted file mode 100644
index 5c20c7bd..00000000
Binary files a/android/app/src/main/res/drawable/video_disanabled.png and /dev/null differ
diff --git a/android/app/src/main/res/drawable/video_enabled.png b/android/app/src/main/res/drawable/video_enabled.png
index 23331e30..6fcbe750 100644
Binary files a/android/app/src/main/res/drawable/video_enabled.png and b/android/app/src/main/res/drawable/video_enabled.png differ
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 a3c2ced1..a54e57b3 100644
--- a/android/app/src/main/res/layout/activity_video_call.xml
+++ b/android/app/src/main/res/layout/activity_video_call.xml
@@ -1,15 +1,19 @@
-
+ android:padding="@dimen/padding_space_medium"
+ app:layout_constraintTop_toTopOf="parent">
+ tools:text="25:45" />
@@ -46,47 +51,44 @@
android:id="@+id/activity_clingo_video_call"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1"
- tools:context=".ui.VideoCallActivity">
+ app:layout_constraintBottom_toTopOf="@id/control_panel"
+ app:layout_constraintTop_toBottomOf="@+id/layout_name">
-
-
-
-
+ android:layout_height="wrap_content"
+ android:background="@color/remoteBackground"
+ android:alpha="0.5"
+ android:visibility="gone">
+
+
-
-
+
+ android:layout_marginEnd="@dimen/local_preview_margin_top">
-
+
-
+ android:padding="@dimen/padding_space_big"
+ app:layout_constraintBottom_toBottomOf="parent">
+ android:src="@drawable/call"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ android:src="@drawable/reducing"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ android:src="@drawable/video_enabled"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/btn_minimize"
+ app:layout_constraintTop_toTopOf="parent" />
+ android:src="@drawable/mic_enabled"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/btn_camera"
+ app:layout_constraintTop_toTopOf="parent" />
+ android:src="@drawable/camera_back"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@id/btn_mic"
+ app:layout_constraintTop_toTopOf="parent" />
-
-
+
+
+
+
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
index 29782be0..f52c2cf2 100644
--- a/android/app/src/main/res/values/colors.xml
+++ b/android/app/src/main/res/values/colors.xml
@@ -5,6 +5,10 @@
#fc3850
#e4e9f2
+ #80757575
+ #00ffffff
+
+
#827b92
#484258
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
index 2d53d554..0694f5f4 100644
--- a/android/app/src/main/res/values/dimens.xml
+++ b/android/app/src/main/res/values/dimens.xml
@@ -3,27 +3,33 @@
16dp
16dp
28dp
+ 12dp
24dp
60dp
54dp
- 64dp
+ 52dp
+ 24dp
24dp
25dp
88dp
+ 40dp
117dp
+ 50dp
50dp
+ 25dp
100dp
+ 40dp
90dp
14sp
16sp
- 24sp
+ 22sp
4dp
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 74349756..bc694f7c 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -4,5 +4,7 @@
الوقت المتبقي بالثانيه:
Settings
Cancel
+
+ Hello blank fragment
\ No newline at end of file
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 24355fae..e13b5b14 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -16,4 +16,21 @@
- true
- @null
+
+
+
diff --git a/ios/Podfile b/ios/Podfile
index 62207805..c2335042 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -65,6 +65,7 @@ target 'Runner' do
pod 'Flutter', :path => 'Flutter'
pod 'OpenTok'
pod 'Alamofire', '~> 5.2'
+ pod 'AADraggableView'
# Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index f0784432..66be39bf 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -9,12 +9,16 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
29211E4225C172B700DD740D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 29211E4125C172B700DD740D /* GoogleService-Info.plist */; };
+ 300790FA266FB14B0052174C /* VCEmbeder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300790F9266FB14B0052174C /* VCEmbeder.swift */; };
+ 300790FC26710CAB0052174C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300790FB26710CAB0052174C /* Extensions.swift */; };
+ 30F70E6C266F56FD005D8F8E /* MainAppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F70E6B266F56FD005D8F8E /* MainAppViewController.swift */; };
+ 30F70E6F266F6509005D8F8E /* VideoCallRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30F70E6E266F6509005D8F8E /* VideoCallRequestParameters.swift */; };
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 */; };
- 9CE61EBD24AB366E008D68DD /* VideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE61EBC24AB366E008D68DD /* VideoViewController.swift */; };
+ 9CE61EBD24AB366E008D68DD /* VideoCallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE61EBC24AB366E008D68DD /* VideoCallViewController.swift */; };
9CE61ECD24ADBB4C008D68DD /* ICallProtocoll.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE61ECC24ADBB4C008D68DD /* ICallProtocoll.swift */; };
B650DC3076E9D70CB188286A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93A5F83B23AB032D1E096663 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
@@ -37,6 +41,10 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
29211CD725C165D600DD740D /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = ""; };
29211E4125C172B700DD740D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
+ 300790F9266FB14B0052174C /* VCEmbeder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VCEmbeder.swift; sourceTree = ""; };
+ 300790FB26710CAB0052174C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
+ 30F70E6B266F56FD005D8F8E /* MainAppViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAppViewController.swift; sourceTree = ""; };
+ 30F70E6E266F6509005D8F8E /* VideoCallRequestParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallRequestParameters.swift; 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 = ""; };
@@ -50,7 +58,7 @@
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 = ""; };
- 9CE61EBC24AB366E008D68DD /* VideoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoViewController.swift; sourceTree = ""; };
+ 9CE61EBC24AB366E008D68DD /* VideoCallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCallViewController.swift; sourceTree = ""; };
9CE61ECC24ADBB4C008D68DD /* ICallProtocoll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICallProtocoll.swift; sourceTree = ""; };
9D4B7DB43C6A6C849D2387CE /* 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 = ""; };
E698D7B14B12DF768FE47A1A /* 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 = ""; };
@@ -68,6 +76,31 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 300790F8266FB0F10052174C /* helpers */ = {
+ isa = PBXGroup;
+ children = (
+ 300790F9266FB14B0052174C /* VCEmbeder.swift */,
+ 300790FB26710CAB0052174C /* Extensions.swift */,
+ );
+ name = helpers;
+ sourceTree = "";
+ };
+ 30F70E6A266F56C9005D8F8E /* controllers */ = {
+ isa = PBXGroup;
+ children = (
+ 30F70E6B266F56FD005D8F8E /* MainAppViewController.swift */,
+ );
+ name = controllers;
+ sourceTree = "";
+ };
+ 30F70E6D266F64F8005D8F8E /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ 30F70E6E266F6509005D8F8E /* VideoCallRequestParameters.swift */,
+ );
+ name = Models;
+ sourceTree = "";
+ };
7D66D387293CE5376A07EC5F /* Pods */ = {
isa = PBXGroup;
children = (
@@ -111,6 +144,9 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ 300790F8266FB0F10052174C /* helpers */,
+ 30F70E6D266F64F8005D8F8E /* Models */,
+ 30F70E6A266F56C9005D8F8E /* controllers */,
29211E4125C172B700DD740D /* GoogleService-Info.plist */,
29211CD725C165D600DD740D /* RunnerRelease.entitlements */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
@@ -122,7 +158,7 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
- 9CE61EBC24AB366E008D68DD /* VideoViewController.swift */,
+ 9CE61EBC24AB366E008D68DD /* VideoCallViewController.swift */,
9CE61ECC24ADBB4C008D68DD /* ICallProtocoll.swift */,
);
path = Runner;
@@ -290,10 +326,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 300790FA266FB14B0052174C /* VCEmbeder.swift in Sources */,
+ 30F70E6F266F6509005D8F8E /* VideoCallRequestParameters.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
- 9CE61EBD24AB366E008D68DD /* VideoViewController.swift in Sources */,
+ 300790FC26710CAB0052174C /* Extensions.swift in Sources */,
+ 9CE61EBD24AB366E008D68DD /* VideoCallViewController.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
9CE61ECD24ADBB4C008D68DD /* ICallProtocoll.swift in Sources */,
+ 30F70E6C266F56FD005D8F8E /* MainAppViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index fb2dffc4..a28140cf 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -27,6 +27,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
+
+
-
-
+
+
+
+
Bool {
- let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
- let videoCallChannel = FlutterMethodChannel(name: "Dr.cloudSolution/videoCall",
- binaryMessenger: controller.binaryMessenger)
- videoCallChannel.setMethodCallHandler({
- (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
- self.result = result
- switch call.method {
- case "openVideoCall":
- do {
- let arguments = call.arguments as? NSDictionary
- let kApiKey = arguments!["kApiKey"] as? String
- let kSessionId = arguments!["kSessionId"] as? String
- let kToken = arguments!["kToken"] as? String
- let appLang = arguments!["appLang"] as? String
- let vC_ID = arguments!["VC_ID"] as? Int
- let tokenID = arguments!["TokenID"] as? String
- let generalId = arguments!["generalId"] as? String
- let doctorId = arguments!["DoctorId"] as? Int
- let baseUrl = arguments!["baseUrl"] as? String
-
- self.openVideoChat(result: result,kApiKey: kApiKey!,kSessionId:kSessionId!,kToken: kToken!, appLang: appLang!, vC_ID: vC_ID!,tokenID: tokenID!,generalId: generalId!,doctorId: doctorId!, baseUrl: baseUrl!)
- }
- default:
- result(FlutterMethodNotImplemented)
- }
- })
- GeneratedPluginRegistrant.register(with: self)
+ GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
@@ -61,7 +35,7 @@ import OpenTok
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let identifier = "ViewControllerNav"
let navVC = storyboard.instantiateViewController(withIdentifier: identifier) as! UINavigationController
- let videoVC = navVC.viewControllers.first as! ViewController
+ let videoVC = navVC.viewControllers.first as! VideoCallViewController
videoVC.kApiKey=kApiKey
videoVC.kSessionId=kSessionId
videoVC.kToken=kToken
@@ -75,6 +49,6 @@ import OpenTok
navVC.modalPresentationStyle = .fullScreen
window.rootViewController?.present(navVC, animated: true, completion: nil)
-
}
}
+
diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json
index da4a164c..73c00596 100755
--- a/ios/Runner/Assets.xcassets/Contents.json
+++ b/ios/Runner/Assets.xcassets/Contents.json
@@ -1,6 +1,6 @@
{
"info" : {
- "version" : 1,
- "author" : "xcode"
+ "author" : "xcode",
+ "version" : 1
}
-}
\ No newline at end of file
+}
diff --git a/ios/Runner/Assets.xcassets/camera_back.imageset/Contents.json b/ios/Runner/Assets.xcassets/camera_back.imageset/Contents.json
new file mode 100644
index 00000000..ab08ef1a
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/camera_back.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "camera_back.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/camera_back.imageset/camera_back.png b/ios/Runner/Assets.xcassets/camera_back.imageset/camera_back.png
new file mode 100644
index 00000000..c3d63ac3
Binary files /dev/null and b/ios/Runner/Assets.xcassets/camera_back.imageset/camera_back.png differ
diff --git a/ios/Runner/Assets.xcassets/camera_front.imageset/Contents.json b/ios/Runner/Assets.xcassets/camera_front.imageset/Contents.json
new file mode 100644
index 00000000..a0e93123
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/camera_front.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "camera_front.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/camera_front.imageset/camera_front.png b/ios/Runner/Assets.xcassets/camera_front.imageset/camera_front.png
new file mode 100644
index 00000000..332ab0f5
Binary files /dev/null and b/ios/Runner/Assets.xcassets/camera_front.imageset/camera_front.png differ
diff --git a/ios/Runner/Assets.xcassets/end_call.imageset/Contents.json b/ios/Runner/Assets.xcassets/end_call.imageset/Contents.json
new file mode 100644
index 00000000..3762a5af
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/end_call.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "end_call.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/end_call.imageset/end_call.png b/ios/Runner/Assets.xcassets/end_call.imageset/end_call.png
new file mode 100644
index 00000000..52f8e267
Binary files /dev/null and b/ios/Runner/Assets.xcassets/end_call.imageset/end_call.png differ
diff --git a/ios/Runner/Assets.xcassets/expand_video.imageset/Contents.json b/ios/Runner/Assets.xcassets/expand_video.imageset/Contents.json
new file mode 100644
index 00000000..1ecfeae9
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/expand_video.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "expand.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/expand_video.imageset/expand.png b/ios/Runner/Assets.xcassets/expand_video.imageset/expand.png
new file mode 100644
index 00000000..7020dc2e
Binary files /dev/null and b/ios/Runner/Assets.xcassets/expand_video.imageset/expand.png differ
diff --git a/ios/Runner/Assets.xcassets/float_video.imageset/Contents.json b/ios/Runner/Assets.xcassets/float_video.imageset/Contents.json
new file mode 100644
index 00000000..e3a2ec5a
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/float_video.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "Group 8024.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/float_video.imageset/Group 8024.png b/ios/Runner/Assets.xcassets/float_video.imageset/Group 8024.png
new file mode 100644
index 00000000..59f2c9e6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/float_video.imageset/Group 8024.png differ
diff --git a/ios/Runner/Assets.xcassets/hide_video_icon.imageset/Contents.json b/ios/Runner/Assets.xcassets/hide_video_icon.imageset/Contents.json
new file mode 100644
index 00000000..91a3b7b1
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/hide_video_icon.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "hide_video_icon.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/hide_video_icon.imageset/hide_video_icon.png b/ios/Runner/Assets.xcassets/hide_video_icon.imageset/hide_video_icon.png
new file mode 100644
index 00000000..40889f77
Binary files /dev/null and b/ios/Runner/Assets.xcassets/hide_video_icon.imageset/hide_video_icon.png differ
diff --git a/ios/Runner/Assets.xcassets/mic_mute.imageset/Contents.json b/ios/Runner/Assets.xcassets/mic_mute.imageset/Contents.json
new file mode 100644
index 00000000..2a0f4d2e
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/mic_mute.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "mic_mute.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/mic_mute.imageset/mic_mute.png b/ios/Runner/Assets.xcassets/mic_mute.imageset/mic_mute.png
new file mode 100644
index 00000000..266cb6c7
Binary files /dev/null and b/ios/Runner/Assets.xcassets/mic_mute.imageset/mic_mute.png differ
diff --git a/ios/Runner/Assets.xcassets/mic_unmute.imageset/Contents.json b/ios/Runner/Assets.xcassets/mic_unmute.imageset/Contents.json
new file mode 100644
index 00000000..8e85d01a
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/mic_unmute.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "Group 8020.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/mic_unmute.imageset/Group 8020.png b/ios/Runner/Assets.xcassets/mic_unmute.imageset/Group 8020.png
new file mode 100644
index 00000000..ef7617f7
Binary files /dev/null and b/ios/Runner/Assets.xcassets/mic_unmute.imageset/Group 8020.png differ
diff --git a/ios/Runner/Assets.xcassets/video_mute.imageset/Contents.json b/ios/Runner/Assets.xcassets/video_mute.imageset/Contents.json
new file mode 100644
index 00000000..4403d6ee
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/video_mute.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "video_mute.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/video_mute.imageset/video_mute.png b/ios/Runner/Assets.xcassets/video_mute.imageset/video_mute.png
new file mode 100644
index 00000000..d6ccbe7a
Binary files /dev/null and b/ios/Runner/Assets.xcassets/video_mute.imageset/video_mute.png differ
diff --git a/ios/Runner/Assets.xcassets/video_unmute.imageset/Contents.json b/ios/Runner/Assets.xcassets/video_unmute.imageset/Contents.json
new file mode 100644
index 00000000..51724501
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/video_unmute.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "Group 8022.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/video_unmute.imageset/Group 8022.png b/ios/Runner/Assets.xcassets/video_unmute.imageset/Group 8022.png
new file mode 100644
index 00000000..6fcbe750
Binary files /dev/null and b/ios/Runner/Assets.xcassets/video_unmute.imageset/Group 8022.png differ
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
index 73e18f76..2bdee5a2 100755
--- a/ios/Runner/Base.lproj/Main.storyboard
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -1,296 +1,366 @@
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
-
-
+
-
-
-
-
-
-
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Extensions.swift b/ios/Runner/Extensions.swift
new file mode 100644
index 00000000..b79c8deb
--- /dev/null
+++ b/ios/Runner/Extensions.swift
@@ -0,0 +1,27 @@
+//
+// Extensions.swift
+// Runner
+//
+// Created by Zohaib Iqbal Kambrani on 09/06/2021.
+// Copyright © 2021 The Chromium Authors. All rights reserved.
+//
+import AADraggableView
+
+extension AADraggableView{
+ func enable(_ enable:Bool){
+ isEnabled = enable
+ if enable == false{
+ gestureRecognizers?.forEach({ gest in
+ removeGestureRecognizer(gest)
+ })
+ }
+ }
+}
+
+extension UIView{
+ func hidden(_ hidden:Bool, rootView:UIView){
+ UIView.transition(with: rootView, duration: 0.5, options: .transitionCrossDissolve, animations: {
+ self.isHidden = hidden
+ })
+ }
+}
diff --git a/ios/Runner/MainAppViewController.swift b/ios/Runner/MainAppViewController.swift
new file mode 100644
index 00000000..c7b7a18d
--- /dev/null
+++ b/ios/Runner/MainAppViewController.swift
@@ -0,0 +1,190 @@
+//
+// MainAppViewController.swift
+// Runner
+//
+// Created by Zohaib Iqbal Kambrani on 08/06/2021.
+// Copyright © 2021 The Chromium Authors. All rights reserved.
+//
+
+import Foundation
+import AADraggableView
+
+class MainAppViewController: FlutterViewController{
+ var videoCallContainer:AADraggableView!
+ var videoCallViewController:VideoCallViewController!
+ var videoCallFlutterResult:FlutterResult?
+ var vdoCallViewFloatRectConstraint:[NSLayoutConstraint]!
+ var vdoCallViewFullConstraint:[NSLayoutConstraint]!
+ var vdoCallViewFloatCircleConstraint:[NSLayoutConstraint]!
+
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ initFlutterBridge()
+ prepareVideoCallView()
+ }
+
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ }
+
+ var videoCallChannel:FlutterMethodChannel?
+ private func initFlutterBridge(){
+ videoCallChannel = FlutterMethodChannel(name: "Dr.cloudSolution/videoCall", binaryMessenger: binaryMessenger)
+ videoCallChannel?.setMethodCallHandler({
+ (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
+ switch call.method {
+ case "openVideoCall":
+ self.startVideoCall(result: result, call: call)
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+ })
+ }
+
+}
+
+
+// Video Call Functions
+extension MainAppViewController : ICallProtocol{
+
+ func prepareVideoCallView(){
+ videoCallContainer = AADraggableView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height))
+ videoCallContainer.backgroundColor = UIColor.black
+ videoCallContainer.isHidden = true
+ videoCallContainer.clipsToBounds = true
+ videoCallContainer.padding = 20
+ videoCallContainer.reposition = .edgesOnly
+ videoCallContainer.isEnabled = false
+
+ view.addSubview(videoCallContainer)
+ setVideoViewConstrints()
+ NSLayoutConstraint.activate(vdoCallViewFullConstraint)
+ NSLayoutConstraint.deactivate(vdoCallViewFloatRectConstraint)
+
+ ViewEmbedder.embed(
+ withIdentifier: "videoCall", // Storyboard ID
+ parent: self,
+ container: self.videoCallContainer){ vc in
+ self.videoCallViewController = vc as? VideoCallViewController
+
+ }
+ }
+
+ private func showVideo(show:Bool){
+ self.videoCallContainer.hidden(!show, rootView: view)
+ }
+
+ private func startVideoCall(result: @escaping FlutterResult, call:FlutterMethodCall) {
+ videoCallFlutterResult = result
+
+ if let arguments = call.arguments as? NSDictionary{
+ showVideoCallView(true)
+
+ videoCallViewController.onRectFloat = { isRectFloat in
+ self.rectFloatVideoCallView(isRectFloat)
+ }
+
+ videoCallViewController.onCircleFloat = { isCircleFloat in
+ self.circleFloatVideoCallView(isCircleFloat)
+ }
+ videoCallViewController.onCallConnect = {
+ self.videoCallChannel?.invokeMethod("onCallConnected",arguments: nil)
+ }
+ videoCallViewController.onCallDisconnect = {
+ self.showVideoCallView(false)
+ self.videoCallViewController.minimizeVideoState(state: false)
+ self.videoCallChannel?.invokeMethod("onCallDisconnected",arguments: nil)
+ }
+ videoCallViewController.callBack = self
+ videoCallViewController.start(params: VideoCallRequestParameters(dictionary: arguments))
+ }
+
+
+ }
+
+ private func rectFloatVideoCallView(_ value:Bool){
+ videoCallContainer.enable(value)
+
+ UIView.animate(withDuration: 0.5) {
+ if(value){
+ NSLayoutConstraint.deactivate(self.vdoCallViewFullConstraint)
+ NSLayoutConstraint.deactivate(self.vdoCallViewFloatCircleConstraint)
+ NSLayoutConstraint.activate(self.vdoCallViewFloatRectConstraint)
+ }else{
+ NSLayoutConstraint.deactivate(self.vdoCallViewFloatRectConstraint)
+ NSLayoutConstraint.deactivate(self.vdoCallViewFloatCircleConstraint)
+ NSLayoutConstraint.activate(self.vdoCallViewFullConstraint)
+ }
+ self.videoCallContainer.layer.cornerRadius = value ? 10 : 0
+ self.videoCallContainer.layer.borderColor = value ? UIColor.white.cgColor : nil
+ self.videoCallContainer.layer.borderWidth = value ? 2 : 0
+ self.view.layoutIfNeeded()
+ }
+ }
+
+ private func circleFloatVideoCallView(_ value:Bool){
+ videoCallContainer.reposition = value ? .sticky : .edgesOnly
+
+ UIView.animate(withDuration: 0.5) {
+ if(value){
+ NSLayoutConstraint.deactivate(self.vdoCallViewFullConstraint)
+ NSLayoutConstraint.deactivate(self.vdoCallViewFloatRectConstraint)
+ NSLayoutConstraint.activate(self.vdoCallViewFloatCircleConstraint)
+ self.videoCallContainer.layer.cornerRadius = 35
+ }else{
+ NSLayoutConstraint.activate(self.vdoCallViewFloatRectConstraint)
+ NSLayoutConstraint.deactivate(self.vdoCallViewFullConstraint)
+ NSLayoutConstraint.deactivate(self.vdoCallViewFloatCircleConstraint)
+ self.videoCallContainer.layer.cornerRadius = 10
+
+ }
+ self.videoCallContainer.layer.borderColor = value ? UIColor.white.cgColor : nil
+ self.videoCallContainer.layer.borderWidth = value ? 2 : 0
+ self.view.layoutIfNeeded()
+ }
+ }
+
+ private func showVideoCallView(_ value:Bool){
+ self.videoCallContainer.hidden(!value, rootView: view)
+ }
+
+ func sessionDone(res: Any) {
+ videoCallFlutterResult?(res)
+ }
+
+ func sessionNotResponded(res: Any) {
+ videoCallFlutterResult?(res)
+ }
+
+
+ func setVideoViewConstrints(){
+ videoCallContainer.layer.shadowColor = UIColor.black.cgColor
+ videoCallContainer.layer.shadowOffset = CGSize(width: 1, height: 1)
+ videoCallContainer.layer.shadowRadius = 5
+
+ let screen = UIScreen.main.bounds
+
+ videoCallContainer.translatesAutoresizingMaskIntoConstraints = false
+
+ vdoCallViewFullConstraint = [
+ videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor),
+ videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ videoCallContainer.widthAnchor.constraint(equalToConstant: screen.width),
+ videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height)
+ ]
+ vdoCallViewFloatRectConstraint = [
+ videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 40),
+ videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
+ videoCallContainer.widthAnchor.constraint(equalToConstant: screen.width/3),
+ videoCallContainer.heightAnchor.constraint(equalToConstant: screen.height/3.5)
+ ]
+ vdoCallViewFloatCircleConstraint = [
+ videoCallContainer.topAnchor.constraint(equalTo: view.topAnchor, constant: 40),
+ videoCallContainer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
+ videoCallContainer.widthAnchor.constraint(equalToConstant: 70),
+ videoCallContainer.heightAnchor.constraint(equalToConstant: 70)
+ ]
+ }
+
+}
diff --git a/ios/Runner/VCEmbeder.swift b/ios/Runner/VCEmbeder.swift
new file mode 100644
index 00000000..9617a247
--- /dev/null
+++ b/ios/Runner/VCEmbeder.swift
@@ -0,0 +1,78 @@
+//
+// VCEmbeder.swift
+// Runner
+//
+// Created by Zohaib Iqbal Kambrani on 08/06/2021.
+// Copyright © 2021 The Chromium Authors. All rights reserved.
+//
+
+import Foundation
+
+extension UIView {
+ func fill(to parent: UIView, animateDuration:Double = 0.5) {
+ self.topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
+ self.leadingAnchor.constraint(equalTo: parent.leadingAnchor).isActive = true
+ self.bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true
+ self.trailingAnchor.constraint(equalTo: parent.trailingAnchor).isActive = true
+ UIView.animate(withDuration: animateDuration) {
+ parent.layoutIfNeeded()
+ }
+ }
+
+ func fillToParent(animateDuration:Double = 0.5) {
+ if let parent = self.superview{
+ self.topAnchor.constraint(equalTo: parent.topAnchor).isActive = true
+ self.leadingAnchor.constraint(equalTo: parent.leadingAnchor).isActive = true
+ self.bottomAnchor.constraint(equalTo: parent.bottomAnchor).isActive = true
+ self.trailingAnchor.constraint(equalTo: parent.trailingAnchor).isActive = true
+ UIView.animate(withDuration: animateDuration) {
+ parent.layoutIfNeeded()
+ }
+ }
+ }
+
+ func fillInTo(view:UIView) {
+ view.addSubview(self)
+ fillToParent()
+ }
+}
+
+class ViewEmbedder {
+ class func embed(
+ parent:UIViewController,
+ container:UIView,
+ child:UIViewController,
+ previous:UIViewController?){
+
+ if let previous = previous {
+ removeFromParent(vc: previous)
+ }
+ child.willMove(toParent: parent)
+ parent.addChild(child)
+ container.addSubview(child.view)
+ child.didMove(toParent: parent)
+ let w = container.frame.size.width;
+ let h = container.frame.size.height;
+ child.view.frame = CGRect(x: 0, y: 0, width: w, height: h)
+ child.view.backgroundColor = UIColor.black
+
+ child.view.fill(to: container)
+ }
+
+ class func removeFromParent(vc:UIViewController){
+ vc.willMove(toParent: nil)
+ vc.view.removeFromSuperview()
+ vc.removeFromParent()
+ }
+
+ class func embed(withIdentifier id:String, parent:UIViewController, container:UIView, completion:((UIViewController)->Void)? = nil){
+ let vc = parent.storyboard!.instantiateViewController(withIdentifier: id)
+ embed(
+ parent: parent,
+ container: container,
+ child: vc,
+ previous: parent.children.first
+ )
+ completion?(vc)
+ }
+}
diff --git a/ios/Runner/VideoCallRequestParameters.swift b/ios/Runner/VideoCallRequestParameters.swift
new file mode 100644
index 00000000..543d7e8b
--- /dev/null
+++ b/ios/Runner/VideoCallRequestParameters.swift
@@ -0,0 +1,29 @@
+
+
+import Foundation
+
+class VideoCallRequestParameters{
+ var apiKey:String?
+ var sessionId:String?
+ var token:String?
+ var lang:String?
+ var vcId:Int?
+ var tokenId:String?
+ var generalId:String?
+ var doctorId:Int?
+ var baseUrl:String?
+ var patientName:String?
+
+ init(dictionary:NSDictionary){
+ self.apiKey = dictionary["kApiKey"] as? String
+ self.sessionId = dictionary["kSessionId"] as? String
+ self.token = dictionary["kToken"] as? String
+ self.lang = dictionary["appLang"] as? String
+ self.vcId = dictionary["VC_ID"] as? Int
+ self.tokenId = dictionary["TokenID"] as? String
+ self.generalId = dictionary["generalId"] as? String
+ self.doctorId = dictionary["DoctorId"] as? Int
+ self.baseUrl = dictionary["baseUrl"] as? String
+ self.patientName = dictionary["patientName"] as? String
+ }
+}
diff --git a/ios/Runner/VideoViewController.swift b/ios/Runner/VideoCallViewController.swift
similarity index 62%
rename from ios/Runner/VideoViewController.swift
rename to ios/Runner/VideoCallViewController.swift
index 0fb39f7c..80a6fcb8 100644
--- a/ios/Runner/VideoViewController.swift
+++ b/ios/Runner/VideoCallViewController.swift
@@ -9,9 +9,9 @@
import UIKit
import OpenTok
import Alamofire
+import AADraggableView
-
-class ViewController: UIViewController {
+class VideoCallViewController: UIViewController {
var session: OTSession?
var publisher: OTPublisher?
@@ -34,18 +34,188 @@ class ViewController: UIViewController {
var seconds = 55
var isUserConnect : Bool = false
+ var onRectFloat:((Bool)->Void)? = nil
+ var onCircleFloat:((Bool)->Void)? = nil
+ var onCallConnect:(()->Void)? = nil
+ var onCallDisconnect:(()->Void)? = nil
+
+
+ @IBOutlet weak var lblRemoteUsername: UILabel!
+
+ // Bottom Actions
+ @IBOutlet weak var videoMuteBtn: UIButton!
+ @IBOutlet weak var micMuteBtn: UIButton!
+ @IBOutlet weak var camSwitchBtn: UIButton!
+
+ @IBOutlet var minimizeConstraint: [NSLayoutConstraint]!
+ @IBOutlet var maximisedConstraint: [NSLayoutConstraint]!
+
+ @IBOutlet weak var btnMinimize: UIButton!
+ @IBOutlet weak var hideVideoBtn: UIButton!
+ var localVideoDraggable:AADraggableView?
+ @IBOutlet weak var controlButtons: UIView!
+ @IBOutlet weak var remoteVideoMutedIndicator: UIImageView!
+ @IBOutlet weak var localVideoMutedBg: UIView!
+
+ @IBOutlet weak var btnScreenTap: UIButton!
+ @IBOutlet weak var localVideoContainer: UIView!
+ @IBOutlet weak var topBar: UIView!
+ @IBOutlet weak var lblCallDuration: UILabel!
+ @IBOutlet weak var fullVideoView: UIView!
+ @IBOutlet weak var smallVideoView: UIView!{
+ didSet{
+ smallVideoView.layer.borderColor = UIColor.white.cgColor
+ localVideoDraggable = smallVideoView?.superview as? AADraggableView
+ localVideoDraggable?.reposition = .edgesOnly
+ }
+ }
override func viewDidLoad() {
- super.viewDidLoad()
+ super.viewDidLoad()
+ localVideoDraggable?.respectedView = localVideoContainer
+ }
+
+ @objc func click(gesture:UIGestureRecognizer){
+ gesture.view?.removeFromSuperview()
+ }
+
+ @IBAction func btnOnScreenTapped(_ sender: Any) {
+ if(hideVideoBtn.isSelected){
+ circleFloatBtnTapped(hideVideoBtn)
+
+ }else if(btnMinimize.isSelected){
+ btnMinimizeTapped(btnMinimize)
+ }
+ }
+
+ @IBAction func btnSwipeVideoTapped(_ sender: Any) {
+// let smallVdoRender = smallVideoView.subviews.first
+// let fullVdoRender = fullVideoView.subviews.first
+// if let vdo = smallVdoRender{
+// fullVideoView.addSubview(vdo)
+// }
+// if let vdo = fullVdoRender{
+// smallVideoView.addSubview(vdo)
+// }
+//
+// layoutVideoRenderViews()
+ }
+
+ @IBAction func didClickMuteButton(_ sender: UIButton) {
+ sender.isSelected = !sender.isSelected
+ publisher!.publishAudio = !sender.isSelected
+ }
+
+ @IBAction func didClickSpeakerButton(_ sender: UIButton) {
+ sender.isSelected = !sender.isSelected
+ subscriber?.subscribeToAudio = !sender.isSelected
+ }
+
+ @IBAction func didClickVideoMuteButton(_ sender: UIButton) {
+ sender.isSelected = !sender.isSelected
+ if publisher!.publishVideo {
+ publisher!.publishVideo = false
+ } else {
+ publisher!.publishVideo = true
+ }
+ smallVideoView.isHidden = sender.isSelected
+ localVideoMutedBg.isHidden = !sender.isSelected
+
+ }
+
+
+ @IBAction func didClickSwitchCameraButton(_ sender: UIButton) {
+ sender.isSelected = !sender.isSelected
+ if sender.isSelected {
+ publisher!.cameraPosition = AVCaptureDevice.Position.front
+ } else {
+ publisher!.cameraPosition = AVCaptureDevice.Position.back
+ }
+ }
+
+ @IBAction func hangUp(_ sender: UIButton) {
+ callBack?.sessionDone(res:["callResponse":"CallEnd"])
+ sessionDisconnect()
+ }
+
+ @IBAction func circleFloatBtnTapped(_ sender: UIButton) {
+ sender.isSelected = !sender.isSelected
+ onCircleFloat?(sender.isSelected)
+ topBar.isHidden = sender.isSelected
+ controlButtons.isHidden = sender.isSelected
+ smallVideoView.isHidden = sender.isSelected
+ self.publisher?.view?.layoutIfNeeded()
+ }
+
+ @IBAction func btnMinimizeTapped(_ sender: UIButton) {
+ minimizeVideoState(state: !sender.isSelected)
+ btnScreenTap.isHidden = !sender.isSelected
+ }
+
+ func minimizeVideoState(state:Bool){
+ btnMinimize.isSelected = state
+ onRectFloat?(state)
- setupButtons()
- askForMicrophonePermission()
- requestCameraPermissionsIfNeeded()
- hideVideoMuted()
- setupSession()
-
- }
+ NSLayoutConstraint.activate(state ? minimizeConstraint : maximisedConstraint)
+ NSLayoutConstraint.deactivate(state ? maximisedConstraint : minimizeConstraint)
+ localVideoDraggable?.enable(!state)
+
+ lblRemoteUsername.isHidden = state
+ hideVideoBtn.isHidden = !state
+ lblCallDuration.superview?.isHidden = !hideVideoBtn.isHidden
+
+ UIView.animate(withDuration: 0.5) {
+ self.videoMuteBtn.isHidden = state
+ self.micMuteBtn.isHidden = state
+ self.camSwitchBtn.isHidden = state
+ self.layoutVideoRenderViews()
+ }
+ }
+ func layoutVideoRenderViews(){
+ if let publisherVdoSize = publisher?.view?.superview?.bounds.size{
+ publisher?.view?.frame = CGRect(x: 0, y: 0, width: publisherVdoSize.width, height: publisherVdoSize.height)
+ }
+ if let subscriberVdoSize = subscriber?.view?.superview?.bounds.size{
+ subscriber?.view?.frame = CGRect(x: 0, y: 0, width: subscriberVdoSize.width, height: subscriberVdoSize.height)
+ }
+ }
+
+ var durationTimer:Timer?;
+ func startUpdateCallDuration(){
+ var seconds = 0
+ durationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
+ seconds = seconds+1
+ let durationSegments = (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
+ let hours = String(format: "%02d", durationSegments.0)
+ let mins = String(format: "%02d", durationSegments.1)
+ let secs = String(format: "%02d", durationSegments.2)
+ let durationString = "\(mins):\(secs)"
+
+ self.lblCallDuration.text = durationString
+ }
+ }
+
+ func start(params:VideoCallRequestParameters){
+ lblRemoteUsername.text = params.patientName ?? "- - -"
+ btnScreenTap.isHidden = true
+ hideVideoBtn.isHidden = true
+
+ self.kApiKey = params.apiKey ?? ""
+ self.kSessionId = params.sessionId ?? ""
+ self.kToken = params.token ?? ""
+ self.VC_ID = params.vcId ?? 0
+ self.generalid = params.generalId ?? ""
+ self.TokenID = params.tokenId ?? ""
+ self.DoctorId = params.doctorId ?? 0
+ self.baseUrl = params.baseUrl ?? ""
+
+ askForMicrophonePermission()
+ requestCameraPermissionsIfNeeded()
+ hideVideoMuted()
+ setupSession()
+
+ }
private func changeCallStatus(callStatus:Int){
let URL_USER_REGISTER = baseUrl+"LiveCareApi/DoctorApp/ChangeCallStatus"
@@ -107,13 +277,6 @@ class ViewController: UIViewController {
}
}
-
- func setupButtons() {
- perform(#selector(hideControlButtons), with: nil, afterDelay: 3)
- let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(remoteVideoTapped(_:)))
- view.addGestureRecognizer(tapGestureRecognizer)
- view.isUserInteractionEnabled = true
- }
// MARK: -Microphone Camera and Permission Request
func askForMicrophonePermission() {
@@ -137,59 +300,16 @@ class ViewController: UIViewController {
// display a useful message asking the user to grant permissions from within Settings > Privacy > Camera
}
-
- @IBAction func didClickMuteButton(_ sender: UIButton) {
- sender.isSelected = !sender.isSelected
- publisher!.publishAudio = !sender.isSelected
-
- }
-
- @IBAction func didClickSpeakerButton(_ sender: UIButton) {
- sender.isSelected = !sender.isSelected
- subscriber?.subscribeToAudio = !sender.isSelected
- // resetHideButtonsTimer()
- }
-
- @IBAction func didClickVideoMuteButton(_ sender: UIButton) {
- sender.isSelected = !sender.isSelected
- if publisher!.publishVideo {
- publisher!.publishVideo = false
- } else {
- publisher!.publishVideo = true
- }
- localVideo.isHidden = sender.isSelected
- localVideoMutedBg.isHidden = !sender.isSelected
- localVideoMutedIndicator.isHidden = !sender.isSelected
- // resetHideButtonsTimer()
-
- }
-
-
- @IBAction func didClickSwitchCameraButton(_ sender: UIButton) {
- sender.isSelected = !sender.isSelected
- if sender.isSelected {
- publisher!.cameraPosition = AVCaptureDevice.Position.front
- } else {
- publisher!.cameraPosition = AVCaptureDevice.Position.back
- }
- /// resetHideButtonsTimer()
- }
-
- @IBAction func hangUp(_ sender: UIButton) {
- callBack?.sessionDone(res:["callResponse":"CallEnd"])
- sessionDisconnect()
- }
-
-
func sessionDisconnect() {
changeCallStatus(callStatus: 16)
if (session != nil) {
print("disconnecting....")
session!.disconnect(nil)
dismiss(animated: true)
- return
}
dismiss(animated: true)
+ onCallDisconnect?()
+ durationTimer?.invalidate()
}
func requestCameraPermissionsIfNeeded() {
@@ -226,7 +346,6 @@ class ViewController: UIViewController {
func hideVideoMuted() {
remoteVideoMutedIndicator.isHidden = true
localVideoMutedBg.isHidden = true
- localVideoMutedIndicator.isHidden = true
}
func setupSession() {
@@ -267,19 +386,6 @@ class ViewController: UIViewController {
- @IBOutlet weak var localVideo: UIView!
-
- @IBOutlet weak var remoteVideo: UIView!
-
- @IBOutlet weak var controlButtons: UIView!
-
- @IBOutlet weak var remoteVideoMutedIndicator: UIImageView!
-
- @IBOutlet weak var localVideoMutedBg: UIImageView!
-
-
- @IBOutlet weak var localVideoMutedIndicator: UIImageView!
-
@objc func updateTimer(){
seconds -= 1 //This will decrement(count down)the seconds.
print(seconds)
@@ -293,17 +399,11 @@ class ViewController: UIViewController {
}
-extension ViewController: OTSessionDelegate {
+extension VideoCallViewController: OTSessionDelegate {
func sessionDidConnect(_ session: OTSession) {
- print("The client connected to the OpenTok session.")
-
-
-
-
- timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(ViewController.updateTimer)), userInfo: nil, repeats: true)
-
-
+ print("The client connected to the OpenTok session.")
+ timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(VideoCallViewController.updateTimer)), userInfo: nil, repeats: true)
setupPublisher()
}
@@ -318,10 +418,12 @@ extension ViewController: OTSessionDelegate {
if error != nil {
showAlert(error?.localizedDescription)
}
-
- publisher?.view!.frame = CGRect(x: localVideo.bounds.origin.x, y: localVideo.bounds.origin.y, width: localVideo.bounds.size.width, height: localVideo.bounds.size.height)
-
- localVideo.addSubview((publisher?.view)!)
+
+ publisher?.view?.tag = 11
+ publisher?.view?.layer.cornerRadius = 5
+ publisher?.view?.clipsToBounds = true
+ smallVideoView.addSubview((publisher?.view)!)
+ layoutVideoRenderViews()
}
func sessionDidDisconnect(_ session: OTSession) {
@@ -346,27 +448,27 @@ extension ViewController: OTSessionDelegate {
func session(_ session: OTSession, streamCreated stream: OTStream) {
- subscriber = OTSubscriber(stream: stream, delegate: self)
- guard let subscriber = subscriber else {
- return
- }
+ subscriber = OTSubscriber(stream: stream, delegate: self)
+ guard let subscriber = subscriber else {
+ return
+ }
- var error: OTError?
- session.subscribe(subscriber, error: &error)
- guard error == nil else {
- print(error!)
- return
- }
+ var error: OTError?
+ session.subscribe(subscriber, error: &error)
+ guard error == nil else {
+ print(error!)
+ return
+ }
- guard let subscriberView = subscriber.view else {
- return
- }
- subscriberView.frame = UIScreen.main.bounds
- view.insertSubview(subscriberView, at: 0)
+ guard let subscriberView = subscriber.view else {
+ return
+ }
+ subscriberView.tag = 22
+ fullVideoView.addSubview(subscriberView)
+ layoutVideoRenderViews()
-// if nil == subscriber {
-// setupSubscribe(stream)
-// }
+ startUpdateCallDuration()
+ onCallConnect?()
}
func setupSubscribe(_ stream: OTStream?) {
@@ -408,19 +510,13 @@ extension ViewController: OTSessionDelegate {
}
-extension ViewController: OTPublisherDelegate {
+extension VideoCallViewController: OTPublisherDelegate {
func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) {
print("The publisher failed: \(error)")
}
- @objc func remoteVideoTapped(_ recognizer: UITapGestureRecognizer?) {
- if controlButtons.isHidden {
- controlButtons.isHidden = false
- perform(#selector(hideControlButtons), with: nil, afterDelay: 3)
- }
- }
}
-extension ViewController: OTSubscriberDelegate {
+extension VideoCallViewController: OTSubscriberDelegate {
public func subscriberDidConnect(toStream subscriber: OTSubscriberKit) {
print("The subscriber did connect to the stream.")
}
@@ -428,8 +524,5 @@ extension ViewController: OTSubscriberDelegate {
public func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) {
print("The subscriber failed to connect to the stream.")
}
- @objc func hideControlButtons() {
- controlButtons.isHidden = true
- }
}
diff --git a/lib/client/base_app_client.dart b/lib/client/base_app_client.dart
index b1785b0e..65c8341c 100644
--- a/lib/client/base_app_client.dart
+++ b/lib/client/base_app_client.dart
@@ -113,7 +113,7 @@ class BaseAppClient {
parsed['AndroidLink'], parsed['IOSLink']);
}
- if (!parsed['IsAuthenticated']) {
+ if (parsed['IsAuthenticated'] != null && !parsed['IsAuthenticated']) {
if (body['OTP_SendType'] != null) {
onFailure(getError(parsed), statusCode);
} else if (!isAllowAny) {
diff --git a/lib/core/model/patient_muse/PatientSearchRequestModel.dart b/lib/core/model/patient_muse/PatientSearchRequestModel.dart
index 2918a393..c2658c9f 100644
--- a/lib/core/model/patient_muse/PatientSearchRequestModel.dart
+++ b/lib/core/model/patient_muse/PatientSearchRequestModel.dart
@@ -12,7 +12,7 @@ class PatientSearchRequestModel {
String mobileNo;
String identificationNo;
int nursingStationID;
- int clinicID;
+ int clinicID=0;
PatientSearchRequestModel(
{this.doctorID = 0,
@@ -63,6 +63,7 @@ class PatientSearchRequestModel {
data['IdentificationNo'] = this.identificationNo;
data['NursingStationID'] = this.nursingStationID;
data['ClinicID'] = this.clinicID;
+ data['ProjectID'] = 0;
return data;
}
}
diff --git a/lib/core/service/NavigationService.dart b/lib/core/service/NavigationService.dart
index 426ace4d..26191ffc 100644
--- a/lib/core/service/NavigationService.dart
+++ b/lib/core/service/NavigationService.dart
@@ -3,9 +3,15 @@ import 'package:flutter/material.dart';
class NavigationService {
final GlobalKey navigatorKey =
new GlobalKey();
- Future navigateTo(String routeName) {
- return navigatorKey.currentState.pushNamed(routeName);
+ Future navigateTo(String routeName,{Object arguments}) {
+ return navigatorKey.currentState.pushNamed(routeName,arguments: arguments);
}
+
+ Future pushReplacementNamed(String routeName,{Object arguments}) {
+ return navigatorKey.currentState.pushReplacementNamed(routeName,arguments: arguments);
+ }
+
+
Future pushNamedAndRemoveUntil(String routeName) {
return navigatorKey.currentState.pushNamedAndRemoveUntil(routeName,(asd)=>false);
}
diff --git a/lib/core/service/VideoCallService.dart b/lib/core/service/VideoCallService.dart
new file mode 100644
index 00000000..f72544a0
--- /dev/null
+++ b/lib/core/service/VideoCallService.dart
@@ -0,0 +1,89 @@
+import 'package:doctor_app_flutter/config/config.dart';
+import 'package:doctor_app_flutter/config/shared_pref_kay.dart';
+import 'package:doctor_app_flutter/core/service/base/base_service.dart';
+import 'package:doctor_app_flutter/core/service/patient/LiveCarePatientServices.dart';
+import 'package:doctor_app_flutter/models/doctor/doctor_profile_model.dart';
+import 'package:doctor_app_flutter/models/livecare/end_call_req.dart';
+import 'package:doctor_app_flutter/models/livecare/session_status_model.dart';
+import 'package:doctor_app_flutter/models/livecare/start_call_res.dart';
+import 'package:doctor_app_flutter/models/patient/patiant_info_model.dart';
+import 'package:doctor_app_flutter/util/VideoChannel.dart';
+import 'package:doctor_app_flutter/util/dr_app_toast_msg.dart';
+import 'package:doctor_app_flutter/widgets/shared/loader/gif_loader_dialog_utils.dart';
+import 'package:flutter/cupertino.dart';
+
+import '../../locator.dart';
+import '../../routes.dart';
+import 'NavigationService.dart';
+
+class VideoCallService extends BaseService{
+
+ StartCallRes startCallRes;
+ PatiantInformtion patient;
+ LiveCarePatientServices _liveCarePatientServices = locator();
+
+ openVideo(StartCallRes startModel,PatiantInformtion patientModel,VoidCallback onCallConnected, VoidCallback onCallDisconnected)async{
+ this.startCallRes = startModel;
+ this.patient = patientModel;
+ DoctorProfileModel doctorProfile = await getDoctorProfile(isGetProfile: true);
+ await VideoChannel.openVideoCallScreen(
+ kToken: startCallRes.openTokenID,//"T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",
+ kSessionId:startCallRes.openSessionID,//1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg
+ kApiKey: '46209962',//'47247954'
+ vcId: patient.vcId,
+ patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"),
+ tokenID: await sharedPref.getString(TOKEN),
+ generalId: GENERAL_ID,
+ doctorId: doctorProfile.doctorID,
+ onFailure: (String error) {
+ DrAppToastMsg.showErrorToast(error);
+ },onCallConnected: onCallConnected,
+ onCallEnd: () {
+ WidgetsBinding.instance.addPostFrameCallback((_) async {
+ GifLoaderDialogUtils.showMyDialog(locator().navigatorKey.currentContext);
+ endCall(patient.vcId, false,).then((value) {
+ GifLoaderDialogUtils.hideDialog(locator().navigatorKey.currentContext);
+ if (hasError) {
+ DrAppToastMsg.showErrorToast(error);
+ }else
+ locator().navigateTo(PATIENTS_END_Call,arguments: {
+ "patient": patient,
+ });
+
+ });
+ });
+ },
+ onCallNotRespond: (SessionStatusModel sessionStatusModel) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ GifLoaderDialogUtils.showMyDialog(locator().navigatorKey.currentContext);
+ endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) {
+ GifLoaderDialogUtils.hideDialog(locator().navigatorKey.currentContext);
+ if (hasError) {
+ DrAppToastMsg.showErrorToast(error);
+ } else {
+ locator().navigateTo(PATIENTS_END_Call,arguments: {
+ "patient": patient,
+ });
+ }
+
+ });
+
+ });
+ });
+
+ }
+ Future endCall(int vCID, bool isPatient) async {
+ hasError = false;
+ await getDoctorProfile(isGetProfile: true);
+ EndCallReq endCallReq = new EndCallReq();
+ endCallReq.doctorId = doctorProfile.doctorID;
+ endCallReq.generalid = 'Cs2020@2016\$2958';
+ endCallReq.vCID = vCID;
+ endCallReq.isDestroy = isPatient;
+ await _liveCarePatientServices.endCall(endCallReq);
+ if (_liveCarePatientServices.hasError) {
+ error = _liveCarePatientServices.error;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/lib/core/viewModel/authentication_view_model.dart b/lib/core/viewModel/authentication_view_model.dart
index 58df9331..9da1933a 100644
--- a/lib/core/viewModel/authentication_view_model.dart
+++ b/lib/core/viewModel/authentication_view_model.dart
@@ -254,8 +254,8 @@ class AuthenticationViewModel extends BaseViewModel {
/// add token to shared preferences in case of send activation code is success
setDataAfterSendActivationSuccess(SendActivationCodeForDoctorAppResponseModel sendActivationCodeForDoctorAppResponseModel) {
- print("VerificationCode : " +
- sendActivationCodeForDoctorAppResponseModel.verificationCode);
+ print("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode);
+ // DrAppToastMsg.showSuccesToast("VerificationCode : " + sendActivationCodeForDoctorAppResponseModel.verificationCode);
sharedPref.setString(VIDA_AUTH_TOKEN_ID,
sendActivationCodeForDoctorAppResponseModel.vidaAuthTokenID);
sharedPref.setString(VIDA_REFRESH_TOKEN_ID,
diff --git a/lib/core/viewModel/patient-referral-viewmodel.dart b/lib/core/viewModel/patient-referral-viewmodel.dart
index 9e852978..0351db77 100644
--- a/lib/core/viewModel/patient-referral-viewmodel.dart
+++ b/lib/core/viewModel/patient-referral-viewmodel.dart
@@ -240,7 +240,7 @@ class PatientReferralViewModel extends BaseViewModel {
patientID: patient.patientId,
roomID: patient.roomId,
referralClinic: clinicID,
- admissionNo: patient.appointmentNo,
+ admissionNo: int.parse(patient.admissionNo),
referralDoctor: doctorID,
patientTypeID: patient.patientType,
referringDoctorRemarks: remarks,
diff --git a/lib/core/viewModel/scan_qr_view_model.dart b/lib/core/viewModel/scan_qr_view_model.dart
index ff934f79..86e975e3 100644
--- a/lib/core/viewModel/scan_qr_view_model.dart
+++ b/lib/core/viewModel/scan_qr_view_model.dart
@@ -13,7 +13,7 @@ class ScanQrViewModel extends BaseViewModel {
await getDoctorProfile();
setState(ViewState.Busy);
- await _scanQrService.getInPatient(requestModel, false);
+ await _scanQrService.getInPatient(requestModel, true);
if (_scanQrService.hasError) {
error = _scanQrService.error;
diff --git a/lib/landing_page.dart b/lib/landing_page.dart
index cd7a8171..5bce4f71 100644
--- a/lib/landing_page.dart
+++ b/lib/landing_page.dart
@@ -4,6 +4,7 @@ import 'package:doctor_app_flutter/screens/home/home_screen.dart';
import 'package:doctor_app_flutter/screens/qr_reader/QR_reader_screen.dart';
import 'package:doctor_app_flutter/util/translations_delegate_base.dart';
import 'package:doctor_app_flutter/widgets/shared/app_drawer_widget.dart';
+import 'package:doctor_app_flutter/widgets/shared/app_scaffold_widget.dart';
import 'package:doctor_app_flutter/widgets/shared/bottom_nav_bar.dart';
import 'package:doctor_app_flutter/widgets/shared/user-guid/app_showcase_widget.dart';
import 'package:flutter/cupertino.dart';
@@ -33,7 +34,7 @@ class _LandingPageState extends State {
@override
Widget build(BuildContext context) {
- return Scaffold(
+ return AppScaffold(
appBar: currentTab != 0
? AppBar(
elevation: 0,
diff --git a/lib/locator.dart b/lib/locator.dart
index 1e4bcb4c..d74fad8b 100644
--- a/lib/locator.dart
+++ b/lib/locator.dart
@@ -11,6 +11,7 @@ import 'package:doctor_app_flutter/core/viewModel/sick_leave_view_model.dart';
import 'package:get_it/get_it.dart';
import 'core/service/NavigationService.dart';
+import 'core/service/VideoCallService.dart';
import 'core/service/home/dasboard_service.dart';
import 'core/service/home/doctor_reply_service.dart';
import 'core/service/home/schedule_service.dart';
@@ -96,6 +97,7 @@ void setupLocator() {
locator.registerLazySingleton(() => NavigationService());
locator.registerLazySingleton(() => ScanQrService());
locator.registerLazySingleton(() => SpecialClinicsService());
+ locator.registerLazySingleton(() => VideoCallService());
/// View Model
locator.registerFactory(() => DoctorReplayViewModel());
diff --git a/lib/models/patient/patiant_info_model.dart b/lib/models/patient/patiant_info_model.dart
index 71d46dc9..29dc6198 100644
--- a/lib/models/patient/patiant_info_model.dart
+++ b/lib/models/patient/patiant_info_model.dart
@@ -224,10 +224,13 @@ class PatiantInformtion {
isSigned: json['isSigned'],
medicationOrders: json['medicationOrders'],
nationality: json['nationality'] ?? json['NationalityNameN'],
- patientMRN: json['patientMRN'] ?? json['PatientMRN']?? (
- json["PatientID"] != null ?
- int.parse(json["PatientID"].toString())
- : int.parse(json["patientID"].toString())),
+ patientMRN: json['patientMRN'] ??
+ json['PatientMRN'] ??
+ (json["PatientID"] != null
+ ? int?.parse(json["PatientID"].toString())
+ : json["patientID"] != null ? int?.parse(
+ json["patientID"].toString()) : json["patientId"] != null ? int
+ ?.parse(json["patientId"].toString()) : ''),
visitType: json['visitType'] ?? json['visitType'] ?? json['visitType'],
nationalityFlagURL:
json['NationalityFlagURL'] ?? json['NationalityFlagURL'],
diff --git a/lib/routes.dart b/lib/routes.dart
index 33aa5636..0826c76e 100644
--- a/lib/routes.dart
+++ b/lib/routes.dart
@@ -1,4 +1,5 @@
import 'package:doctor_app_flutter/root_page.dart';
+import 'package:doctor_app_flutter/screens/live_care/end_call_screen.dart';
import 'package:doctor_app_flutter/screens/medical-file/health_summary_page.dart';
import 'package:doctor_app_flutter/screens/patients/ECGPage.dart';
import 'package:doctor_app_flutter/screens/patients/insurance_approval_screen_patient.dart';
@@ -37,6 +38,7 @@ const String LOGIN = 'login';
const String VERIFICATION_METHODS = 'verification-methods';
const String PATIENTS = 'patients/patients';
const String PATIENTS_PROFILE = 'patients/patients-profile';
+const String PATIENTS_END_Call = 'patients/patients-profile/endCall';
const String IN_PATIENTS_PROFILE = 'inpatients/patients-profile';
const String LAB_RESULT = 'patients/lab_result';
const String HEALTH_SUMMARY = 'patients/health-summary';
@@ -88,6 +90,7 @@ var routes = {
PATIENT_MEDICAL_REPORT: (_) => MedicalReportPage(),
PATIENT_MEDICAL_REPORT_INSERT: (_) => AddVerifyMedicalReport(),
PATIENT_MEDICAL_REPORT_DETAIL: (_) => MedicalReportDetailPage(),
+ PATIENTS_END_Call: (_) => EndCallScreen(),
CREATE_EPISODE: (_) => UpdateSoapIndex(
isUpdate: true,
),
diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart
index aaf4d9b9..d448083f 100644
--- a/lib/screens/home/home_screen.dart
+++ b/lib/screens/home/home_screen.dart
@@ -69,7 +69,7 @@ class _HomeScreenState extends State {
await model.getDashboard();
await model.getDoctorProfile(isGetProfile: true);
await model.checkDoctorHasLiveCare();
- await model.getSpecialClinicalCareList();
+ // await model.getSpecialClinicalCareList();
},
builder: (_, model, w) => AppScaffold(
baseViewModel: model,
diff --git a/lib/screens/live_care/end_call_screen.dart b/lib/screens/live_care/end_call_screen.dart
index 76e87a87..d3db12b6 100644
--- a/lib/screens/live_care/end_call_screen.dart
+++ b/lib/screens/live_care/end_call_screen.dart
@@ -25,7 +25,7 @@ import 'package:hexcolor/hexcolor.dart';
class EndCallScreen extends StatefulWidget {
final PatiantInformtion patient;
- const EndCallScreen({Key key, this.patient}) : super(key: key);
+ const EndCallScreen({Key key, this.patient,}) : super(key: key);
@override
_EndCallScreenState createState() => _EndCallScreenState();
@@ -33,7 +33,7 @@ class EndCallScreen extends StatefulWidget {
class _EndCallScreenState extends State {
bool isInpatient = false;
-
+ PatiantInformtion patient;
bool isDischargedPatient = false;
bool isSearchAndOut = false;
String patientType;
@@ -42,6 +42,20 @@ class _EndCallScreenState extends State {
String to;
LiveCarePatientViewModel liveCareModel;
+ @override
+ void initState() {
+ super.initState();
+ if(widget.patient!=null)
+ patient = widget.patient;
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ final routeArgs = ModalRoute.of(context).settings.arguments as Map;
+ if(routeArgs.containsKey('patient'))
+ patient = routeArgs['patient'];
+ }
@override
Widget build(BuildContext context) {
@@ -53,7 +67,7 @@ class _EndCallScreenState extends State {
onTap: () async {
GifLoaderDialogUtils.showMyDialog(context);
await liveCareModel
- .startCall(isReCall: false, vCID: widget.patient.vcId)
+ .startCall(isReCall: false, vCID: patient.vcId)
.then((value) async {
await liveCareModel.getDoctorProfile();
GifLoaderDialogUtils.hideDialog(context);
@@ -64,8 +78,8 @@ class _EndCallScreenState extends State {
kToken: liveCareModel.startCallRes.openTokenID,
kSessionId: liveCareModel.startCallRes.openSessionID,
kApiKey: '46209962',
- vcId: widget.patient.vcId,
- patientName: widget.patient.fullName ?? (widget.patient.firstName != null ? "${widget.patient.firstName} ${widget.patient.lastName}" : "-"),
+ vcId: patient.vcId,
+ patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"),
tokenID: await liveCareModel.getToken(),
generalId: GENERAL_ID,
doctorId: liveCareModel.doctorProfile.doctorID,
@@ -76,7 +90,7 @@ class _EndCallScreenState extends State {
GifLoaderDialogUtils.showMyDialog(context);
GifLoaderDialogUtils.showMyDialog(context);
await liveCareModel.endCall(
- widget.patient.vcId,
+ patient.vcId,
false,
);
GifLoaderDialogUtils.hideDialog(context);
@@ -89,7 +103,7 @@ class _EndCallScreenState extends State {
GifLoaderDialogUtils.showMyDialog(context);
GifLoaderDialogUtils.showMyDialog(context);
await liveCareModel.endCall(
- widget.patient.vcId,
+ patient.vcId,
sessionStatusModel.sessionStatus == 3,
);
GifLoaderDialogUtils.hideDialog(context);
@@ -116,14 +130,14 @@ class _EndCallScreenState extends State {
() async {
Navigator.of(context).pop();
GifLoaderDialogUtils.showMyDialog(context);
- await liveCareModel.getAlternativeServices(widget.patient.vcId);
+ await liveCareModel.getAlternativeServices(patient.vcId);
GifLoaderDialogUtils.hideDialog(context);
if (liveCareModel.state == ViewState.ErrorLocal) {
DrAppToastMsg.showErrorToast(liveCareModel.error);
} else {
showAlternativesDialog(context, liveCareModel, (bool isConfirmed) async {
GifLoaderDialogUtils.showMyDialog(context);
- await liveCareModel.endCallWithCharge(widget.patient.vcId, isConfirmed);
+ await liveCareModel.endCallWithCharge(patient.vcId, isConfirmed);
GifLoaderDialogUtils.hideDialog(context);
if (liveCareModel.state == ViewState.ErrorLocal) {
DrAppToastMsg.showErrorToast(liveCareModel.error);
@@ -147,7 +161,7 @@ class _EndCallScreenState extends State {
() async {
Navigator.of(context).pop();
GifLoaderDialogUtils.showMyDialog(context);
- await liveCareModel.sendSMSInstruction(widget.patient.vcId);
+ await liveCareModel.sendSMSInstruction(patient.vcId);
GifLoaderDialogUtils.hideDialog(context);
if (liveCareModel.state == ViewState.ErrorLocal) {
DrAppToastMsg.showErrorToast(liveCareModel.error);
@@ -169,7 +183,7 @@ class _EndCallScreenState extends State {
context,
MaterialPageRoute(
builder: (BuildContext context) =>
- LivaCareTransferToAdmin(patient: widget.patient)));
+ LivaCareTransferToAdmin(patient: patient)));
},
isInPatient: isInpatient,
isDartIcon: true,
@@ -186,10 +200,14 @@ class _EndCallScreenState extends State {
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
isShowAppBar: true,
appBar: PatientProfileAppBar(
- widget.patient,
+ patient,
+ onPressed: (){
+ Navigator.pop(context);
+
+ },
isInpatient: isInpatient,
- height: (widget.patient.patientStatusType != null &&
- widget.patient.patientStatusType == 43)
+ height: (patient.patientStatusType != null &&
+ patient.patientStatusType == 43)
? 210
: isDischargedPatient
? 240
@@ -232,7 +250,7 @@ class _EndCallScreenState extends State {
staggeredTileBuilder: (int index) => StaggeredTile.fit(1),
itemBuilder: (BuildContext context, int index) =>
PatientProfileButton(
- patient: widget.patient,
+ patient: patient,
patientType: patientType,
arrivalType: arrivalType,
from: from,
@@ -331,6 +349,7 @@ class _EndCallScreenState extends State {
),
AppButton(
onPressed: () {
+ Navigator.of(context).pop();
Navigator.of(context).pop();
okFunction(false);
},
diff --git a/lib/screens/patients/PatientsInPatientScreen.dart b/lib/screens/patients/PatientsInPatientScreen.dart
index 4971b636..ae0fbb59 100644
--- a/lib/screens/patients/PatientsInPatientScreen.dart
+++ b/lib/screens/patients/PatientsInPatientScreen.dart
@@ -65,13 +65,12 @@ class _PatientInPatientScreenState extends State
return BaseView(
onModelReady: (model) async {
model.clearPatientList();
- if (widget.specialClinic != null) {
- await model
- .getSpecialClinicalCareMappingList(widget.specialClinic.clinicID);
- requestModel.nursingStationID =
- model.specialClinicalCareMappingList[0].nursingStationID;
- requestModel.clinicID = 0;
- }
+ // if (widget.specialClinic != null) {
+ // await model.getSpecialClinicalCareMappingList(widget.specialClinic.clinicID);
+ // requestModel.nursingStationID =
+ // model.specialClinicalCareMappingList[0].nursingStationID;
+ // requestModel.clinicID = 0;
+ // }
model.getInPatientList(requestModel);
},
builder: (_, model, w) =>
diff --git a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart
index 4e98aa22..36388e87 100644
--- a/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart
+++ b/lib/screens/patients/profile/profile_screen/patient_profile_screen.dart
@@ -1,5 +1,8 @@
+import 'dart:async';
+
import 'package:doctor_app_flutter/config/config.dart';
import 'package:doctor_app_flutter/core/enum/viewstate.dart';
+import 'package:doctor_app_flutter/core/service/VideoCallService.dart';
import 'package:doctor_app_flutter/core/viewModel/LiveCarePatientViewModel.dart';
import 'package:doctor_app_flutter/core/viewModel/SOAP_view_model.dart';
import 'package:doctor_app_flutter/models/SOAP/PostEpisodeReqModel.dart';
@@ -10,17 +13,21 @@ import 'package:doctor_app_flutter/screens/live_care/end_call_screen.dart';
import 'package:doctor_app_flutter/screens/patients/profile/profile_screen/profile_gird_for_InPatient.dart';
import 'package:doctor_app_flutter/screens/patients/profile/profile_screen/profile_gird_for_other.dart';
import 'package:doctor_app_flutter/screens/patients/profile/profile_screen/profile_gird_for_search.dart';
+import 'package:doctor_app_flutter/util/NotificationPermissionUtils.dart';
import 'package:doctor_app_flutter/util/VideoChannel.dart';
import 'package:doctor_app_flutter/util/dr_app_toast_msg.dart';
import 'package:doctor_app_flutter/util/helpers.dart';
import 'package:doctor_app_flutter/util/translations_delegate_base.dart';
import 'package:doctor_app_flutter/widgets/patients/profile/patient-profile-app-bar.dart';
+import 'package:doctor_app_flutter/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart';
import 'package:doctor_app_flutter/widgets/shared/app_scaffold_widget.dart';
import 'package:doctor_app_flutter/widgets/shared/buttons/app_buttons_widget.dart';
import 'package:doctor_app_flutter/widgets/shared/loader/gif_loader_dialog_utils.dart';
import 'package:flutter/material.dart';
import 'package:hexcolor/hexcolor.dart';
+import 'package:quiver/async.dart';
+import '../../../../locator.dart';
import '../../../../routes.dart';
class PatientProfileScreen extends StatefulWidget {
@@ -46,10 +53,15 @@ class _PatientProfileScreenState extends State
TabController _tabController;
int index = 0;
int _activeTab = 0;
+
+ StreamController videoCallDurationStreamController;
+ Stream videoCallDurationStream = (() async*{})();
@override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
+ videoCallDurationStreamController = StreamController();
+ videoCallDurationStream = videoCallDurationStreamController.stream;
}
@override
@@ -82,12 +94,32 @@ class _PatientProfileScreenState extends State
if(routeArgs.containsKey("isFromLiveCare")) {
isFromLiveCare = routeArgs['isFromLiveCare'];
}
+ if(routeArgs.containsKey("isCallFinished")) {
+ isCallFinished = routeArgs['isCallFinished'];
+ }
if (isInpatient)
_activeTab = 0;
else
_activeTab = 1;
}
+ StreamSubscription callTimer;
+ callConnected(){
+ callTimer = CountdownTimer(Duration(minutes: 90), Duration(seconds: 1)).listen(null)
+ ..onDone(() {
+ callTimer.cancel();
+ })
+ ..onData((data) {
+ var t = Helpers.timeFrom(duration: data.elapsed);
+ videoCallDurationStreamController.sink.add(t);
+ });
+ }
+
+ callDisconnected(){
+ callTimer.cancel();
+ videoCallDurationStreamController.sink.add(null);
+ }
+
@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
@@ -102,17 +134,18 @@ class _PatientProfileScreenState extends State
children: [
Column(
children: [
- PatientProfileAppBar(
- patient,
- isFromLiveCare: isFromLiveCare,
- isInpatient: isInpatient,
- height: (patient.patientStatusType != null &&
- patient.patientStatusType == 43)
- ? 220
- : isDischargedPatient
- ? 240
- : 0,
- isDischargedPatient: isDischargedPatient),
+ PatientProfileHeaderNewDesignAppBar(
+ patient, arrivalType ?? '0', patientType,
+ videoCallDurationStream: videoCallDurationStream,
+ isInpatient: isInpatient,
+ isFromLiveCare: isFromLiveCare,
+ height: (patient.patientStatusType != null &&
+ patient.patientStatusType == 43)
+ ? 210
+ : isDischargedPatient
+ ? 240
+ : 0,
+ isDischargedPatient: isDischargedPatient),
Container(
height: !isSearchAndOut
? isDischargedPatient
@@ -307,65 +340,29 @@ class _PatientProfileScreenState extends State
if(isCallFinished) {
Navigator.push(context, MaterialPageRoute(
- builder: (BuildContext context) =>
- EndCallScreen(patient:patient)));
+ builder: (BuildContext context) => EndCallScreen(patient:patient)));
} else {
GifLoaderDialogUtils.showMyDialog(context);
- await model.startCall( isReCall : false, vCID: patient.vcId);
+ await model.startCall( isReCall : false, vCID: patient.vcId);
- if(model.state == ViewState.ErrorLocal) {
- GifLoaderDialogUtils.hideDialog(context);
- Helpers.showErrorToast(model.error);
- } else {
- await model.getDoctorProfile();
- patient.appointmentNo = model.startCallRes.appointmentNo;
- patient.episodeNo = 0;
+ if(model.state == ViewState.ErrorLocal) {
+ GifLoaderDialogUtils.hideDialog(context);
+ Helpers.showErrorToast(model.error);
+ } else {
+ await model.getDoctorProfile();
+ patient.appointmentNo = model.startCallRes.appointmentNo;
+ patient.episodeNo = 0;
- GifLoaderDialogUtils.hideDialog(context);
- await VideoChannel.openVideoCallScreen(
- kToken: model.startCallRes.openTokenID,
- kSessionId: model.startCallRes.openSessionID,
- kApiKey: '46209962',
- vcId: patient.vcId,
- patientName: patient.fullName ?? (patient.firstName != null ? "${patient.firstName} ${patient.lastName}" : "-"),
- tokenID: await model.getToken(),
- generalId: GENERAL_ID,
- doctorId: model.doctorProfile.doctorID,
- onFailure: (String error) {
- DrAppToastMsg.showErrorToast(error);
- },
- onCallEnd: () {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- GifLoaderDialogUtils.showMyDialog(context);
- model.endCall(patient.vcId, false,).then((value) {
- GifLoaderDialogUtils.hideDialog(context);
- if (model.state == ViewState.ErrorLocal) {
- DrAppToastMsg.showErrorToast(model.error);
- }
- setState(() {
- isCallFinished = true;
- });
- });
- });
- },
- onCallNotRespond: (SessionStatusModel sessionStatusModel) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- GifLoaderDialogUtils.showMyDialog(context);
- model.endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) {
- GifLoaderDialogUtils.hideDialog(context);
- if (model.state == ViewState.ErrorLocal) {
- DrAppToastMsg.showErrorToast(model.error);
- }
- setState(() {
- isCallFinished = true;
- });
- });
+ GifLoaderDialogUtils.hideDialog(context);
+ AppPermissionsUtils.requestVideoCallPermission(context: context,onTapGrant: (){
+ locator().openVideo(model.startCallRes, patient, callConnected, callDisconnected);
+ });
- });
- });
- }
+
+ }
}
+
},
),
),
diff --git a/lib/screens/procedures/ProcedureCard.dart b/lib/screens/procedures/ProcedureCard.dart
index d998ca58..31611b67 100644
--- a/lib/screens/procedures/ProcedureCard.dart
+++ b/lib/screens/procedures/ProcedureCard.dart
@@ -18,7 +18,7 @@ class ProcedureCard extends StatelessWidget {
final int categoryID;
final PatiantInformtion patient;
final int doctorID;
-
+ final bool isInpatient;
const ProcedureCard({
Key key,
this.onTap,
@@ -26,7 +26,7 @@ class ProcedureCard extends StatelessWidget {
this.categoryID,
this.categoryName,
this.patient,
- this.doctorID,
+ this.doctorID, this.isInpatient = false,
}) : super(key: key);
@override
@@ -132,36 +132,6 @@ class ProcedureCard extends StatelessWidget {
),
],
),
- // Row(
- // children: [
- // AppText(
- // TranslationBase.of(context).doctorName + ": ",
- // //color: Colors.grey,
- // fontSize: 12,
- // color: Colors.grey,
- // ),
- // AppText(
- // entityList.doctorName.toString(),
- // fontSize: 12,
- // bold: true,
- // ),
- // ],
- // ),
- // Row(
- // children: [
- // AppText(
- // TranslationBase.of(context).clinic + ": ",
- // //color: Colors.grey,
- // fontSize: 12,
- // color: Colors.grey,
- // ),
- // AppText(
- // entityList.clinicDescription ?? "",
- // bold: true,
- // fontSize: 12,
- // ),
- // ],
- // ),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -222,27 +192,6 @@ class ProcedureCard extends StatelessWidget {
),
],
),
- /*Container(
- alignment: Alignment.centerRight,
- child: InkWell(
- onTap: () {
- Navigator.push(
- context,
- FadePage(
- page: FlowChartPage(
- filterName: entityList.procedureName,
- patient: patient,
- ),
- ),
- );
- },
- child: AppText(
- TranslationBase.of(context).showMoreBtn,
- textDecoration: TextDecoration.underline,
- color: Colors.blue,
- ),
- ),
- ),*/
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
@@ -254,9 +203,7 @@ class ProcedureCard extends StatelessWidget {
fontSize: 12,
),
),
- if ((entityList.categoryID == 2 ||
- entityList.categoryID == 4) &&
- doctorID == entityList.doctorID)
+ if ((entityList.categoryID == 2 || entityList.categoryID == 4) && doctorID == entityList.doctorID && !isInpatient)
InkWell(
child: Icon(DoctorApp.edit),
onTap: onTap,
diff --git a/lib/util/NotificationPermissionUtils.dart b/lib/util/NotificationPermissionUtils.dart
new file mode 100644
index 00000000..8950fae3
--- /dev/null
+++ b/lib/util/NotificationPermissionUtils.dart
@@ -0,0 +1,42 @@
+import 'package:doctor_app_flutter/widgets/dialog/AskPermissionDialog.dart';
+import 'package:doctor_app_flutter/widgets/transitions/slide_up_page.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+
+class AppPermissionsUtils {
+
+ static requestVideoCallPermission({BuildContext context, String type,Function onTapGrant}) async {
+
+ var cameraPermission = Permission.camera;
+ var microphonePermission = Permission.microphone;
+ PermissionStatus permissionCameraStatus = await cameraPermission.status;
+ PermissionStatus permissionMicrophoneStatus = await microphonePermission.status;
+
+ if (permissionCameraStatus.isPermanentlyDenied || permissionMicrophoneStatus.isPermanentlyDenied) {
+ await _showPermissionDialog(context, type,onTapGrant);
+ } else if (!permissionCameraStatus.isGranted || !permissionMicrophoneStatus.isGranted) {
+ permissionCameraStatus = await cameraPermission.request();
+ permissionMicrophoneStatus = await microphonePermission.request();
+ if (permissionCameraStatus.isDenied || permissionMicrophoneStatus.isDenied)
+ await _showPermissionDialog(context, type,onTapGrant);
+ else
+ onTapGrant();
+ } else if (permissionCameraStatus.isDenied || permissionMicrophoneStatus.isDenied)
+ await _showPermissionDialog(context, type,onTapGrant);
+ else
+ onTapGrant();
+ }
+
+ static _showPermissionDialog(BuildContext context, String type,Function onTapGrant) async {
+ Navigator.push(
+ context, SlideUpPageRoute(widget: AskPermissionDialog(type: type,onTapGrant: onTapGrant,)));
+ }
+
+ static Future isVideoCallPermissionGranted() async {
+ PermissionStatus permissionCameraStatus = await Permission.camera.status;
+ PermissionStatus permissionMicrophoneStatus = await Permission.microphone.status;
+ return permissionCameraStatus.isGranted && permissionMicrophoneStatus.isGranted;
+ }
+}
diff --git a/lib/util/VideoChannel.dart b/lib/util/VideoChannel.dart
index 0104f55b..832749cf 100644
--- a/lib/util/VideoChannel.dart
+++ b/lib/util/VideoChannel.dart
@@ -4,15 +4,30 @@ import 'dart:io' show Platform;
import 'package:doctor_app_flutter/config/config.dart';
import 'package:doctor_app_flutter/models/livecare/session_status_model.dart';
+import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class VideoChannel{
/// channel name
static const _channel = const MethodChannel("Dr.cloudSolution/videoCall");
- static openVideoCallScreen(
- {kApiKey, kSessionId, kToken, callDuration, warningDuration,int vcId,String tokenID,String generalId,int doctorId, String patientName, Function() onCallEnd , Function(SessionStatusModel sessionStatusModel) onCallNotRespond ,Function(String error) onFailure}) async {
+ static openVideoCallScreen({kApiKey, kSessionId, kToken, callDuration, warningDuration,int vcId,String tokenID,
+ String generalId,int doctorId, String patientName, Function() onCallEnd ,
+ Function(SessionStatusModel sessionStatusModel) onCallNotRespond ,Function(String error) onFailure, VoidCallback onCallConnected, VoidCallback onCallDisconnected}) async {
+
+ onCallConnected = onCallConnected ?? (){};
+ onCallDisconnected = onCallDisconnected ?? (){};
var result;
try {
+ _channel.setMethodCallHandler((call) {
+ if(call.method == 'onCallConnected'){
+ onCallConnected();
+ }
+ if(call.method == 'onCallDisconnected'){
+ onCallDisconnected();
+ }
+ return true as dynamic;
+ });
+
result = await _channel.invokeMethod(
'openVideoCall',
{
diff --git a/lib/util/helpers.dart b/lib/util/helpers.dart
index 634abd02..7232f0c9 100644
--- a/lib/util/helpers.dart
+++ b/lib/util/helpers.dart
@@ -270,4 +270,11 @@ class Helpers {
var htmlRegex = RegExp("<(“[^”]*”|'[^’]*’|[^'”>])*>");
return htmlRegex.hasMatch(text);
}
+
+ static String timeFrom({Duration duration}) {
+ String twoDigits(int n) => n.toString().padLeft(2, "0");
+ String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
+ String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
+ return "$twoDigitMinutes:$twoDigitSeconds";
+ }
}
diff --git a/lib/widgets/dialog/AskPermissionDialog.dart b/lib/widgets/dialog/AskPermissionDialog.dart
new file mode 100644
index 00000000..58718373
--- /dev/null
+++ b/lib/widgets/dialog/AskPermissionDialog.dart
@@ -0,0 +1,90 @@
+import 'package:doctor_app_flutter/widgets/shared/app_texts_widget.dart';
+import 'package:doctor_app_flutter/widgets/shared/buttons/app_buttons_widget.dart';
+import 'package:doctor_app_flutter/widgets/shared/buttons/secondary_button.dart';
+import 'package:eva_icons_flutter/eva_icons_flutter.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+class AskPermissionDialog extends StatefulWidget {
+ final String type;
+ final Function onTapGrant;
+
+ AskPermissionDialog({this.type, this.onTapGrant});
+
+ @override
+ _AskPermissionDialogState createState() => _AskPermissionDialogState();
+}
+
+class _AskPermissionDialogState extends State {
+ getText() {
+ return "Turn on your Camera, Microphone to start video call";
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ automaticallyImplyLeading: false,
+ elevation: 0.5,
+ actions: [
+ Padding(
+ padding: EdgeInsets.only(right: 18.0),
+ child: GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: Feedback.wrapForTap(() {
+ Navigator.pop(context);
+ }, context),
+ child:
+ Icon(EvaIcons.close, color: Theme.of(context).primaryColor),
+ ),
+ )
+ ],
+ ),
+ body: Container(
+ margin: EdgeInsets.symmetric(horizontal: 48),
+ child: Center(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ AppText(
+ "🙋♀",
+ style: "headline1",
+ color: Colors.black,
+ ),
+ AppText(
+ "Don't miss out!",
+ style: "headline4",
+ color: Colors.black,
+ ),
+ SizedBox(
+ height: 8,
+ ),
+ AppText(
+ getText(),
+ color: Colors.grey,
+ style: "bodyText2",
+ textAlign: TextAlign.center,
+ ),
+ SizedBox(
+ height: MediaQuery.of(context).size.height / 6,
+ ),
+ AppButton(
+ fontColor: Theme.of(context).backgroundColor,
+ color: Colors.red[700],
+ title: "Turn On Camera, Microphone",
+ onPressed: () async {
+ openAppSettings().then((value) {
+ Navigator.pop(context);
+ widget.onTapGrant();
+ });
+ },
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/patients/profile/patient-profile-app-bar.dart b/lib/widgets/patients/profile/patient-profile-app-bar.dart
index 83186b02..2a0fa072 100644
--- a/lib/widgets/patients/profile/patient-profile-app-bar.dart
+++ b/lib/widgets/patients/profile/patient-profile-app-bar.dart
@@ -33,6 +33,7 @@ class PatientProfileAppBar extends StatelessWidget
final String clinic;
final bool isAppointmentHeader;
final bool isFromLabResult;
+ final VoidCallback onPressed;
PatientProfileAppBar(
this.patient,
@@ -52,7 +53,7 @@ class PatientProfileAppBar extends StatelessWidget
this.episode,
this.visitDate,
this.isAppointmentHeader = false,
- this.isFromLabResult = false});
+ this.isFromLabResult = false, this.onPressed});
@override
@@ -93,7 +94,11 @@ class PatientProfileAppBar extends StatelessWidget
IconButton(
icon: Icon(Icons.arrow_back_ios),
color: Color(0xFF2B353E), //Colors.black,
- onPressed: () => Navigator.pop(context),
+ onPressed: () {
+ if(onPressed!=null)
+ onPressed();
+ Navigator.pop(context);
+ },
),
Expanded(
child: AppText(
diff --git a/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart b/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart
new file mode 100644
index 00000000..f2a32e63
--- /dev/null
+++ b/lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart
@@ -0,0 +1,390 @@
+import 'package:doctor_app_flutter/config/config.dart';
+import 'package:doctor_app_flutter/config/size_config.dart';
+import 'package:doctor_app_flutter/icons_app/doctor_app_icons.dart';
+import 'package:doctor_app_flutter/models/patient/patiant_info_model.dart';
+import 'package:doctor_app_flutter/util/date-utils.dart';
+import 'package:doctor_app_flutter/util/helpers.dart';
+import 'package:doctor_app_flutter/util/translations_delegate_base.dart';
+import 'package:doctor_app_flutter/widgets/shared/app_texts_widget.dart';
+import 'package:flutter/material.dart';
+import 'package:hexcolor/hexcolor.dart';
+import 'package:intl/intl.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class PatientProfileHeaderNewDesignAppBar extends StatelessWidget
+ with PreferredSizeWidget {
+ final PatiantInformtion patient;
+ final String patientType;
+ final String arrivalType;
+ final double height;
+ final bool isInpatient;
+ final bool isDischargedPatient;
+ final bool isFromLiveCare;
+
+ final Stream videoCallDurationStream;
+
+ PatientProfileHeaderNewDesignAppBar(
+ this.patient, this.patientType, this.arrivalType, {this.height = 0.0, this.isInpatient=false, this.isDischargedPatient=false, this.isFromLiveCare = false, this.videoCallDurationStream});
+
+ @override
+ Widget build(BuildContext context) {
+ int gender = 1;
+ if (patient.patientDetails != null) {
+ gender = patient.patientDetails.gender;
+ } else {
+ gender = patient.gender;
+ }
+ return Container(
+ padding: EdgeInsets.only(
+ left: 0,
+ right: 5,
+ bottom: 5,
+ ),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ ),
+ height: height == 0 ? isInpatient? 215:200 : height,
+ child: Container(
+ padding: EdgeInsets.only(left: 10, right: 10, bottom: 10),
+ margin: EdgeInsets.only(top: 50),
+ child: Column(
+ children: [
+ Container(
+ padding: EdgeInsets.only(left: 12.0),
+ child: Row(children: [
+ IconButton(
+ icon: Icon(Icons.arrow_back_ios),
+ color: Colors.black, //Colors.black,
+ onPressed: () => Navigator.pop(context),
+ ),
+ Expanded(
+ child: AppText(
+ patient.firstName != null
+ ? (Helpers.capitalize(patient.firstName) +
+ " " +
+ Helpers.capitalize(patient.lastName))
+ : Helpers.capitalize(patient.fullName??patient.patientDetails.fullName),
+ fontSize: SizeConfig.textMultiplier * 1.8,
+ fontWeight: FontWeight.bold,
+ fontFamily: 'Poppins',
+ ),
+ ),
+ gender == 1
+ ? Icon(
+ DoctorApp.male_2,
+ color: Colors.blue,
+ )
+ : Icon(
+ DoctorApp.female_1,
+ color: Colors.pink,
+ ),
+ Container(
+ margin: EdgeInsets.symmetric(horizontal: 4),
+ child: InkWell(
+ onTap: () {
+ launch("tel://" + patient.mobileNumber);
+ },
+ child: Icon(
+ Icons.phone,
+ color: Colors.black87,
+ ),
+ ),
+ ),
+ StreamBuilder(
+ stream: videoCallDurationStream,
+ builder: (BuildContext context, AsyncSnapshot snapshot) {
+ if(snapshot.hasData && snapshot.data != null)
+ return InkWell(
+ onTap: (){
+ },
+ child: Container(
+ decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(20)),
+ padding: EdgeInsets.symmetric(vertical: 2, horizontal: 10),
+ child: Text(snapshot.data, style: TextStyle(color: Colors.white),),
+ ),
+ );
+ else
+ return Container();
+ },
+ ),
+ ]),
+ ),
+ Row(children: [
+ Padding(
+ padding: EdgeInsets.only(left: 12.0),
+ child: Container(
+ width: 60,
+ height: 60,
+ child: Image.asset(
+ gender == 1
+ ? 'assets/images/male_avatar.png'
+ : 'assets/images/female_avatar.png',
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ SizedBox(
+ width: 10,
+ ),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SERVICES_PATIANT2[int.parse(patientType)] ==
+ "patientArrivalList"
+ ? Container(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ patient.patientStatusType == 43
+ ? AppText(
+ TranslationBase.of(context).arrivedP,
+ color: Colors.green,
+ fontWeight: FontWeight.bold,
+ fontFamily: 'Poppins',
+ fontSize: 12,
+ )
+ : AppText(
+ TranslationBase.of(context).notArrived,
+ color: Colors.red[800],
+ fontWeight: FontWeight.bold,
+ fontFamily: 'Poppins',
+ fontSize: 12,
+ ),
+ arrivalType == '1'|| patient.arrivedOn == null
+ ? AppText(
+ patient.startTime != null
+ ? patient.startTime
+ : '',
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w600,
+ )
+ : AppText(
+ patient.arrivedOn != null
+ ? AppDateUtils.convertStringToDateFormat(
+ patient.arrivedOn,
+ 'MM-dd-yyyy HH:mm')
+ : '',
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w600,
+ )
+ ],
+ ))
+ : SizedBox(),
+ if (SERVICES_PATIANT2[int.parse(patientType)] ==
+ "List_MyOutPatient" && !isFromLiveCare)
+ Container(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [
+ AppText(
+ TranslationBase.of(context).appointmentDate +
+ " : ",
+ fontSize: 14,
+ ),
+ patient.startTime != null
+ ? Container(
+ height: 15,
+ width: 60,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(25),
+ color: HexColor("#20A169"),
+ ),
+ child: AppText(
+ patient.startTime??"",
+ color: Colors.white,
+ fontSize: 1.5 * SizeConfig.textMultiplier,
+ textAlign: TextAlign.center,
+ fontWeight: FontWeight.bold,
+ ),
+ )
+ : SizedBox(),
+ SizedBox(
+ width: 3.5,
+ ),
+ Container(
+ child: AppText(
+ convertDateFormat2(
+ patient.appointmentDate ?? ''),
+ fontSize: 1.5 * SizeConfig.textMultiplier,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(
+ height: 0.5,
+ )
+ ],
+ ),
+ margin: EdgeInsets.only(
+ top: 8,
+ ),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ RichText(
+ text: TextSpan(
+ style: TextStyle(
+ fontSize: 1.6 * SizeConfig.textMultiplier,
+ color: Colors.black),
+ children: [
+ new TextSpan(
+ text: TranslationBase.of(context).fileNumber,
+ style: TextStyle(
+ fontSize: 12, fontFamily: 'Poppins')),
+ new TextSpan(
+ text: patient.patientId.toString(),
+ style: TextStyle(
+ fontWeight: FontWeight.w700,
+ fontFamily: 'Poppins',
+ fontSize: 14)),
+ ],
+ ),
+ ),
+ Row(
+ children: [
+ AppText(
+ patient.nationalityName ?? patient.nationality?? patient.nationalityId ?? '',
+ fontWeight: FontWeight.bold,
+ fontSize: 12,
+ ),
+ patient.nationalityFlagURL != null
+ ? ClipRRect(
+ borderRadius: BorderRadius.circular(20.0),
+ child: Image.network(
+ patient.nationalityFlagURL,
+ height: 25,
+ width: 30,
+ errorBuilder: (BuildContext context,
+ Object exception,
+ StackTrace stackTrace) {
+ return Text('No Image');
+ },
+ ))
+ : SizedBox()
+ ],
+ )
+ ],
+ ),
+ Container(
+ child: RichText(
+ text: new TextSpan(
+ style: new TextStyle(
+ fontSize: 1.6 * SizeConfig.textMultiplier,
+ color: Colors.black,
+ fontFamily: 'Poppins',
+ ),
+ children: [
+ new TextSpan(
+ text: TranslationBase.of(context).age+ " : ",
+ style: TextStyle(fontSize: 14)),
+ new TextSpan(
+ text:
+ "${AppDateUtils.getAgeByBirthday(patient.patientDetails != null ? patient.patientDetails.dateofBirth??"" : patient.dateofBirth??"", context,isServerFormat: !isFromLiveCare)}",
+ style: TextStyle(
+ fontWeight: FontWeight.w700, fontSize: 14)),
+ ],
+ ),
+ ),
+ ),
+ if(isInpatient)
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ child: RichText(
+ text: new TextSpan(
+ style: new TextStyle(
+ fontSize:
+ 2.0 * SizeConfig.textMultiplier,
+ color: Colors.black,
+ fontFamily: 'Poppins',
+ ),
+ children: [
+ new TextSpan(
+ text: patient.admissionDate == null
+ ? ""
+ : TranslationBase.of(context)
+ .admissionDate +
+ " : ",
+ style: TextStyle(fontSize: 14)),
+ new TextSpan(
+ text: patient.admissionDate == null
+ ? ""
+ : "${AppDateUtils.convertDateFromServerFormat(patient.admissionDate.toString(), 'yyyy-MM-dd')}",
+ style: TextStyle(
+ fontWeight: FontWeight.w700,
+ fontSize: 15)),
+ ]))),
+ if (patient.admissionDate != null)
+ Row(
+ children: [
+ AppText(
+ "${TranslationBase.of(context).numOfDays}: ",
+ fontSize: 15,
+ ),
+ if(isDischargedPatient && patient.dischargeDate!=null)
+ AppText(
+ "${AppDateUtils.getDateTimeFromServerFormat(patient.dischargeDate).difference(AppDateUtils.getDateTimeFromServerFormat(patient.admissionDate)).inDays + 1}",
+ fontSize: 15,
+ fontWeight: FontWeight.w700)
+ else
+ AppText(
+ "${DateTime.now().difference(AppDateUtils.getDateTimeFromServerFormat(patient.admissionDate)).inDays + 1}",
+ fontSize: 15,
+ fontWeight: FontWeight.w700),
+ ],
+ ),
+ ],
+ )
+ ],
+ ),
+ ),
+ ]),
+ ],
+ ),
+ ),
+ );
+ }
+
+ convertDateFormat2(String str) {
+ String newDate;
+ const start = "/Date(";
+ if (str.isNotEmpty) {
+ const end = "+0300)";
+
+ final startIndex = str.indexOf(start);
+ final endIndex = str.indexOf(end, startIndex + start.length);
+
+ var date = new DateTime.fromMillisecondsSinceEpoch(
+ int.parse(str.substring(startIndex + start.length, endIndex)));
+ newDate = date.year.toString() +
+ "/" +
+ date.month.toString().padLeft(2, '0') +
+ "/" +
+ date.day.toString().padLeft(2, '0');
+ }
+
+ return newDate??'';
+ }
+
+ isToday(date) {
+ DateTime tempDate = new DateFormat("yyyy-MM-dd").parse(date);
+ return DateFormat("yyyy-MM-dd").format(tempDate) ==
+ DateFormat("yyyy-MM-dd").format(DateTime.now());
+ }
+
+ myBoxDecoration() {
+ return BoxDecoration(
+ border: Border(
+ top: BorderSide(
+ color: Colors.green,
+ width: 5,
+ ),
+ ),
+ borderRadius: BorderRadius.circular(10));
+ }
+
+ @override
+ Size get preferredSize => Size(double.maxFinite, 200);
+}
diff --git a/lib/widgets/shared/app_scaffold_widget.dart b/lib/widgets/shared/app_scaffold_widget.dart
index e957b5d4..b930c21a 100644
--- a/lib/widgets/shared/app_scaffold_widget.dart
+++ b/lib/widgets/shared/app_scaffold_widget.dart
@@ -19,8 +19,11 @@ class AppScaffold extends StatelessWidget {
final Widget bottomSheet;
final Color backgroundColor;
final Widget appBar;
+ final Widget drawer;
+ final Widget bottomNavigationBar;
final String subtitle;
final bool isHomeIcon;
+ final bool extendBody;
AppScaffold(
{this.appBarTitle = '',
this.body,
@@ -30,7 +33,7 @@ class AppScaffold extends StatelessWidget {
this.bottomSheet,
this.backgroundColor,
this.isHomeIcon = true,
- this.appBar, this.subtitle});
+ this.appBar, this.subtitle, this.drawer, this.extendBody = false, this.bottomNavigationBar});
@override
Widget build(BuildContext context) {
@@ -42,6 +45,9 @@ class AppScaffold extends StatelessWidget {
},
child: Scaffold(
backgroundColor: backgroundColor ?? Colors.white,
+ drawer: drawer,
+ extendBody: extendBody,
+ bottomNavigationBar: bottomNavigationBar,
appBar: isShowAppBar
? appBar ??
AppBar(
diff --git a/pubspec.lock b/pubspec.lock
index 77df9848..e4fdc2dc 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -587,7 +587,7 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
- version: "0.6.3-nullsafety.1"
+ version: "0.6.2"
json_annotation:
dependency: transitive
description:
@@ -813,7 +813,7 @@ packages:
source: hosted
version: "0.1.8"
quiver:
- dependency: transitive
+ dependency: "direct main"
description:
name: quiver
url: "https://pub.dartlang.org"
diff --git a/pubspec.yaml b/pubspec.yaml
index 973e8800..1429bc3e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -90,7 +90,7 @@ dependencies:
speech_to_text:
path: speech_to_text
-
+ quiver: ^2.1.5
# Html Editor Enhanced
html_editor_enhanced: ^1.3.0