diff --git a/android/app/build.gradle b/android/app/build.gradle index 1aa8b742..048dea59 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -61,7 +61,15 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.appcompat:appcompat:1.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + //openTok + implementation 'com.opentok.android:opentok-android-sdk:2.16.5' + //permissions + implementation 'pub.devrel:easypermissions:0.4.0' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e6d9a9ad..fda3b312 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,32 +1,41 @@ + - - + FlutterApplication and put your custom class here. + --> + + + android:icon="@mipmap/ic_launcher" + android:label="doctor_app_flutter"> + - - + + + - + - + + \ No newline at end of file diff --git a/android/app/src/main/java/com/example/doctor_app_flutter/VideoCallActivity.java b/android/app/src/main/java/com/example/doctor_app_flutter/VideoCallActivity.java new file mode 100644 index 00000000..146a3d54 --- /dev/null +++ b/android/app/src/main/java/com/example/doctor_app_flutter/VideoCallActivity.java @@ -0,0 +1,390 @@ +package com.example.doctor_app_flutter; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.os.Handler; +import android.util.Log; +import android.view.MotionEvent; +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.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 { + + + private static final String TAG = VideoCallActivity.class.getSimpleName(); + + 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; + private Runnable mVolRunnable; + + private FrameLayout mPublisherViewContainer; + private RelativeLayout mSubscriberViewContainer; + private RelativeLayout controlPanel; + + private String apiKey; + private String sessionId; + private String token; + private String callDuration; + private String warningDuration; + private String appLang; + + 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; + + + @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"); + // callDuration = getIntent().getStringExtra("callDuration"); + // warningDuration = getIntent().getStringExtra("warningDuration"); + appLang=getIntent().getStringExtra("appLang"); + + controlPanel=findViewById(R.id.control_panel); + + 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(); + + 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 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 perms) { + Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size()); + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size()); + + if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { + new AppSettingsDialog.Builder(this) + .setTitle(getString(R.string.title_settings_dialog)) + .setRationale(getString(R.string.rationale_ask_again)) + .setPositiveButton(getString(R.string.setting)) + .setNegativeButton(getString(R.string.cancel)) + .setRequestCode(RC_SETTINGS_SCREEN_PERM) + .build() + .show(); + } + } + + @AfterPermissionGranted(RC_VIDEO_APP_PERM) + private void requestPermissions() { + String[] perms = {Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}; + if (EasyPermissions.hasPermissions(this, perms)) { + mSession = new Session.Builder(VideoCallActivity.this, apiKey, sessionId).build(); + mSession.setSessionListener(this); + mSession.connect(token); + } 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) { + return; + } + subscribeToStream(stream); + } + + @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) { + 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(); + } + 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(); + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/doctor_app_flutter/MainActivity.kt b/android/app/src/main/kotlin/com/example/doctor_app_flutter/MainActivity.kt index ce684883..d1d710ad 100644 --- a/android/app/src/main/kotlin/com/example/doctor_app_flutter/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/doctor_app_flutter/MainActivity.kt @@ -1,13 +1,45 @@ package com.example.doctor_app_flutter +import android.content.Intent import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugins.GeneratedPluginRegistrant import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterFragmentActivity() { - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - GeneratedPluginRegistrant.registerWith(flutterEngine); + + private val CHANNEL = "Dr.cloudSolution/videoCall" + + + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + GeneratedPluginRegistrant.registerWith(flutterEngine) + + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { + call, result -> + if (call.method == "openVideoCall") { + val apiKey = call.argument("kApiKey") + val sessionId = call.argument("kSessionId") + val token = call.argument("kToken") + // val callDuration = call.argument("callDuration") + // val warningDuration = call.argument("warningDuration") + val appLang = call.argument("appLang") + openVideoCall(apiKey,sessionId,token/*,callDuration,warningDuration*/,appLang) + } else { + result.notImplemented() + } + } + } + + private fun openVideoCall(apiKey: String?, sessionId: String?, token: String?/*, callDuration: String?, warningDuration: String?*/, appLang: String?) { + val intent = Intent(this, VideoCallActivity::class.java) + intent.putExtra("apiKey", apiKey) + intent.putExtra("sessionId", sessionId) + intent.putExtra("token", token) + // intent.putExtra("callDuration", callDuration) + //intent.putExtra("warningDuration", warningDuration) + intent.putExtra("appLang", appLang) + startActivity(intent) } } diff --git a/android/app/src/main/res/drawable/audio_disabled.png b/android/app/src/main/res/drawable/audio_disabled.png new file mode 100644 index 00000000..3afc77ce Binary files /dev/null and b/android/app/src/main/res/drawable/audio_disabled.png differ diff --git a/android/app/src/main/res/drawable/audio_enabled.png b/android/app/src/main/res/drawable/audio_enabled.png new file mode 100644 index 00000000..f2e25f75 Binary files /dev/null and b/android/app/src/main/res/drawable/audio_enabled.png differ diff --git a/android/app/src/main/res/drawable/call.png b/android/app/src/main/res/drawable/call.png new file mode 100644 index 00000000..fc00f4f9 Binary files /dev/null and b/android/app/src/main/res/drawable/call.png differ diff --git a/android/app/src/main/res/drawable/flip_disapled.png b/android/app/src/main/res/drawable/flip_disapled.png new file mode 100644 index 00000000..5226029e Binary files /dev/null and b/android/app/src/main/res/drawable/flip_disapled.png differ diff --git a/android/app/src/main/res/drawable/flip_enabled.png b/android/app/src/main/res/drawable/flip_enabled.png new file mode 100644 index 00000000..152dc10e Binary files /dev/null and b/android/app/src/main/res/drawable/flip_enabled.png differ diff --git a/android/app/src/main/res/drawable/launch_image.png b/android/app/src/main/res/drawable/launch_image.png new file mode 100644 index 00000000..a6d723fa Binary files /dev/null and b/android/app/src/main/res/drawable/launch_image.png differ diff --git a/android/app/src/main/res/drawable/mic_disabled.png b/android/app/src/main/res/drawable/mic_disabled.png new file mode 100644 index 00000000..3603df75 Binary files /dev/null and b/android/app/src/main/res/drawable/mic_disabled.png differ diff --git a/android/app/src/main/res/drawable/mic_enabled.png b/android/app/src/main/res/drawable/mic_enabled.png new file mode 100644 index 00000000..5d9aa677 Binary files /dev/null and b/android/app/src/main/res/drawable/mic_enabled.png differ diff --git a/android/app/src/main/res/drawable/video_disanabled.png b/android/app/src/main/res/drawable/video_disanabled.png new file mode 100644 index 00000000..5c20c7bd Binary files /dev/null and b/android/app/src/main/res/drawable/video_disanabled.png differ diff --git a/android/app/src/main/res/drawable/video_enabled.png b/android/app/src/main/res/drawable/video_enabled.png new file mode 100644 index 00000000..23331e30 Binary files /dev/null and b/android/app/src/main/res/drawable/video_enabled.png differ diff --git a/android/app/src/main/res/drawable/video_off_fill.png b/android/app/src/main/res/drawable/video_off_fill.png new file mode 100644 index 00000000..d153439f Binary files /dev/null and b/android/app/src/main/res/drawable/video_off_fill.png differ diff --git a/android/app/src/main/res/layout/activity_video_call.xml b/android/app/src/main/res/layout/activity_video_call.xml new file mode 100644 index 00000000..c7500b6c --- /dev/null +++ b/android/app/src/main/res/layout/activity_video_call.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..d1d2a305 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + + + #ffffff + #303F9F + #fc3850 + #e4e9f2 + + + #827b92 + #484258 + diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..79f3d269 --- /dev/null +++ b/android/app/src/main/res/values/dimens.xml @@ -0,0 +1,22 @@ + + + 16dp + 16dp + 28dp + 24dp + + + 60dp + 54dp + + + 88dp + 117dp + 50dp + 100dp + 90dp + + + 24dp + 25dp + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..74349756 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + + Remaining Time In Seconds: + الوقت المتبقي بالثانيه: + Settings + Cancel + + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 00fa4417..24355fae 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -5,4 +5,15 @@ Flutter draws its first frame --> @drawable/launch_background + + + + diff --git a/android/build.gradle b/android/build.gradle index 3100ad2d..357c6a1a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -15,6 +15,7 @@ allprojects { repositories { google() jcenter() + maven { url 'https://tokbox.bintray.com/maven' } } } diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart index b4448d23..32aedeff 100644 --- a/lib/screens/dashboard_screen.dart +++ b/lib/screens/dashboard_screen.dart @@ -11,6 +11,7 @@ import 'package:doctor_app_flutter/providers/medicine_provider.dart'; import 'package:doctor_app_flutter/providers/referral_patient_provider.dart'; import 'package:doctor_app_flutter/providers/referred_patient_provider.dart'; import 'package:doctor_app_flutter/screens/medicine/medicine_search_screen.dart'; +import 'package:doctor_app_flutter/util/VideoChannel.dart'; import 'package:doctor_app_flutter/util/dr_app_shared_pref.dart'; import 'package:doctor_app_flutter/util/helpers.dart'; import 'package:doctor_app_flutter/util/translations_delegate_base.dart'; @@ -82,9 +83,16 @@ class _DashboardScreenState extends State { ), InkWell( onTap: () { - showCupertinoPicker( - context: context, - actionList: authProvider.doctorsClinicList); + + //TODO Change the location of it + + VideoChannel.openVideoCallScreen(kApiKey: '46803224', + kSessionId: '1_MX40NjgwMzIyNH5-MTU5Mjk4ODM4NTIwMH5tdjZpNmY2S2REa3c0ZUFobWxpS2RVdmt-fg', + kToken: 'T1==cGFydG5lcl9pZD00NjgwMzIyNCZzaWc9MzM4NWRkMjEzOTU5ZTdhYzliM2M0MTI1YTBkYzI4MDNhNTJhNjQ0ZTpzZXNzaW9uX2lkPTFfTVg0ME5qZ3dNekl5Tkg1LU1UVTVNams0T0RNNE5USXdNSDV0ZGpacE5tWTJTMlJFYTNjMFpVRm9iV3hwUzJSVmRtdC1mZyZjcmVhdGVfdGltZT0xNTkyOTg4NDE3Jm5vbmNlPTAuMzQ1MjQ4NTU3MzgxNzY4NSZyb2xlPXB1Ymxpc2hlciZleHBpcmVfdGltZT0xNTkzMDc0ODE2JmluaXRpYWxfbGF5b3V0X2NsYXNzX2xpc3Q9',); + +// showCupertinoPicker( +// context: context, +// actionList: authProvider.doctorsClinicList); }, child: Container( margin: diff --git a/lib/util/VideoChannel.dart b/lib/util/VideoChannel.dart new file mode 100644 index 00000000..c5bf278e --- /dev/null +++ b/lib/util/VideoChannel.dart @@ -0,0 +1,29 @@ + +import 'package:flutter/services.dart'; + +class VideoChannel{ + /// channel name + static const _channel = const MethodChannel("Dr.cloudSolution/videoCall"); + static Future openVideoCallScreen( + {kApiKey, kSessionId, kToken, callDuration, warningDuration}) { + var result; + try { + result = _channel.invokeMethod( + 'openVideoCall', + { + "kApiKey": kApiKey, + "kSessionId": kSessionId, + "kToken": kToken, + /* "callDuration": callDuration, + "warningDuration": warningDuration,*/ + "appLang": "en", + }, + ); + } on PlatformException catch (e) { + result = e.toString(); + } + return result; + } + + +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index cafaf489..71445e35 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -127,13 +127,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" charcode: dependency: transitive description: @@ -162,13 +155,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" code_builder: dependency: transitive description: @@ -260,13 +246,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.4" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" fixnum: dependency: transitive description: @@ -371,6 +350,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.12" imei_plugin: dependency: "direct main" description: @@ -482,7 +468,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.6.4" pedantic: dependency: transitive description: @@ -511,6 +497,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" platform: dependency: transitive description: @@ -683,7 +676,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.16" + version: "0.2.15" timing: dependency: transitive description: @@ -747,6 +740,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.1" yaml: dependency: transitive description: @@ -755,5 +755,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=2.9.0-14.0.dev <3.0.0" + dart: ">=2.7.0 <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0"