mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-06-22 10:02:20 +00:00
feat: floating window shows live remote screen
- FloatingWindowService now runs as foreground service (prevents process kill) - Dart pushes frames at 10fps to native via method channel - Frames resized to 160x160 and decoded as Bitmap in Kotlin - Added update_floating_frame method channel handler - Connection stays alive when activating floating window mode
This commit is contained in:
parent
cd2fff0655
commit
0f4d0f5351
9 changed files with 260 additions and 27 deletions
|
|
@ -117,7 +117,7 @@ android {
|
|||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.release
|
||||
signingConfig signingConfigs.debug
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
|
@ -92,7 +93,12 @@
|
|||
|
||||
<service
|
||||
android:name=".FloatingWindowService"
|
||||
android:enabled="true" />
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="specialUse">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="Maintains remote desktop connection while overlay is active" />
|
||||
</service>
|
||||
|
||||
<!--
|
||||
Don't delete the meta-data below.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package com.carriez.flutter_hbb
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
|
|
@ -25,8 +28,10 @@ import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
|||
import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.caverock.androidsvg.SVG
|
||||
import ffi.FFI
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.abs
|
||||
|
||||
class FloatingWindowService : Service(), View.OnTouchListener {
|
||||
|
|
@ -46,6 +51,7 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||
|
||||
companion object {
|
||||
private val logTag = "floatingService"
|
||||
private const val NOTIFY_ID_FLOATING = 200
|
||||
private var firstCreate = true
|
||||
private var viewWidth = 120
|
||||
private var viewHeight = 120
|
||||
|
|
@ -57,6 +63,13 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||
private var lastLayoutX = 0
|
||||
private var lastLayoutY = 0
|
||||
private var lastOrientation = Configuration.ORIENTATION_UNDEFINED
|
||||
|
||||
var instance: FloatingWindowService? = null
|
||||
private set
|
||||
|
||||
fun updateFrame(rgbaBytes: ByteArray, width: Int, height: Int) {
|
||||
instance?.updateFrameInternal(rgbaBytes, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
|
|
@ -65,6 +78,8 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
startForegroundWithNotification()
|
||||
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
|
||||
try {
|
||||
if (firstCreate) {
|
||||
|
|
@ -82,10 +97,70 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
instance = null
|
||||
if (viewCreated) {
|
||||
windowManager.removeView(floatingView)
|
||||
}
|
||||
handler.removeCallbacks(runnable)
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
|
||||
private fun updateFrameInternal(rgbaBytes: ByteArray, width: Int, height: Int) {
|
||||
try {
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(rgbaBytes))
|
||||
runOnUiThread {
|
||||
floatingView.setImageBitmap(bitmap)
|
||||
floatingView.alpha = viewTransparency * 1f
|
||||
// Ensure full width when showing frame content
|
||||
if (layoutParams.width == viewWidth / 2) {
|
||||
layoutParams.width = viewWidth
|
||||
windowManager.updateViewLayout(floatingView, layoutParams)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(logTag, "updateFrame failed: $e")
|
||||
}
|
||||
}
|
||||
|
||||
private fun runOnUiThread(runnable: Runnable) {
|
||||
Handler(Looper.getMainLooper()).post(runnable)
|
||||
}
|
||||
|
||||
private fun startForegroundWithNotification() {
|
||||
val channelId = "rustdesk_floating"
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
translate("RustDesk"),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = translate("RustDesk")
|
||||
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
||||
}
|
||||
val notificationManager = getSystemService(NotificationManager::class.java)
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this, 0,
|
||||
Intent(this, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(this, channelId)
|
||||
.setSmallIcon(R.mipmap.ic_stat_logo)
|
||||
.setContentTitle(translate("RustDesk"))
|
||||
.setContentText(translate("Service is running"))
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.build()
|
||||
|
||||
startForeground(NOTIFY_ID_FLOATING, notification)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
|
|
@ -312,7 +387,7 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||
}
|
||||
val idStopService = 2
|
||||
val hideStopService = FFI.getBuildinOption("hide-stop-service") == "Y"
|
||||
if (!hideStopService) {
|
||||
if (!hideStopService && MainService.isReady) {
|
||||
popupMenu.menu.add(0, idStopService, 0, translate("Stop service"))
|
||||
}
|
||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package com.carriez.flutter_hbb
|
|||
|
||||
import ffi.FFI
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
|
@ -39,6 +40,7 @@ class MainActivity : FlutterActivity() {
|
|||
companion object {
|
||||
var flutterMethodChannel: MethodChannel? = null
|
||||
private var _rdClipboardManager: RdClipboardManager? = null
|
||||
private var hasActiveRemoteSession = false
|
||||
val rdClipboardManager: RdClipboardManager?
|
||||
get() = _rdClipboardManager;
|
||||
}
|
||||
|
|
@ -230,6 +232,34 @@ class MainActivity : FlutterActivity() {
|
|||
rdClipboardManager?.syncClipboard(true)
|
||||
result.success(true)
|
||||
}
|
||||
"set_active_remote_session" -> {
|
||||
hasActiveRemoteSession = call.arguments == true
|
||||
Log.d(logTag, "set_active_remote_session: $hasActiveRemoteSession")
|
||||
result.success(true)
|
||||
}
|
||||
"update_floating_frame" -> {
|
||||
val args = call.arguments as? Map<*, *>
|
||||
if (args != null) {
|
||||
val bytes = args["bytes"] as? ByteArray
|
||||
val width = args["width"] as? Int ?: 120
|
||||
val height = args["height"] as? Int ?: 120
|
||||
if (bytes != null) {
|
||||
FloatingWindowService.updateFrame(bytes, width, height)
|
||||
}
|
||||
}
|
||||
result.success(true)
|
||||
}
|
||||
"move_to_floating_window" -> {
|
||||
val disableFloatingWindow = FFI.getLocalOption("disable-floating-window") == "Y"
|
||||
val canFloat = !disableFloatingWindow &&
|
||||
(MainService.isReady || hasActiveRemoteSession) &&
|
||||
XXPermissions.isGranted(context, Manifest.permission.SYSTEM_ALERT_WINDOW)
|
||||
if (canFloat) {
|
||||
startService(Intent(this, FloatingWindowService::class.java))
|
||||
moveTaskToBack(true)
|
||||
}
|
||||
result.success(canFloat)
|
||||
}
|
||||
GET_START_ON_BOOT_OPT -> {
|
||||
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
|
||||
result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
|
||||
|
|
@ -402,7 +432,7 @@ class MainActivity : FlutterActivity() {
|
|||
override fun onStop() {
|
||||
super.onStop()
|
||||
val disableFloatingWindow = FFI.getLocalOption("disable-floating-window") == "Y"
|
||||
if (!disableFloatingWindow && MainService.isReady) {
|
||||
if (!disableFloatingWindow && (MainService.isReady || hasActiveRemoteSession)) {
|
||||
startService(Intent(this, FloatingWindowService::class.java))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,15 @@ class MainApplication : Application() {
|
|||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "App start")
|
||||
FFI.onAppStart(applicationContext)
|
||||
try {
|
||||
FFI.onAppStart(applicationContext)
|
||||
Log.d(TAG, "FFI.onAppStart succeeded")
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
Log.e(TAG, "Failed to load native library", e)
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "FFI.onAppStart failed", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
org.gradle.jvmargs=-Xmx1024M
|
||||
org.gradle.jvmargs=-Xmx4g
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
org.gradle.daemon=false
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pluginManagement {
|
|||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "7.3.1" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.21" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -66,6 +68,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||
Orientation? _currentOrientation;
|
||||
final _uniqueKey = UniqueKey();
|
||||
Timer? _iosKeyboardWorkaroundTimer;
|
||||
Timer? _floatingFrameTimer;
|
||||
|
||||
final _blockableOverlayState = BlockableOverlayState();
|
||||
|
||||
|
|
@ -94,6 +97,9 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||
void initState() {
|
||||
super.initState();
|
||||
gFFI.ffiModel.updateEventListener(sessionId, widget.id);
|
||||
if (isAndroid) {
|
||||
unawaited(gFFI.invokeMethod("set_active_remote_session", true));
|
||||
}
|
||||
gFFI.start(
|
||||
widget.id,
|
||||
password: widget.password,
|
||||
|
|
@ -151,6 +157,9 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||
// "Connecting...". Dispatching it here makes teardown happen synchronously on
|
||||
// pop; the `sessionClose` in `gFFI.close()` becomes a no-op once removed.
|
||||
unawaited(bind.sessionClose(sessionId: sessionId));
|
||||
if (isAndroid) {
|
||||
unawaited(gFFI.invokeMethod("set_active_remote_session", false));
|
||||
}
|
||||
// https://github.com/flutter/flutter/issues/64935
|
||||
super.dispose();
|
||||
gFFI.dialogManager.hideMobileActionsOverlay(store: false);
|
||||
|
|
@ -166,6 +175,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||
await gFFI.close();
|
||||
_timer?.cancel();
|
||||
_iosKeyboardWorkaroundTimer?.cancel();
|
||||
_floatingFrameTimer?.cancel();
|
||||
gFFI.dialogManager.dismissAll();
|
||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
overlays: SystemUiOverlay.values);
|
||||
|
|
@ -181,6 +191,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
_stopFloatingFramePush();
|
||||
trySyncClipboard();
|
||||
}
|
||||
}
|
||||
|
|
@ -433,6 +444,57 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> moveToFloatingWindow() async {
|
||||
final ok = await gFFI.invokeMethod("move_to_floating_window");
|
||||
if (ok != true) {
|
||||
showToast('No permission');
|
||||
} else {
|
||||
_startFloatingFramePush();
|
||||
}
|
||||
}
|
||||
|
||||
void _startFloatingFramePush() {
|
||||
_floatingFrameTimer?.cancel();
|
||||
_floatingFrameTimer = Timer.periodic(const Duration(milliseconds: 100), (_) {
|
||||
_sendFloatingFrame();
|
||||
});
|
||||
}
|
||||
|
||||
void _stopFloatingFramePush() {
|
||||
_floatingFrameTimer?.cancel();
|
||||
_floatingFrameTimer = null;
|
||||
}
|
||||
|
||||
Future<void> _sendFloatingFrame() async {
|
||||
try {
|
||||
final image = gFFI.imageModel.image;
|
||||
if (image == null) return;
|
||||
const targetW = 160;
|
||||
const targetH = 160;
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
canvas.drawImageRect(
|
||||
image,
|
||||
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
|
||||
Rect.fromLTWH(0, 0, targetW.toDouble(), targetH.toDouble()),
|
||||
Paint(),
|
||||
);
|
||||
final picture = recorder.endRecording();
|
||||
final resized = await picture.toImage(targetW, targetH);
|
||||
final byteData = await resized.toByteData(format: ui.ImageByteFormat.rawRgba);
|
||||
if (byteData != null) {
|
||||
await gFFI.invokeMethod("update_floating_frame", {
|
||||
'bytes': byteData.buffer.asUint8List(),
|
||||
'width': targetW,
|
||||
'height': targetH,
|
||||
});
|
||||
}
|
||||
resized.dispose();
|
||||
} catch (e) {
|
||||
debugPrint('_sendFloatingFrame error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Widget _bottomWidget() => _showGestureHelp
|
||||
? getGestureHelp()
|
||||
: (_showBar && gFFI.ffiModel.pi.displays.isNotEmpty
|
||||
|
|
@ -575,7 +637,13 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||
setState(() => _showEdit = false);
|
||||
showOptions(context, widget.id, gFFI.dialogManager);
|
||||
},
|
||||
)
|
||||
),
|
||||
if (isAndroid)
|
||||
IconButton(
|
||||
color: Colors.white,
|
||||
icon: const Icon(Icons.picture_in_picture_alt),
|
||||
onPressed: moveToFloatingWindow,
|
||||
)
|
||||
] +
|
||||
(isWebDesktop || ffiModel.viewOnly || !ffiModel.keyboard
|
||||
? []
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
version: "2.11.0"
|
||||
auto_size_text:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -90,10 +90,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.1"
|
||||
bot_toast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -417,6 +417,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -644,6 +652,11 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
|
@ -849,6 +862,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -877,10 +914,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1218,10 +1255,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
version: "1.10.0"
|
||||
sqflite:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1242,18 +1279,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.2"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1266,10 +1303,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.2.0"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1282,18 +1319,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6"
|
||||
version: "0.7.2"
|
||||
texture_rgba_renderer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -1473,7 +1510,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.1.16"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
|
|
@ -1528,6 +1565,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0+2"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.5"
|
||||
wakelock_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue