Merge branch 'flutter_2_developemt' of https://gitlab.com/Cloud_Solution/doctor_app_flutter into flutter_vervion_2_elham
Conflicts: ios/Podfile lib/client/base_app_client.dart lib/core/model/patient_muse/PatientSearchRequestModel.dart lib/core/model/referral/MyReferralPatientModel.dart lib/core/service/patient/LiveCarePatientServices.dart lib/core/service/patient/MyReferralPatientService.dart lib/core/service/patient/patient_service.dart lib/core/viewModel/LiveCarePatientViewModel.dart lib/core/viewModel/PatientMedicalReportViewModel.dart lib/core/viewModel/PatientSearchViewModel.dart lib/core/viewModel/authentication_view_model.dart lib/core/viewModel/dashboard_view_model.dart lib/core/viewModel/patient-referral-viewmodel.dart lib/core/viewModel/procedure_View_model.dart lib/core/viewModel/referred_view_model.dart lib/models/livecare/start_call_req.dart lib/models/patient/patiant_info_model.dart lib/screens/auth/login_screen.dart lib/screens/auth/verification_methods_screen.dart lib/screens/home/dashboard_slider-item-widget.dart lib/screens/home/dashboard_swipe_widget.dart lib/screens/home/home_page_card.dart lib/screens/home/home_patient_card.dart lib/screens/home/home_screen.dart lib/screens/live_care/end_call_screen.dart lib/screens/live_care/live-care_transfer_to_admin.dart lib/screens/live_care/live_care_patient_screen.dart lib/screens/medical-file/health_summary_page.dart lib/screens/medical-file/medical_file_details.dart lib/screens/patients/ECGPage.dart lib/screens/patients/insurance_approval_screen_patient.dart lib/screens/patients/insurance_approvals_details.dart lib/screens/patients/profile/UCAF/UCAF-detail-screen.dart lib/screens/patients/profile/UCAF/UCAF-input-screen.dart lib/screens/patients/profile/admission-request/admission-request-first-screen.dart lib/screens/patients/profile/admission-request/admission-request-third-screen.dart lib/screens/patients/profile/admission-request/admission-request_second-screen.dart lib/screens/patients/profile/medical_report/AddVerifyMedicalReport.dart lib/screens/patients/profile/medical_report/MedicalReportPage.dart lib/screens/patients/profile/note/progress_note_screen.dart lib/screens/patients/profile/profile_screen/PatientProfileCardModel.dart lib/screens/patients/profile/profile_screen/patient_profile_screen.dart lib/screens/patients/profile/radiology/radiology_details_page.dart lib/screens/patients/profile/radiology/radiology_home_page.dart lib/screens/patients/profile/referral/AddReplayOnReferralPatient.dart lib/screens/patients/profile/referral/my-referral-inpatient-screen.dart lib/screens/patients/profile/referral/refer-patient-screen-in-patient.dart lib/screens/patients/profile/referral/refer-patient-screen.dart lib/screens/patients/profile/referral/referral_patient_detail_in-paint.dart lib/screens/patients/profile/referral/referred-patient-screen.dart lib/screens/patients/profile/referral/referred_patient_detail_in-paint.dart lib/screens/patients/profile/soap_update/update_soap_index.dart lib/screens/patients/profile/vital_sign/vital_sign_details_screen.dart lib/screens/patients/profile/vital_sign/vital_sign_item_details_screen.dart lib/screens/prescription/add_favourite_prescription.dart lib/screens/prescription/add_prescription_form.dart lib/screens/prescription/prescription_home_screen.dart lib/screens/prescription/prescription_item_in_patient_page.dart lib/screens/prescription/prescription_items_page.dart lib/screens/prescription/prescription_screen.dart lib/screens/prescription/prescription_screen_history.dart lib/screens/prescription/prescriptions_page.dart lib/screens/procedures/ProcedureCard.dart lib/screens/procedures/add-favourite-procedure.dart lib/screens/procedures/add-procedure-form.dart lib/screens/procedures/add_lab_home_screen.dart lib/screens/procedures/add_lab_orders.dart lib/screens/procedures/add_radiology_order.dart lib/screens/procedures/add_radiology_screen.dart lib/screens/procedures/base_add_procedure_tab_page.dart lib/screens/procedures/entity_list_procedure_widget.dart lib/screens/qr_reader/QR_reader_screen.dart lib/util/VideoChannel.dart lib/util/translations_delegate_base.dart lib/widgets/auth/sms-popup.dart lib/widgets/auth/verification_methods_list.dart lib/widgets/dashboard/out_patient_stack.dart lib/widgets/patients/patient-referral-item-widget.dart lib/widgets/patients/patient_card/PatientCard.dart lib/widgets/patients/profile/PatientHeaderWidgetNoAvatar.dart lib/widgets/patients/profile/PatientProfileButton.dart lib/widgets/patients/profile/patient-page-header-widget.dart lib/widgets/patients/profile/patient-profile-header-new-design-app-bar.dart lib/widgets/patients/profile/patient-profile-header-new-design.dart lib/widgets/patients/profile/patient_profile_header_with_appointment_card_app_bar.dart lib/widgets/patients/profile/profile-welcome-widget.dart lib/widgets/shared/app_drawer_widget.dart lib/widgets/shared/app_scaffold_widget.dart lib/widgets/shared/app_texts_widget.dart lib/widgets/shared/bottom_navigation_item.dart lib/widgets/shared/buttons/app_buttons_widget.dart lib/widgets/shared/drawer_item_widget.dart pubspec.lockmerge-requests/748/head
@ -1,442 +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.util.Log;
|
||||
import android.view.View;
|
||||
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 ImageView mCallBtn;
|
||||
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();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initUI() {
|
||||
mPublisherViewContainer = (FrameLayout) findViewById(R.id.local_video_view_container);
|
||||
mSubscriberViewContainer = (RelativeLayout) 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);
|
||||
|
||||
|
||||
mCallBtn = findViewById(R.id.btn_call);
|
||||
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, 30 * 1000);
|
||||
|
||||
}
|
||||
|
||||
private void hiddenButtons() {
|
||||
mVolHandler = new Handler();
|
||||
mVolRunnable = new Runnable() {
|
||||
public void run() {
|
||||
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<String> perms) {
|
||||
Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsDenied(int requestCode, List<String> 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(Session session) {
|
||||
Log.d(TAG, "onDisconnected: disconnected from session " + session.getSessionId());
|
||||
|
||||
mSession = null;
|
||||
}
|
||||
|
||||
@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);
|
||||
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, "Session error. See the logcat please.", 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 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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallChangeCallStatusSuccessful(SessionStatusModel sessionStatusModel) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -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(){}
|
||||
}
|
||||
@ -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<TextView>(R.id.patient_name)
|
||||
patientName.text = sessionStatusModel!!.patientName
|
||||
|
||||
cmTimer = view.findViewById<Chronometer>(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<ImageView>(R.id.btn_call)
|
||||
mCallBtn.setOnClickListener {
|
||||
onCallClicked()
|
||||
}
|
||||
btnMinimize = view.findViewById<ImageView>(R.id.btn_minimize)
|
||||
btnMinimize.setOnClickListener {
|
||||
onMinimizedClicked(it)
|
||||
}
|
||||
mCameraBtn = view.findViewById<ImageView>(R.id.btn_camera)
|
||||
mCameraBtn.setOnClickListener {
|
||||
onCameraClicked(it)
|
||||
}
|
||||
mSwitchCameraBtn = view.findViewById<ImageView>(R.id.btn_switch_camera)
|
||||
mSwitchCameraBtn.setOnClickListener {
|
||||
onSwitchCameraClicked(it)
|
||||
}
|
||||
mspeckerBtn = view.findViewById<ImageView>(R.id.btn_specker)
|
||||
mspeckerBtn.setOnClickListener {
|
||||
onSpeckerClicked(it)
|
||||
}
|
||||
mMicBtn = view.findViewById<ImageView>(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<String?>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
|
||||
}
|
||||
|
||||
override fun onPermissionsGranted(requestCode: Int, perms: List<String?>) {
|
||||
Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size)
|
||||
}
|
||||
|
||||
override fun onPermissionsDenied(requestCode: Int, perms: List<String?>) {
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<solid
|
||||
android:color="@color/remoteBackground"/>
|
||||
|
||||
<stroke android:width="2dp" android:color="@color/text_color" />
|
||||
|
||||
<size
|
||||
android:width="120dp"
|
||||
android:height="120dp"/>
|
||||
</shape>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,13H5v-2h14v2z"/>
|
||||
</vector>
|
||||
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/text_color" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/text_color" />
|
||||
<corners android:radius="10dp" />
|
||||
<padding
|
||||
android:bottom="0dp"
|
||||
android:left="0dp"
|
||||
android:right="0dp"
|
||||
android:top="0dp" />
|
||||
</shape>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="1000dp"/>
|
||||
|
||||
<solid android:color="@color/green_dark"/>
|
||||
|
||||
</shape>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.6 KiB |
@ -0,0 +1,92 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '11.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def parse_KV_file(file, separator='=')
|
||||
file_abs_path = File.expand_path(file)
|
||||
if !File.exists? file_abs_path
|
||||
return [];
|
||||
end
|
||||
generated_key_values = {}
|
||||
skip_line_start_symbols = ["#", "/"]
|
||||
File.foreach(file_abs_path) do |line|
|
||||
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
|
||||
plugin = line.split(pattern=separator)
|
||||
if plugin.length == 2
|
||||
podname = plugin[0].strip()
|
||||
path = plugin[1].strip()
|
||||
podpath = File.expand_path("#{path}", file_abs_path)
|
||||
generated_key_values[podname] = podpath
|
||||
else
|
||||
puts "Invalid plugin specification: #{line}"
|
||||
end
|
||||
end
|
||||
generated_key_values
|
||||
end
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
# Flutter Pod
|
||||
|
||||
copied_flutter_dir = File.join(__dir__, 'Flutter')
|
||||
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
|
||||
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
|
||||
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
|
||||
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
|
||||
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
|
||||
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
|
||||
|
||||
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
|
||||
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
|
||||
|
||||
unless File.exist?(copied_framework_path)
|
||||
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
|
||||
end
|
||||
unless File.exist?(copied_podspec_path)
|
||||
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
|
||||
end
|
||||
end
|
||||
|
||||
# Keep pod path relative so it can be checked into Podfile.lock.
|
||||
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
|
||||
# referring to absolute paths on developers' machines.
|
||||
system('rm -rf .symlinks')
|
||||
system('mkdir -p .symlinks/plugins')
|
||||
plugin_pods = parse_KV_file('../.flutter-plugins')
|
||||
plugin_pods.each do |name, path|
|
||||
symlink = File.join('.symlinks', 'plugins', name)
|
||||
File.symlink(path, symlink)
|
||||
pod name, :path => File.join(symlink, 'ios')
|
||||
end
|
||||
end
|
||||
|
||||
# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
|
||||
install! 'cocoapods', :disable_input_output_paths => true
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.9 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 21 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 906 B |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
@ -1,296 +1,366 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina6_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<!--Main App View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<viewController id="BYZ-38-t0r" customClass="MainAppViewController" customModule="Runner" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-45" y="1436"/>
|
||||
<point key="canvasLocation" x="-1535" y="2239"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<!--Video Call View Controller-->
|
||||
<scene sceneID="L7v-n1-KvF">
|
||||
<objects>
|
||||
<viewController id="t2c-G5-7AE" customClass="ViewController" customModule="Runner" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController storyboardIdentifier="videoCall" id="t2c-G5-7AE" customClass="VideoCallViewController" customModule="Runner" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="BIa-yr-ZMY"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="VIH-1T-Ife"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="PoR-7r-yNe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6C5-o5-R1n" userLabel="controlButtonSpacer">
|
||||
<rect key="frame" x="14" y="844" width="139" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="0mU-Ko-Up3"/>
|
||||
<constraint firstAttribute="width" constant="139" id="DNz-Ig-XOS"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yS9-HT-vp8" userLabel="controlButtonSpacer">
|
||||
<rect key="frame" x="265" y="844" width="140" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="140" id="Alm-7E-yeU"/>
|
||||
<constraint firstAttribute="height" constant="30" id="amS-in-0UH"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" restorationIdentifier="VideoCallNavigationViewController" translatesAutoresizingMaskIntoConstraints="NO" id="eOT-Jr-GUl" userLabel="remoteVideo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.98999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="videoMutedIndicator" translatesAutoresizingMaskIntoConstraints="NO" id="CL1-2v-m1L" userLabel="remoteVideoMutedIndicator">
|
||||
<rect key="frame" x="147" y="398" width="120" height="100"/>
|
||||
<rect key="frame" x="160.66666666666666" y="418.33333333333331" width="106.99999999999997" height="89.333333333333314"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="120" id="eon-3Z-74P"/>
|
||||
<constraint firstAttribute="height" constant="100" id="iyK-hL-H3m"/>
|
||||
<constraint firstAttribute="width" secondItem="CL1-2v-m1L" secondAttribute="height" multiplier="6:5" id="xqE-6Q-WAu"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0ZP-s2-kM5" userLabel="localVideo">
|
||||
<rect key="frame" x="270.5" y="138" width="103.5" height="224"/>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="localVideoMutedBg" translatesAutoresizingMaskIntoConstraints="NO" id="A0G-cT-yji" userLabel="localVideoMutedBg">
|
||||
<rect key="frame" x="215" y="138" width="159" height="224"/>
|
||||
</imageView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sK0-tB-H9u" userLabel="controlButtons">
|
||||
<rect key="frame" x="0.0" y="740" width="414" height="80"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="O5s-CJ-RFv">
|
||||
<rect key="frame" x="0.0" y="100" width="428" height="731"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NUO-2p-pzL" userLabel="videoMuteButton">
|
||||
<rect key="frame" x="29" y="20.5" width="39" height="39"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="39" id="CWU-fT-9gx"/>
|
||||
<constraint firstAttribute="height" constant="39" id="nsM-Xv-O2N"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Button" image="speakerOff"/>
|
||||
<state key="selected" image="speakerOffSelected"/>
|
||||
<connections>
|
||||
<action selector="didClickSpeakerButton:" destination="t2c-G5-7AE" eventType="touchUpInside" id="XHg-oo-TWD"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="N2h-FX-kF5" userLabel="muteButton">
|
||||
<rect key="frame" x="98" y="20.5" width="39" height="39"/>
|
||||
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XHG-Db-3Px" customClass="AADraggableView" customModule="AADraggableView">
|
||||
<rect key="frame" x="288" y="10" width="130" height="182"/>
|
||||
<subviews>
|
||||
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0ZP-s2-kM5" userLabel="localVideo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="130" height="182"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.borderWidth">
|
||||
<real key="value" value="0.5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6y1-an-W7F">
|
||||
<rect key="frame" x="0.0" y="0.0" width="130" height="182"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="videoMutedIndicator" translatesAutoresizingMaskIntoConstraints="NO" id="FEH-bC-Hnh" userLabel="localVideoMutedIndicator">
|
||||
<rect key="frame" x="47" y="76" width="36" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="1iv-w5-GDT"/>
|
||||
<constraint firstAttribute="width" constant="36" id="AwX-vQ-Ynn"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="FEH-bC-Hnh" firstAttribute="centerX" secondItem="6y1-an-W7F" secondAttribute="centerX" id="dtO-VP-8qV"/>
|
||||
<constraint firstItem="FEH-bC-Hnh" firstAttribute="centerY" secondItem="6y1-an-W7F" secondAttribute="centerY" id="lpW-Vr-bH2"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="5"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="39" id="Nv9-dR-SYL"/>
|
||||
<constraint firstAttribute="height" constant="39" id="lFc-rS-c6B"/>
|
||||
<constraint firstItem="0ZP-s2-kM5" firstAttribute="top" secondItem="XHG-Db-3Px" secondAttribute="top" id="E5A-Yq-Ods"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6y1-an-W7F" secondAttribute="trailing" id="I0l-x2-rGO"/>
|
||||
<constraint firstAttribute="width" secondItem="XHG-Db-3Px" secondAttribute="height" multiplier="1:1.4" id="M3V-vM-m0V"/>
|
||||
<constraint firstAttribute="bottom" secondItem="6y1-an-W7F" secondAttribute="bottom" id="T0D-IE-19P"/>
|
||||
<constraint firstItem="6y1-an-W7F" firstAttribute="top" secondItem="XHG-Db-3Px" secondAttribute="top" id="T8b-Nd-jdR"/>
|
||||
<constraint firstItem="6y1-an-W7F" firstAttribute="leading" secondItem="XHG-Db-3Px" secondAttribute="leading" id="brg-bp-MME"/>
|
||||
<constraint firstAttribute="bottom" secondItem="0ZP-s2-kM5" secondAttribute="bottom" id="hso-Od-7QF"/>
|
||||
<constraint firstItem="0ZP-s2-kM5" firstAttribute="leading" secondItem="XHG-Db-3Px" secondAttribute="leading" id="k5R-AM-3J6"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0ZP-s2-kM5" secondAttribute="trailing" id="z8V-qh-Rzq"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Button" image="muteButton"/>
|
||||
<state key="selected" image="muteButtonSelected"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="padding">
|
||||
<integer key="value" value="20"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4S7-45-trL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="428" height="731"/>
|
||||
<connections>
|
||||
<action selector="didClickMuteButton:" destination="t2c-G5-7AE" eventType="touchUpInside" id="7lr-uj-db1"/>
|
||||
<action selector="btnSwipeVideoTapped:" destination="t2c-G5-7AE" eventType="touchUpInside" id="7AQ-6d-4iR"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ygd-al-G4F" userLabel="switchCameraButton">
|
||||
<rect key="frame" x="352" y="20.5" width="39" height="39"/>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="4S7-45-trL" firstAttribute="leading" secondItem="O5s-CJ-RFv" secondAttribute="leading" id="AMe-9q-Xk9"/>
|
||||
<constraint firstAttribute="trailing" secondItem="4S7-45-trL" secondAttribute="trailing" id="CX4-Rs-rep"/>
|
||||
<constraint firstAttribute="trailing" secondItem="XHG-Db-3Px" secondAttribute="trailing" constant="10" id="Ncy-sD-qeL"/>
|
||||
<constraint firstAttribute="bottom" secondItem="4S7-45-trL" secondAttribute="bottom" id="iJ9-pg-VXQ"/>
|
||||
<constraint firstItem="XHG-Db-3Px" firstAttribute="top" secondItem="O5s-CJ-RFv" secondAttribute="top" constant="10" id="nM8-B7-soj"/>
|
||||
<constraint firstItem="4S7-45-trL" firstAttribute="top" secondItem="O5s-CJ-RFv" secondAttribute="top" id="sNK-WK-tqV"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="WPH-QE-Fvp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="428" height="926"/>
|
||||
<connections>
|
||||
<action selector="btnOnScreenTapped:" destination="t2c-G5-7AE" eventType="touchUpInside" id="jDS-QU-1VX"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sK0-tB-H9u" userLabel="controlButtons">
|
||||
<rect key="frame" x="0.0" y="831" width="428" height="95"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="r9w-8l-Zxj">
|
||||
<rect key="frame" x="15" y="15" width="230" height="50"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="UZ6-EY-a59" userLabel="minMax">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="UZ6-EY-a59" secondAttribute="height" multiplier="1:1" id="Ula-Qc-wJE"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Button" image="float_video"/>
|
||||
<state key="selected" image="expand_video"/>
|
||||
<connections>
|
||||
<action selector="btnMinimizeTapped:" destination="t2c-G5-7AE" eventType="touchUpInside" id="xul-cC-cbm"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleAspectFit" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NUO-2p-pzL" userLabel="videoMuteButton">
|
||||
<rect key="frame" x="60" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="NUO-2p-pzL" secondAttribute="height" multiplier="1:1" id="KGm-Yp-MqK"/>
|
||||
</constraints>
|
||||
<state key="normal" image="video_unmute"/>
|
||||
<state key="selected" image="video_mute"/>
|
||||
<connections>
|
||||
<action selector="didClickVideoMuteButton:" destination="t2c-G5-7AE" eventType="touchUpInside" id="nk9-nS-UDE"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="N2h-FX-kF5" userLabel="muteButton">
|
||||
<rect key="frame" x="120" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="N2h-FX-kF5" secondAttribute="height" multiplier="1:1" id="hI1-G0-K3C"/>
|
||||
</constraints>
|
||||
<state key="normal" image="mic_unmute"/>
|
||||
<state key="selected" image="mic_mute"/>
|
||||
<connections>
|
||||
<action selector="didClickMuteButton:" destination="t2c-G5-7AE" eventType="touchUpInside" id="7lr-uj-db1"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" selected="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lar-xK-rhR" userLabel="switchCamera">
|
||||
<rect key="frame" x="180" y="0.0" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="lar-xK-rhR" secondAttribute="height" multiplier="1:1" id="Wpc-dx-kpF"/>
|
||||
</constraints>
|
||||
<state key="normal" image="camera_back"/>
|
||||
<state key="selected" image="camera_front"/>
|
||||
<connections>
|
||||
<action selector="didClickSwitchCameraButton:" destination="t2c-G5-7AE" eventType="touchUpInside" id="S3g-7j-Ca0"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="39" id="EHa-e5-BfD"/>
|
||||
<constraint firstAttribute="height" constant="39" id="KEK-0n-HJw"/>
|
||||
<constraint firstAttribute="height" constant="50" id="3L0-3h-Bxg"/>
|
||||
<constraint firstItem="lar-xK-rhR" firstAttribute="width" secondItem="lar-xK-rhR" secondAttribute="height" multiplier="1:1" id="R46-j0-zPf"/>
|
||||
<constraint firstItem="UZ6-EY-a59" firstAttribute="height" secondItem="NUO-2p-pzL" secondAttribute="height" id="kK6-uF-bf6"/>
|
||||
<constraint firstItem="N2h-FX-kF5" firstAttribute="height" secondItem="NUO-2p-pzL" secondAttribute="height" id="o4R-49-iMe"/>
|
||||
<constraint firstAttribute="height" constant="30" id="y90-hA-p5Z"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Button" image="switchCameraButton"/>
|
||||
<state key="selected" image="switchCameraButtonSelected"/>
|
||||
<connections>
|
||||
<action selector="didClickSwitchCameraButton:" destination="t2c-G5-7AE" eventType="touchUpInside" id="TBa-Av-IXy"/>
|
||||
</connections>
|
||||
</button>
|
||||
<variation key="default">
|
||||
<mask key="constraints">
|
||||
<exclude reference="y90-hA-p5Z"/>
|
||||
</mask>
|
||||
</variation>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="AoF-Up-Yu5" userLabel="hangUpButton">
|
||||
<rect key="frame" x="174" y="4.5" width="71" height="71"/>
|
||||
<rect key="frame" x="363" y="15" width="50" height="50"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="71" id="ACP-i0-gK8"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="71" id="DS1-a1-ep3"/>
|
||||
<constraint firstAttribute="height" constant="71" id="ltx-rR-am0"/>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="71" id="s7u-Zs-z2Q"/>
|
||||
<constraint firstAttribute="width" secondItem="AoF-Up-Yu5" secondAttribute="height" multiplier="1:1" id="6LJ-QR-WEY"/>
|
||||
</constraints>
|
||||
<state key="normal" image="hangUpButton"/>
|
||||
<state key="normal" image="end_call"/>
|
||||
<connections>
|
||||
<action selector="hangUp:" destination="t2c-G5-7AE" eventType="touchUpInside" id="SUH-Gd-OXj"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7vf-kV-eQ5" userLabel="videoMuteButton">
|
||||
<rect key="frame" x="283" y="20.5" width="39" height="39"/>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.18431372549019609" green="0.1764705882352941" blue="0.16470588235294117" alpha="0.5" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<constraints>
|
||||
<constraint firstItem="r9w-8l-Zxj" firstAttribute="top" secondItem="sK0-tB-H9u" secondAttribute="top" constant="15" id="00C-1i-sGc"/>
|
||||
<constraint firstItem="AoF-Up-Yu5" firstAttribute="bottom" secondItem="r9w-8l-Zxj" secondAttribute="bottom" id="1kB-OK-b0L"/>
|
||||
<constraint firstItem="r9w-8l-Zxj" firstAttribute="leading" secondItem="sK0-tB-H9u" secondAttribute="leading" priority="900" constant="5" id="6B7-94-Myo"/>
|
||||
<constraint firstAttribute="trailing" secondItem="AoF-Up-Yu5" secondAttribute="trailing" priority="900" constant="5" id="DgE-WQ-KQm"/>
|
||||
<constraint firstItem="r9w-8l-Zxj" firstAttribute="top" secondItem="sK0-tB-H9u" secondAttribute="top" priority="900" constant="5" id="Ft5-cy-2d0"/>
|
||||
<constraint firstAttribute="bottom" secondItem="r9w-8l-Zxj" secondAttribute="bottom" constant="30" id="G9u-kw-mVz"/>
|
||||
<constraint firstItem="AoF-Up-Yu5" firstAttribute="top" secondItem="r9w-8l-Zxj" secondAttribute="top" id="H4s-Y8-HAI"/>
|
||||
<constraint firstAttribute="trailing" secondItem="AoF-Up-Yu5" secondAttribute="trailing" constant="15" id="flB-AR-zo3"/>
|
||||
<constraint firstAttribute="bottom" secondItem="r9w-8l-Zxj" secondAttribute="bottom" priority="900" constant="5" id="n0r-vM-wKh"/>
|
||||
<constraint firstItem="r9w-8l-Zxj" firstAttribute="leading" secondItem="sK0-tB-H9u" secondAttribute="leading" constant="15" id="x13-gZ-cYp"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2nj-Va-L7c" userLabel="TopBar">
|
||||
<rect key="frame" x="0.0" y="0.0" width="428" height="100"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="n8H-om-4LV">
|
||||
<rect key="frame" x="398" y="75" width="25" height="25"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="39" id="7Rd-r8-zJP"/>
|
||||
<constraint firstAttribute="width" constant="39" id="mwK-hd-jIy"/>
|
||||
<constraint firstAttribute="height" constant="25" id="dyc-7x-bTk"/>
|
||||
<constraint firstAttribute="width" constant="25" id="tYx-Ul-0Pf"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Button" image="videoMuteButton"/>
|
||||
<state key="selected" image="videoMuteButtonSelected"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<state key="normal" title="●">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="didClickVideoMuteButton:" destination="t2c-G5-7AE" eventType="touchUpInside" id="Hza-Ft-NNB"/>
|
||||
<action selector="circleFloatBtnTapped:" destination="t2c-G5-7AE" eventType="touchUpInside" id="2mC-ry-Lis"/>
|
||||
</connections>
|
||||
</button>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9g2-IH-l8e" userLabel="controlButtonSpacer">
|
||||
<rect key="frame" x="396" y="0.0" width="18" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="750" constant="18" id="7f6-zT-RoW"/>
|
||||
<constraint firstAttribute="height" constant="80" id="cmy-aX-hM0"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="a4N-1U-1Yt" userLabel="controlButtonSpacer">
|
||||
<rect key="frame" x="328" y="0.0" width="18" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="750" constant="18" id="X8j-wu-KrQ"/>
|
||||
<constraint firstAttribute="height" constant="80" id="vkd-63-bmN"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GbA-j6-191" userLabel="controlButtonSpacer">
|
||||
<rect key="frame" x="259" y="0.0" width="18" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="750" constant="18" id="Bz0-tD-Q4d"/>
|
||||
<constraint firstAttribute="height" constant="80" id="Ovg-ap-S7E"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="h0g-z5-Y95" userLabel="controlButtonSpacer">
|
||||
<rect key="frame" x="143" y="0.0" width="18" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="80" id="Vaj-Va-jZn"/>
|
||||
<constraint firstAttribute="width" priority="750" constant="18" id="Wv5-ys-Ho4"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gop-Yl-2fm" userLabel="controlButtonSpacer">
|
||||
<rect key="frame" x="73" y="0.0" width="18" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="750" constant="18" id="iqX-Wz-fb1"/>
|
||||
<constraint firstAttribute="height" constant="80" id="j0P-xC-zSP"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JCG-W9-5ko" userLabel="controlButtonSpacer">
|
||||
<rect key="frame" x="6" y="0.0" width="18" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="750" constant="18" id="La6-ZY-Qs8"/>
|
||||
<constraint firstAttribute="height" constant="80" id="mf9-Qg-gVZ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="bvC-Fa-s3m">
|
||||
<rect key="frame" x="15" y="50" width="398" height="30"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Al-Anoud Al-Anoud" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Zct-62-YE6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="328" height="30"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="25"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8Xn-w6-Mox">
|
||||
<rect key="frame" x="338" y="3" width="60" height="24"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-- : --" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Btq-Ll-BPH">
|
||||
<rect key="frame" x="13.333333333333314" y="5.0000000000000009" width="33.666666666666664" height="14.333333333333336"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="12"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.0018553551427920477" green="0.59902576721627221" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="60" id="InL-xK-60G"/>
|
||||
<constraint firstItem="Btq-Ll-BPH" firstAttribute="centerY" secondItem="8Xn-w6-Mox" secondAttribute="centerY" id="Vbi-zv-vUQ"/>
|
||||
<constraint firstItem="Btq-Ll-BPH" firstAttribute="centerX" secondItem="8Xn-w6-Mox" secondAttribute="centerX" id="WNT-XA-AW6"/>
|
||||
<constraint firstAttribute="height" constant="24" id="Zck-PD-EN0"/>
|
||||
</constraints>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
|
||||
<integer key="value" value="12"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</view>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.18431372549019609" green="0.1764705882352941" blue="0.16470588235294117" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<constraints>
|
||||
<constraint firstItem="9g2-IH-l8e" firstAttribute="leading" secondItem="ygd-al-G4F" secondAttribute="trailing" constant="5" id="0Bv-9b-HQh"/>
|
||||
<constraint firstItem="a4N-1U-1Yt" firstAttribute="leading" secondItem="7vf-kV-eQ5" secondAttribute="trailing" constant="6" id="4vp-mw-gRE"/>
|
||||
<constraint firstAttribute="height" constant="80" id="5QG-54-ulG"/>
|
||||
<constraint firstItem="GbA-j6-191" firstAttribute="width" secondItem="h0g-z5-Y95" secondAttribute="width" id="5cw-b2-SPx"/>
|
||||
<constraint firstItem="h0g-z5-Y95" firstAttribute="width" secondItem="gop-Yl-2fm" secondAttribute="width" id="7oE-3V-2Gc"/>
|
||||
<constraint firstItem="7vf-kV-eQ5" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="8zH-6k-6Wz"/>
|
||||
<constraint firstItem="gop-Yl-2fm" firstAttribute="leading" secondItem="NUO-2p-pzL" secondAttribute="trailing" constant="5" id="Bbh-OY-I9f"/>
|
||||
<constraint firstItem="NUO-2p-pzL" firstAttribute="leading" secondItem="JCG-W9-5ko" secondAttribute="trailing" constant="5" id="Ccz-Qq-RXf"/>
|
||||
<constraint firstItem="gop-Yl-2fm" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="DE6-nZ-rEI"/>
|
||||
<constraint firstItem="GbA-j6-191" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="EVH-xy-wpT"/>
|
||||
<constraint firstItem="ygd-al-G4F" firstAttribute="leading" secondItem="a4N-1U-1Yt" secondAttribute="trailing" constant="6" id="FNy-Br-FIj"/>
|
||||
<constraint firstItem="NUO-2p-pzL" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="Fme-oB-F7i"/>
|
||||
<constraint firstItem="GbA-j6-191" firstAttribute="leading" secondItem="AoF-Up-Yu5" secondAttribute="trailing" constant="14" id="GxO-Fe-iqR"/>
|
||||
<constraint firstItem="JCG-W9-5ko" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="HPP-q4-IDT"/>
|
||||
<constraint firstItem="h0g-z5-Y95" firstAttribute="leading" secondItem="N2h-FX-kF5" secondAttribute="trailing" constant="6" id="I2J-pE-7yx"/>
|
||||
<constraint firstItem="7vf-kV-eQ5" firstAttribute="leading" secondItem="GbA-j6-191" secondAttribute="trailing" constant="6" id="MVo-If-UVQ"/>
|
||||
<constraint firstItem="a4N-1U-1Yt" firstAttribute="width" secondItem="GbA-j6-191" secondAttribute="width" id="Me9-IC-YE2"/>
|
||||
<constraint firstItem="N2h-FX-kF5" firstAttribute="leading" secondItem="gop-Yl-2fm" secondAttribute="trailing" constant="7" id="RHr-s8-sAg"/>
|
||||
<constraint firstItem="h0g-z5-Y95" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="RJj-Uh-H2E"/>
|
||||
<constraint firstItem="a4N-1U-1Yt" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="Tb2-6o-Wqh"/>
|
||||
<constraint firstItem="AoF-Up-Yu5" firstAttribute="leading" secondItem="h0g-z5-Y95" secondAttribute="trailing" constant="13" id="VZS-xZ-nbe"/>
|
||||
<constraint firstItem="NUO-2p-pzL" firstAttribute="leading" secondItem="JCG-W9-5ko" secondAttribute="trailing" constant="5" id="Z1j-5z-34E"/>
|
||||
<constraint firstItem="7vf-kV-eQ5" firstAttribute="leading" secondItem="GbA-j6-191" secondAttribute="trailing" constant="6" id="ZjD-GK-SkY"/>
|
||||
<constraint firstItem="ygd-al-G4F" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="bJA-GM-b2E"/>
|
||||
<constraint firstItem="ygd-al-G4F" firstAttribute="leading" secondItem="a4N-1U-1Yt" secondAttribute="trailing" constant="6" id="eFd-3F-A5f"/>
|
||||
<constraint firstItem="JCG-W9-5ko" firstAttribute="leading" secondItem="sK0-tB-H9u" secondAttribute="leading" constant="6" id="f0s-gb-347"/>
|
||||
<constraint firstItem="gop-Yl-2fm" firstAttribute="leading" secondItem="NUO-2p-pzL" secondAttribute="trailing" constant="5" id="frf-I5-Rs9"/>
|
||||
<constraint firstItem="gop-Yl-2fm" firstAttribute="width" secondItem="JCG-W9-5ko" secondAttribute="width" id="i84-jd-Hwr"/>
|
||||
<constraint firstItem="N2h-FX-kF5" firstAttribute="leading" secondItem="gop-Yl-2fm" secondAttribute="trailing" constant="7" id="jlv-lR-Nlf"/>
|
||||
<constraint firstItem="N2h-FX-kF5" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="nU8-Hw-s0U"/>
|
||||
<constraint firstItem="9g2-IH-l8e" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="pxq-U1-zP5"/>
|
||||
<constraint firstItem="AoF-Up-Yu5" firstAttribute="centerY" secondItem="sK0-tB-H9u" secondAttribute="centerY" id="ql2-fm-OeZ"/>
|
||||
<constraint firstItem="9g2-IH-l8e" firstAttribute="width" secondItem="a4N-1U-1Yt" secondAttribute="width" id="uXs-zM-gyf"/>
|
||||
<constraint firstItem="h0g-z5-Y95" firstAttribute="leading" secondItem="N2h-FX-kF5" secondAttribute="trailing" constant="6" id="uia-9X-UrW"/>
|
||||
<constraint firstItem="a4N-1U-1Yt" firstAttribute="leading" secondItem="7vf-kV-eQ5" secondAttribute="trailing" constant="6" id="vgH-Oj-HJc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="9g2-IH-l8e" secondAttribute="trailing" id="vng-6I-eYj"/>
|
||||
<constraint firstAttribute="height" constant="100" id="6eP-Zl-97q"/>
|
||||
<constraint firstAttribute="trailing" secondItem="n8H-om-4LV" secondAttribute="trailing" constant="5" id="7Uq-4Q-ywD"/>
|
||||
<constraint firstAttribute="bottom" secondItem="bvC-Fa-s3m" secondAttribute="bottom" priority="900" constant="5" id="HgY-Dw-B9R"/>
|
||||
<constraint firstAttribute="bottom" secondItem="n8H-om-4LV" secondAttribute="bottom" id="Nr2-zL-avJ"/>
|
||||
<constraint firstAttribute="bottom" secondItem="bvC-Fa-s3m" secondAttribute="bottom" constant="20" id="PWn-Sb-O8B"/>
|
||||
<constraint firstItem="bvC-Fa-s3m" firstAttribute="leading" secondItem="2nj-Va-L7c" secondAttribute="leading" constant="15" id="YH4-fV-fkQ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="bvC-Fa-s3m" secondAttribute="trailing" constant="15" id="oq6-Oi-i7g"/>
|
||||
<constraint firstAttribute="height" priority="900" constant="25" id="s8H-wK-WPB"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="videoMutedIndicator" translatesAutoresizingMaskIntoConstraints="NO" id="FEH-bC-Hnh" userLabel="localVideoMutedIndicator">
|
||||
<rect key="frame" x="276" y="235" width="36" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="30" id="1iv-w5-GDT"/>
|
||||
<constraint firstAttribute="width" constant="36" id="AwX-vQ-Ynn"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="VIH-1T-Ife" firstAttribute="top" secondItem="sK0-tB-H9u" secondAttribute="bottom" constant="42" id="1tT-KH-NEk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sK0-tB-H9u" secondAttribute="trailing" id="6K9-n9-ceJ"/>
|
||||
<constraint firstAttribute="bottom" secondItem="6C5-o5-R1n" secondAttribute="bottom" constant="22" id="76x-6M-xOb"/>
|
||||
<constraint firstItem="yS9-HT-vp8" firstAttribute="top" secondItem="sK0-tB-H9u" secondAttribute="bottom" constant="24" id="9zS-Fw-oaT"/>
|
||||
<constraint firstItem="eOT-Jr-GUl" firstAttribute="centerX" secondItem="PoR-7r-yNe" secondAttribute="centerX" id="HMH-Hc-ofZ"/>
|
||||
<constraint firstItem="FEH-bC-Hnh" firstAttribute="centerY" secondItem="0ZP-s2-kM5" secondAttribute="centerY" id="K8x-bd-bS9"/>
|
||||
<constraint firstItem="eOT-Jr-GUl" firstAttribute="width" secondItem="PoR-7r-yNe" secondAttribute="width" id="PH6-qw-V6p"/>
|
||||
<constraint firstItem="eOT-Jr-GUl" firstAttribute="centerY" secondItem="PoR-7r-yNe" secondAttribute="centerY" id="Prz-4m-LHB"/>
|
||||
<constraint firstItem="0ZP-s2-kM5" firstAttribute="width" secondItem="PoR-7r-yNe" secondAttribute="width" multiplier="0.25" id="QCm-L0-oF7"/>
|
||||
<constraint firstItem="sK0-tB-H9u" firstAttribute="leading" secondItem="PoR-7r-yNe" secondAttribute="leading" id="TPj-1d-lx6"/>
|
||||
<constraint firstItem="eOT-Jr-GUl" firstAttribute="leading" secondItem="PoR-7r-yNe" secondAttribute="leading" id="4xz-lj-CXd"/>
|
||||
<constraint firstAttribute="bottom" secondItem="eOT-Jr-GUl" secondAttribute="bottom" id="5Ae-F1-hSz"/>
|
||||
<constraint firstItem="WPH-QE-Fvp" firstAttribute="leading" secondItem="PoR-7r-yNe" secondAttribute="leading" id="5Yr-p4-csA"/>
|
||||
<constraint firstItem="2nj-Va-L7c" firstAttribute="leading" secondItem="PoR-7r-yNe" secondAttribute="leading" id="8Gx-Tb-bpo"/>
|
||||
<constraint firstItem="O5s-CJ-RFv" firstAttribute="leading" secondItem="PoR-7r-yNe" secondAttribute="leading" id="Hyn-9r-k8s"/>
|
||||
<constraint firstItem="sK0-tB-H9u" firstAttribute="leading" secondItem="PoR-7r-yNe" secondAttribute="leading" id="Itp-Pe-kh9"/>
|
||||
<constraint firstItem="2nj-Va-L7c" firstAttribute="top" secondItem="PoR-7r-yNe" secondAttribute="top" id="LDC-Rq-79f"/>
|
||||
<constraint firstItem="XHG-Db-3Px" firstAttribute="height" secondItem="eOT-Jr-GUl" secondAttribute="height" multiplier="0.196544" id="LwG-7m-Ihd"/>
|
||||
<constraint firstAttribute="trailing" secondItem="eOT-Jr-GUl" secondAttribute="trailing" id="Pcn-K9-H92"/>
|
||||
<constraint firstAttribute="bottom" secondItem="sK0-tB-H9u" secondAttribute="bottom" id="Tu5-Op-mnF"/>
|
||||
<constraint firstItem="CL1-2v-m1L" firstAttribute="centerY" secondItem="PoR-7r-yNe" secondAttribute="centerY" id="UGv-46-ri8"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="0ZP-s2-kM5" secondAttribute="trailing" constant="20" id="UZX-Yj-Efk"/>
|
||||
<constraint firstItem="A0G-cT-yji" firstAttribute="width" secondItem="0ZP-s2-kM5" secondAttribute="width" constant="55.5" id="WMH-vd-rQe"/>
|
||||
<constraint firstAttribute="trailing" secondItem="O5s-CJ-RFv" secondAttribute="trailing" id="VcP-8e-3Un"/>
|
||||
<constraint firstItem="CL1-2v-m1L" firstAttribute="centerX" secondItem="PoR-7r-yNe" secondAttribute="centerX" id="Xt8-k3-fDj"/>
|
||||
<constraint firstItem="FEH-bC-Hnh" firstAttribute="centerX" secondItem="0ZP-s2-kM5" secondAttribute="centerX" constant="-28.25" id="Yvq-7p-bjS"/>
|
||||
<constraint firstAttribute="bottom" secondItem="yS9-HT-vp8" secondAttribute="bottom" constant="22" id="bVF-wg-lH8"/>
|
||||
<constraint firstItem="6C5-o5-R1n" firstAttribute="leading" secondItem="PoR-7r-yNe" secondAttribute="leading" constant="14" id="der-54-SZ4"/>
|
||||
<constraint firstItem="A0G-cT-yji" firstAttribute="centerX" secondItem="0ZP-s2-kM5" secondAttribute="centerX" constant="-27.75" id="ga5-7D-8CU"/>
|
||||
<constraint firstItem="A0G-cT-yji" firstAttribute="height" secondItem="0ZP-s2-kM5" secondAttribute="height" id="hMV-RD-NwE"/>
|
||||
<constraint firstItem="A0G-cT-yji" firstAttribute="centerY" secondItem="0ZP-s2-kM5" secondAttribute="centerY" id="nBC-L0-5Pe"/>
|
||||
<constraint firstItem="0ZP-s2-kM5" firstAttribute="height" secondItem="PoR-7r-yNe" secondAttribute="height" multiplier="0.25" id="pq3-Iv-N4l"/>
|
||||
<constraint firstItem="0ZP-s2-kM5" firstAttribute="top" secondItem="BIa-yr-ZMY" secondAttribute="bottom" constant="50" id="qrs-H2-Rw4"/>
|
||||
<constraint firstItem="eOT-Jr-GUl" firstAttribute="height" secondItem="PoR-7r-yNe" secondAttribute="height" id="rbk-Lc-qip"/>
|
||||
<constraint firstAttribute="trailing" secondItem="yS9-HT-vp8" secondAttribute="trailing" constant="9" id="vEv-GY-lDH"/>
|
||||
<constraint firstItem="6C5-o5-R1n" firstAttribute="top" secondItem="sK0-tB-H9u" secondAttribute="bottom" constant="24" id="xhd-lu-lFH"/>
|
||||
<constraint firstItem="CL1-2v-m1L" firstAttribute="width" secondItem="PoR-7r-yNe" secondAttribute="width" multiplier="0.25" id="Yce-bz-JBd"/>
|
||||
<constraint firstItem="WPH-QE-Fvp" firstAttribute="top" secondItem="PoR-7r-yNe" secondAttribute="top" id="ddA-LD-2O3"/>
|
||||
<constraint firstItem="sK0-tB-H9u" firstAttribute="top" secondItem="O5s-CJ-RFv" secondAttribute="bottom" id="eOi-4p-iMm"/>
|
||||
<constraint firstAttribute="trailing" secondItem="WPH-QE-Fvp" secondAttribute="trailing" id="evw-Oc-GgJ"/>
|
||||
<constraint firstItem="O5s-CJ-RFv" firstAttribute="top" secondItem="2nj-Va-L7c" secondAttribute="bottom" id="fLH-dK-xpa"/>
|
||||
<constraint firstItem="eOT-Jr-GUl" firstAttribute="top" secondItem="PoR-7r-yNe" secondAttribute="top" id="gFm-X8-RIU"/>
|
||||
<constraint firstAttribute="trailing" secondItem="sK0-tB-H9u" secondAttribute="trailing" id="gSh-7K-3p5"/>
|
||||
<constraint firstAttribute="bottom" secondItem="WPH-QE-Fvp" secondAttribute="bottom" id="sYs-wp-cot"/>
|
||||
<constraint firstAttribute="trailing" secondItem="2nj-Va-L7c" secondAttribute="trailing" id="vQp-Bv-IVU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="s5b-6P-Hwj"/>
|
||||
<connections>
|
||||
<outlet property="btnMinimize" destination="UZ6-EY-a59" id="bDC-LS-qlX"/>
|
||||
<outlet property="btnScreenTap" destination="WPH-QE-Fvp" id="wiN-5a-WJO"/>
|
||||
<outlet property="camSwitchBtn" destination="lar-xK-rhR" id="tde-s5-Mji"/>
|
||||
<outlet property="controlButtons" destination="sK0-tB-H9u" id="mDK-cq-oQz"/>
|
||||
<outlet property="localVideo" destination="0ZP-s2-kM5" id="C68-k7-h5j"/>
|
||||
<outlet property="localVideoMutedBg" destination="A0G-cT-yji" id="5Ia-9g-7CT"/>
|
||||
<outlet property="localVideoMutedIndicator" destination="FEH-bC-Hnh" id="PKd-gQ-Jct"/>
|
||||
<outlet property="remoteVideo" destination="eOT-Jr-GUl" id="VKE-el-YGR"/>
|
||||
<outlet property="fullVideoView" destination="eOT-Jr-GUl" id="VKE-el-YGR"/>
|
||||
<outlet property="hideVideoBtn" destination="n8H-om-4LV" id="tTp-Ep-XKS"/>
|
||||
<outlet property="lblCallDuration" destination="Btq-Ll-BPH" id="Jdh-yK-PEx"/>
|
||||
<outlet property="lblRemoteUsername" destination="Zct-62-YE6" id="OQ0-mf-mvc"/>
|
||||
<outlet property="localVideoContainer" destination="O5s-CJ-RFv" id="bh9-Yr-UGv"/>
|
||||
<outlet property="localVideoMutedBg" destination="6y1-an-W7F" id="5FF-Ak-Vxb"/>
|
||||
<outlet property="micMuteBtn" destination="N2h-FX-kF5" id="zgK-g1-Ndr"/>
|
||||
<outlet property="remoteVideoMutedIndicator" destination="CL1-2v-m1L" id="aJ0-QC-m9Y"/>
|
||||
<outlet property="smallVideoView" destination="0ZP-s2-kM5" id="C68-k7-h5j"/>
|
||||
<outlet property="topBar" destination="2nj-Va-L7c" id="Zt4-J2-uw8"/>
|
||||
<outlet property="videoMuteBtn" destination="NUO-2p-pzL" id="XCc-nu-N8P"/>
|
||||
<outletCollection property="minimizeConstraint" destination="s8H-wK-WPB" collectionClass="NSMutableArray" id="XTM-jR-Cos"/>
|
||||
<outletCollection property="minimizeConstraint" destination="HgY-Dw-B9R" collectionClass="NSMutableArray" id="Fi8-YX-9iQ"/>
|
||||
<outletCollection property="minimizeConstraint" destination="Ft5-cy-2d0" collectionClass="NSMutableArray" id="Ze3-H8-gj4"/>
|
||||
<outletCollection property="minimizeConstraint" destination="n0r-vM-wKh" collectionClass="NSMutableArray" id="IaQ-TS-ng8"/>
|
||||
<outletCollection property="maximisedConstraint" destination="G9u-kw-mVz" collectionClass="NSMutableArray" id="TaK-5m-byK"/>
|
||||
<outletCollection property="maximisedConstraint" destination="00C-1i-sGc" collectionClass="NSMutableArray" id="vmx-rp-JZI"/>
|
||||
<outletCollection property="maximisedConstraint" destination="PWn-Sb-O8B" collectionClass="NSMutableArray" id="pK4-yA-36p"/>
|
||||
<outletCollection property="maximisedConstraint" destination="6eP-Zl-97q" collectionClass="NSMutableArray" id="dxk-ol-uAW"/>
|
||||
<outletCollection property="maximisedConstraint" destination="3L0-3h-Bxg" collectionClass="NSMutableArray" id="w3Q-AR-TQY"/>
|
||||
<outletCollection property="minimizeConstraint" destination="y90-hA-p5Z" collectionClass="NSMutableArray" id="INL-Bc-eTM"/>
|
||||
<outletCollection property="minimizeConstraint" destination="DgE-WQ-KQm" collectionClass="NSMutableArray" id="zy0-aI-bnY"/>
|
||||
<outletCollection property="maximisedConstraint" destination="flB-AR-zo3" collectionClass="NSMutableArray" id="hwu-U4-5s5"/>
|
||||
<outletCollection property="minimizeConstraint" destination="6B7-94-Myo" collectionClass="NSMutableArray" id="epM-Kg-2yi"/>
|
||||
<outletCollection property="maximisedConstraint" destination="x13-gZ-cYp" collectionClass="NSMutableArray" id="uLI-UQ-yIo"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x33-EE-6Ak" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1685.5072463768117" y="1435.7142857142856"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="GvA-TT-FAo">
|
||||
<objects>
|
||||
<navigationController restorationIdentifier="ViewControllerNav" storyboardIdentifier="ViewControllerNav" id="0WJ-RG-MEh" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="S7n-df-cyC">
|
||||
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="t2c-G5-7AE" kind="relationship" relationship="rootViewController" id="L74-If-nYJ"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="vSx-dK-kQe" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="880" y="1436"/>
|
||||
<point key="canvasLocation" x="-797.66355140186909" y="2238.6609071274302"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="hangUpButton" width="71" height="71"/>
|
||||
<image name="localVideoMutedBg" width="90" height="120"/>
|
||||
<image name="muteButton" width="39" height="39"/>
|
||||
<image name="muteButtonSelected" width="39" height="39"/>
|
||||
<image name="speakerOff" width="39" height="39"/>
|
||||
<image name="speakerOffSelected" width="39" height="39"/>
|
||||
<image name="switchCameraButton" width="39" height="39"/>
|
||||
<image name="switchCameraButtonSelected" width="39" height="39"/>
|
||||
<image name="videoMuteButton" width="39" height="39"/>
|
||||
<image name="videoMuteButtonSelected" width="39" height="39"/>
|
||||
<image name="camera_back" width="198" height="202"/>
|
||||
<image name="camera_front" width="198" height="202"/>
|
||||
<image name="end_call" width="488" height="488"/>
|
||||
<image name="expand_video" width="202" height="199"/>
|
||||
<image name="float_video" width="202" height="202"/>
|
||||
<image name="mic_mute" width="198" height="199"/>
|
||||
<image name="mic_unmute" width="198" height="202"/>
|
||||
<image name="videoMutedIndicator" width="153.5" height="129"/>
|
||||
<image name="video_mute" width="202" height="199"/>
|
||||
<image name="video_unmute" width="202" height="202"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
enum PatientType{
|
||||
IN_PATIENT,
|
||||
OUT_PATIENT,
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AlternativeService {
|
||||
int serviceID;
|
||||
String serviceName;
|
||||
bool isSelected;
|
||||
|
||||
AlternativeService(
|
||||
{this.serviceID, this.serviceName, this.isSelected = false});
|
||||
|
||||
AlternativeService.fromJson(Map<String, dynamic> json) {
|
||||
serviceID = json['ServicID'];
|
||||
serviceName = json['ServiceName'];
|
||||
isSelected = false;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['ServicID'] = this.serviceID;
|
||||
data['ServiceName'] = this.serviceName;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class AlternativeServicesList with ChangeNotifier {
|
||||
List<AlternativeService> _alternativeServicesList;
|
||||
|
||||
getServicesList(){
|
||||
return _alternativeServicesList;
|
||||
}
|
||||
|
||||
setServicesList(List<AlternativeService> alternativeServicesList) {
|
||||
this._alternativeServicesList = alternativeServicesList;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
setSelected(AlternativeService service, bool isSelected) {
|
||||
List<AlternativeService> alternativeService = _alternativeServicesList.where((element) => service.serviceID == element.serviceID).toList();
|
||||
|
||||
alternativeService[0].isSelected = isSelected;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
class LiveCareUserLoginRequestModel {
|
||||
String tokenID;
|
||||
String generalid;
|
||||
int doctorId;
|
||||
int isOutKsa;
|
||||
int isLogin;
|
||||
|
||||
LiveCareUserLoginRequestModel({this.tokenID, this.generalid, this.doctorId, this.isOutKsa, this.isLogin});
|
||||
|
||||
LiveCareUserLoginRequestModel.fromJson(Map<String, dynamic> json) {
|
||||
tokenID = json['TokenID'];
|
||||
generalid = json['generalid'];
|
||||
doctorId = json['DoctorId'];
|
||||
isOutKsa = json['IsOutKsa'];
|
||||
isLogin = json['IsLogin'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['TokenID'] = this.tokenID;
|
||||
data['generalid'] = this.generalid;
|
||||
data['DoctorId'] = this.doctorId;
|
||||
data['IsOutKsa'] = this.isOutKsa;
|
||||
data['IsLogin'] = this.isLogin;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
class MyReferralPatientRequestModel {
|
||||
int channel;
|
||||
int clinicID;
|
||||
int doctorID;
|
||||
int editedBy;
|
||||
String firstName;
|
||||
String from;
|
||||
String iPAdress;
|
||||
bool isLoginForDoctorApp;
|
||||
int languageID;
|
||||
String lastName;
|
||||
String middleName;
|
||||
int patientID;
|
||||
String patientIdentificationID;
|
||||
String patientMobileNumber;
|
||||
bool patientOutSA;
|
||||
int patientTypeID;
|
||||
int projectID;
|
||||
String sessionID;
|
||||
String stamp;
|
||||
String to;
|
||||
String tokenID;
|
||||
double versionID;
|
||||
String vidaAuthTokenID;
|
||||
|
||||
MyReferralPatientRequestModel(
|
||||
{this.channel,
|
||||
this.clinicID,
|
||||
this.doctorID,
|
||||
this.editedBy,
|
||||
this.firstName,
|
||||
this.from,
|
||||
this.iPAdress,
|
||||
this.isLoginForDoctorApp,
|
||||
this.languageID,
|
||||
this.lastName,
|
||||
this.middleName,
|
||||
this.patientID,
|
||||
this.patientIdentificationID,
|
||||
this.patientMobileNumber,
|
||||
this.patientOutSA,
|
||||
this.patientTypeID,
|
||||
this.projectID,
|
||||
this.sessionID,
|
||||
this.stamp,
|
||||
this.to,
|
||||
this.tokenID,
|
||||
this.versionID,
|
||||
this.vidaAuthTokenID});
|
||||
|
||||
MyReferralPatientRequestModel.fromJson(Map<String, dynamic> json) {
|
||||
channel = json['Channel'];
|
||||
clinicID = json['ClinicID'];
|
||||
doctorID = json['DoctorID'];
|
||||
editedBy = json['EditedBy'];
|
||||
firstName = json['FirstName'];
|
||||
from = json['From'];
|
||||
iPAdress = json['IPAdress'];
|
||||
isLoginForDoctorApp = json['IsLoginForDoctorApp'];
|
||||
languageID = json['LanguageID'];
|
||||
lastName = json['LastName'];
|
||||
middleName = json['MiddleName'];
|
||||
patientID = json['PatientID'];
|
||||
patientIdentificationID = json['PatientIdentificationID'];
|
||||
patientMobileNumber = json['PatientMobileNumber'];
|
||||
patientOutSA = json['PatientOutSA'];
|
||||
patientTypeID = json['PatientTypeID'];
|
||||
projectID = json['ProjectID'];
|
||||
sessionID = json['SessionID'];
|
||||
stamp = json['stamp'];
|
||||
to = json['To'];
|
||||
tokenID = json['TokenID'];
|
||||
versionID = json['VersionID'];
|
||||
vidaAuthTokenID = json['VidaAuthTokenID'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['Channel'] = this.channel;
|
||||
data['ClinicID'] = this.clinicID;
|
||||
data['DoctorID'] = this.doctorID;
|
||||
data['EditedBy'] = this.editedBy;
|
||||
data['FirstName'] = this.firstName;
|
||||
data['From'] = this.from;
|
||||
data['IPAdress'] = this.iPAdress;
|
||||
data['IsLoginForDoctorApp'] = this.isLoginForDoctorApp;
|
||||
data['LanguageID'] = this.languageID;
|
||||
data['LastName'] = this.lastName;
|
||||
data['MiddleName'] = this.middleName;
|
||||
data['PatientID'] = this.patientID;
|
||||
data['PatientIdentificationID'] = this.patientIdentificationID;
|
||||
data['PatientMobileNumber'] = this.patientMobileNumber;
|
||||
data['PatientOutSA'] = this.patientOutSA;
|
||||
data['PatientTypeID'] = this.patientTypeID;
|
||||
data['ProjectID'] = this.projectID;
|
||||
data['SessionID'] = this.sessionID;
|
||||
data['stamp'] = this.stamp;
|
||||
data['To'] = this.to;
|
||||
data['TokenID'] = this.tokenID;
|
||||
data['VersionID'] = this.versionID;
|
||||
data['VidaAuthTokenID'] = this.vidaAuthTokenID;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
class AddReferredRemarksRequestModel {
|
||||
int projectID;
|
||||
int admissionNo;
|
||||
int lineItemNo;
|
||||
String referredDoctorRemarks;
|
||||
int editedBy;
|
||||
int referalStatus;
|
||||
bool isLoginForDoctorApp;
|
||||
String iPAdress;
|
||||
bool patientOutSA;
|
||||
String tokenID;
|
||||
int languageID;
|
||||
double versionID;
|
||||
int channel;
|
||||
String sessionID;
|
||||
int deviceTypeID;
|
||||
|
||||
AddReferredRemarksRequestModel(
|
||||
{this.projectID,
|
||||
this.admissionNo,
|
||||
this.lineItemNo,
|
||||
this.referredDoctorRemarks,
|
||||
this.editedBy,
|
||||
this.referalStatus,
|
||||
this.isLoginForDoctorApp,
|
||||
this.iPAdress,
|
||||
this.patientOutSA,
|
||||
this.tokenID,
|
||||
this.languageID,
|
||||
this.versionID,
|
||||
this.channel,
|
||||
this.sessionID,
|
||||
this.deviceTypeID});
|
||||
|
||||
AddReferredRemarksRequestModel.fromJson(Map<String, dynamic> json) {
|
||||
projectID = json['ProjectID'];
|
||||
admissionNo = json['AdmissionNo'];
|
||||
lineItemNo = json['LineItemNo'];
|
||||
referredDoctorRemarks = json['ReferredDoctorRemarks'];
|
||||
editedBy = json['EditedBy'];
|
||||
referalStatus = json['ReferalStatus'];
|
||||
isLoginForDoctorApp = json['IsLoginForDoctorApp'];
|
||||
iPAdress = json['IPAdress'];
|
||||
patientOutSA = json['PatientOutSA'];
|
||||
tokenID = json['TokenID'];
|
||||
languageID = json['LanguageID'];
|
||||
versionID = json['VersionID'];
|
||||
channel = json['Channel'];
|
||||
sessionID = json['SessionID'];
|
||||
deviceTypeID = json['DeviceTypeID'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['ProjectID'] = this.projectID;
|
||||
data['AdmissionNo'] = this.admissionNo;
|
||||
data['LineItemNo'] = this.lineItemNo;
|
||||
data['ReferredDoctorRemarks'] = this.referredDoctorRemarks;
|
||||
data['EditedBy'] = this.editedBy;
|
||||
data['ReferalStatus'] = this.referalStatus;
|
||||
data['IsLoginForDoctorApp'] = this.isLoginForDoctorApp;
|
||||
data['IPAdress'] = this.iPAdress;
|
||||
data['PatientOutSA'] = this.patientOutSA;
|
||||
data['TokenID'] = this.tokenID;
|
||||
data['LanguageID'] = this.languageID;
|
||||
data['VersionID'] = this.versionID;
|
||||
data['Channel'] = this.channel;
|
||||
data['SessionID'] = this.sessionID;
|
||||
data['DeviceTypeID'] = this.deviceTypeID;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NavigationService {
|
||||
final GlobalKey<NavigatorState> navigatorKey =
|
||||
new GlobalKey<NavigatorState>();
|
||||
Future<dynamic> navigateTo(String routeName,{Object arguments}) {
|
||||
return navigatorKey.currentState.pushNamed(routeName,arguments: arguments);
|
||||
}
|
||||
|
||||
Future<dynamic> pushReplacementNamed(String routeName,{Object arguments}) {
|
||||
return navigatorKey.currentState.pushReplacementNamed(routeName,arguments: arguments);
|
||||
}
|
||||
|
||||
|
||||
Future<dynamic> pushNamedAndRemoveUntil(String routeName) {
|
||||
return navigatorKey.currentState.pushNamedAndRemoveUntil(routeName,(asd)=>false);
|
||||
}
|
||||
}
|
||||
@ -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<LiveCarePatientServices>();
|
||||
|
||||
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<NavigationService>().navigatorKey.currentContext);
|
||||
endCall(patient.vcId, false,).then((value) {
|
||||
GifLoaderDialogUtils.hideDialog(locator<NavigationService>().navigatorKey.currentContext);
|
||||
if (hasError) {
|
||||
DrAppToastMsg.showErrorToast(error);
|
||||
}else
|
||||
locator<NavigationService>().navigateTo(PATIENTS_END_Call,arguments: {
|
||||
"patient": patient,
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
},
|
||||
onCallNotRespond: (SessionStatusModel sessionStatusModel) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
GifLoaderDialogUtils.showMyDialog(locator<NavigationService>().navigatorKey.currentContext);
|
||||
endCall(patient.vcId, sessionStatusModel.sessionStatus == 3,).then((value) {
|
||||
GifLoaderDialogUtils.hideDialog(locator<NavigationService>().navigatorKey.currentContext);
|
||||
if (hasError) {
|
||||
DrAppToastMsg.showErrorToast(error);
|
||||
} else {
|
||||
locator<NavigationService>().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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import 'package:doctor_app_flutter/config/config.dart';
|
||||
import 'package:doctor_app_flutter/core/model/patient_muse/PatientSearchRequestModel.dart';
|
||||
import 'package:doctor_app_flutter/core/service/base/base_service.dart';
|
||||
import 'package:doctor_app_flutter/models/patient/patiant_info_model.dart';
|
||||
|
||||
class ScanQrService extends BaseService {
|
||||
List<PatiantInformtion> myInPatientList = List();
|
||||
List<PatiantInformtion> inPatientList = List();
|
||||
|
||||
Future getInPatient(PatientSearchRequestModel requestModel, bool isMyInpatient) async {
|
||||
hasError = false;
|
||||
await getDoctorProfile();
|
||||
|
||||
if (isMyInpatient) {
|
||||
requestModel.doctorID = doctorProfile.doctorID;
|
||||
} else {
|
||||
requestModel.doctorID = 0;
|
||||
}
|
||||
|
||||
await baseAppClient.post(
|
||||
GET_PATIENT_IN_PATIENT_LIST,
|
||||
onSuccess: (dynamic response, int statusCode) {
|
||||
inPatientList.clear();
|
||||
myInPatientList.clear();
|
||||
|
||||
response['List_MyInPatient'].forEach((v) {
|
||||
PatiantInformtion patient = PatiantInformtion.fromJson(v);
|
||||
inPatientList.add(patient);
|
||||
if (patient.doctorId == doctorProfile.doctorID) {
|
||||
myInPatientList.add(patient);
|
||||
}
|
||||
});
|
||||
},
|
||||
onFailure: (String error, int statusCode) {
|
||||
hasError = true;
|
||||
super.error = error;
|
||||
},
|
||||
body: requestModel.toJson(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import 'package:doctor_app_flutter/config/config.dart';
|
||||
import 'package:doctor_app_flutter/core/service/base/base_service.dart';
|
||||
import 'package:doctor_app_flutter/models/doctor/verify_referral_doctor_remarks.dart';
|
||||
import 'package:doctor_app_flutter/models/patient/my_referral/my_referred_patient_model.dart';
|
||||
import 'package:doctor_app_flutter/models/patient/request_my_referral_patient_model.dart';
|
||||
|
||||
class ReferredPatientService extends BaseService {
|
||||
List<MyReferredPatientModel> _listMyReferredPatientModel = [];
|
||||
|
||||
List<MyReferredPatientModel> get listMyReferredPatientModel =>
|
||||
_listMyReferredPatientModel;
|
||||
|
||||
RequestMyReferralPatientModel _requestMyReferralPatient =
|
||||
RequestMyReferralPatientModel();
|
||||
VerifyReferralDoctorRemarks _verifyreferraldoctorremarks =
|
||||
VerifyReferralDoctorRemarks();
|
||||
|
||||
Future getMyReferredPatient() async {
|
||||
await baseAppClient.post(
|
||||
GET_MY_REFERRED_PATIENT,
|
||||
onSuccess: (dynamic response, int statusCode) {
|
||||
_listMyReferredPatientModel.clear();
|
||||
response['List_MyReferredPatient'].forEach((v) {
|
||||
listMyReferredPatientModel.add(MyReferredPatientModel.fromJson(v));
|
||||
});
|
||||
// print(response['List_MyReferredPatient']);
|
||||
},
|
||||
onFailure: (String error, int statusCode) {
|
||||
hasError = true;
|
||||
super.error = error;
|
||||
},
|
||||
body: _requestMyReferralPatient.toJson(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import 'package:doctor_app_flutter/config/config.dart';
|
||||
import 'package:doctor_app_flutter/core/service/base/base_service.dart';
|
||||
import 'package:doctor_app_flutter/models/dashboard/dashboard_model.dart';
|
||||
import 'package:doctor_app_flutter/models/dashboard/get_special_clinical_care_List_Respose_Model.dart';
|
||||
import 'package:doctor_app_flutter/models/dashboard/get_special_clinical_care_mapping_List_Respose_Model.dart';
|
||||
|
||||
class SpecialClinicsService extends BaseService {
|
||||
|
||||
|
||||
List<GetSpecialClinicalCareListResponseModel> _specialClinicalCareList = [];
|
||||
List<GetSpecialClinicalCareListResponseModel> get specialClinicalCareList => _specialClinicalCareList;
|
||||
|
||||
List<GetSpecialClinicalCareMappingListResponseModel> _specialClinicalCareMappingList = [];
|
||||
List<GetSpecialClinicalCareMappingListResponseModel> get specialClinicalCareMappingList => _specialClinicalCareMappingList;
|
||||
Future getSpecialClinicalCareList() async {
|
||||
hasError = false;
|
||||
await baseAppClient.post(
|
||||
GET_SPECIAL_CLINICAL_CARE_LIST,
|
||||
onSuccess: (dynamic response, int statusCode) {
|
||||
|
||||
_specialClinicalCareList.clear();
|
||||
response['List_SpecialClinicalCareList'].forEach((v) {
|
||||
_specialClinicalCareList.add(GetSpecialClinicalCareListResponseModel.fromJson(v));
|
||||
});},
|
||||
onFailure: (String error, int statusCode) {
|
||||
hasError = true;
|
||||
super.error = error;
|
||||
},
|
||||
body: {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Future getSpecialClinicalCareMappingList(int clinicId) async {
|
||||
hasError = false;
|
||||
await baseAppClient.post(
|
||||
GET_SPECIAL_CLINICAL_CARE_MAPPING_LIST,
|
||||
onSuccess: (dynamic response, int statusCode) {
|
||||
|
||||
_specialClinicalCareMappingList.clear();
|
||||
response['List_SpecialClinicalCareMappingList'].forEach((v) {
|
||||
_specialClinicalCareMappingList.add(GetSpecialClinicalCareMappingListResponseModel.fromJson(v));
|
||||
});},
|
||||
onFailure: (String error, int statusCode) {
|
||||
hasError = true;
|
||||
super.error = error;
|
||||
},
|
||||
body: {
|
||||
"ClinicID": clinicId,
|
||||
"DoctorID":0,
|
||||
"EditedBy":0
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import 'package:doctor_app_flutter/core/enum/viewstate.dart';
|
||||
import 'package:doctor_app_flutter/core/service/patient/referred_patient_service.dart';
|
||||
import 'package:doctor_app_flutter/models/patient/my_referral/my_referred_patient_model.dart';
|
||||
|
||||
import '../../locator.dart';
|
||||
import 'base_view_model.dart';
|
||||
|
||||
class ReferredPatientViewModel extends BaseViewModel {
|
||||
ReferredPatientService _referralPatientService = locator<ReferredPatientService>();
|
||||
|
||||
List<MyReferredPatientModel> get listMyReferredPatientModel => _referralPatientService.listMyReferredPatientModel;
|
||||
|
||||
Future getMyReferredPatient() async {
|
||||
setState(ViewState.Busy);
|
||||
await _referralPatientService.getMyReferredPatient();
|
||||
if (_referralPatientService.hasError) {
|
||||
error = _referralPatientService.error!;
|
||||
setState(ViewState.Error);
|
||||
} else
|
||||
setState(ViewState.Idle);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
import 'package:doctor_app_flutter/core/enum/viewstate.dart';
|
||||
import 'package:doctor_app_flutter/core/model/patient_muse/PatientSearchRequestModel.dart';
|
||||
import 'package:doctor_app_flutter/core/service/home/scan_qr_service.dart';
|
||||
import 'package:doctor_app_flutter/core/viewModel/base_view_model.dart';
|
||||
import 'package:doctor_app_flutter/locator.dart';
|
||||
import 'package:doctor_app_flutter/models/patient/patiant_info_model.dart';
|
||||
|
||||
class ScanQrViewModel extends BaseViewModel {
|
||||
ScanQrService _scanQrService = locator<ScanQrService>();
|
||||
List<PatiantInformtion> get inPatientList => _scanQrService.inPatientList;
|
||||
|
||||
Future getInPatientList(PatientSearchRequestModel requestModel, {bool isMyInpatient = false}) async {
|
||||
await getDoctorProfile();
|
||||
setState(ViewState.Busy);
|
||||
|
||||
await _scanQrService.getInPatient(requestModel, true);
|
||||
if (_scanQrService.hasError) {
|
||||
error = _scanQrService.error;
|
||||
|
||||
setState(ViewState.ErrorLocal);
|
||||
} else {
|
||||
// setDefaultInPatientList();
|
||||
setState(ViewState.Idle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
class GetSpecialClinicalCareListResponseModel {
|
||||
int projectID;
|
||||
int clinicID;
|
||||
String clinicDescription;
|
||||
String clinicDescriptionN;
|
||||
bool isActive;
|
||||
|
||||
GetSpecialClinicalCareListResponseModel(
|
||||
{this.projectID,
|
||||
this.clinicID,
|
||||
this.clinicDescription,
|
||||
this.clinicDescriptionN,
|
||||
this.isActive});
|
||||
|
||||
GetSpecialClinicalCareListResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
projectID = json['ProjectID'];
|
||||
clinicID = json['ClinicID'];
|
||||
clinicDescription = json['ClinicDescription'];
|
||||
clinicDescriptionN = json['ClinicDescriptionN'];
|
||||
isActive = json['IsActive'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['ProjectID'] = this.projectID;
|
||||
data['ClinicID'] = this.clinicID;
|
||||
data['ClinicDescription'] = this.clinicDescription;
|
||||
data['ClinicDescriptionN'] = this.clinicDescriptionN;
|
||||
data['IsActive'] = this.isActive;
|
||||
return data;
|
||||
}
|
||||
}
|
||||