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