#1110 fixed widget setup race
This commit is contained in:
parent
0bbed0ebda
commit
3bad4b6814
5 changed files with 71 additions and 34 deletions
|
@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
- Viewer: display more items in tag/copy/move quick action choosers
|
- Viewer: display more items in tag/copy/move quick action choosers
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- multiple widget setup after device reboot
|
||||||
|
|
||||||
## <a id="v1.11.8"></a>[v1.11.8] - 2024-07-19
|
## <a id="v1.11.8"></a>[v1.11.8] - 2024-07-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -11,12 +11,18 @@ import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.SizeF
|
import android.util.SizeF
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import app.loup.streams_channel.StreamsChannel
|
import app.loup.streams_channel.StreamsChannel
|
||||||
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
|
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
|
||||||
import deckers.thibault.aves.channel.calls.*
|
import deckers.thibault.aves.channel.calls.DeviceHandler
|
||||||
|
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
|
||||||
|
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
|
||||||
|
import deckers.thibault.aves.channel.calls.MediaStoreHandler
|
||||||
|
import deckers.thibault.aves.channel.calls.StorageHandler
|
||||||
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
|
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
|
||||||
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
|
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
|
@ -26,8 +32,14 @@ import io.flutter.FlutterInjector
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.embedding.engine.dart.DartExecutor
|
import io.flutter.embedding.engine.dart.DartExecutor
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
@ -108,37 +120,25 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
|
|
||||||
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
val isNightModeOn = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||||
|
|
||||||
|
val params = hashMapOf(
|
||||||
|
"widgetId" to widgetId,
|
||||||
|
"sizesDip" to sizesDip,
|
||||||
|
"devicePixelRatio" to getDevicePixelRatio(),
|
||||||
|
"drawEntryImage" to drawEntryImage,
|
||||||
|
"reuseEntry" to reuseEntry,
|
||||||
|
"isSystemThemeDark" to isNightModeOn,
|
||||||
|
).apply {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
put("cornerRadiusPx", context.resources.getDimension(android.R.dimen.system_app_widget_background_radius))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initFlutterEngine(context)
|
initFlutterEngine(context)
|
||||||
val messenger = flutterEngine!!.dartExecutor
|
|
||||||
val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
|
|
||||||
try {
|
try {
|
||||||
val props = suspendCoroutine<Any?> { cont ->
|
val props = suspendCoroutine { cont ->
|
||||||
defaultScope.launch {
|
defaultScope.launch {
|
||||||
FlutterUtils.runOnUiThread {
|
FlutterUtils.runOnUiThread {
|
||||||
channel.invokeMethod("drawWidget", hashMapOf(
|
tryDrawWidget(params, cont, 0)
|
||||||
"widgetId" to widgetId,
|
|
||||||
"sizesDip" to sizesDip,
|
|
||||||
"devicePixelRatio" to getDevicePixelRatio(),
|
|
||||||
"drawEntryImage" to drawEntryImage,
|
|
||||||
"reuseEntry" to reuseEntry,
|
|
||||||
"isSystemThemeDark" to isNightModeOn,
|
|
||||||
).apply {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
put("cornerRadiusPx", context.resources.getDimension(android.R.dimen.system_app_widget_background_radius))
|
|
||||||
}
|
|
||||||
}, object : MethodChannel.Result {
|
|
||||||
override fun success(result: Any?) {
|
|
||||||
cont.resume(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
|
||||||
cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun notImplemented() {
|
|
||||||
cont.resumeWithException(Exception("not implemented"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,30 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tryDrawWidget(params: HashMap<String, Any>, cont: Continuation<Any?>, drawRetry: Int) {
|
||||||
|
val messenger = flutterEngine!!.dartExecutor
|
||||||
|
val channel = MethodChannel(messenger, WIDGET_DRAW_CHANNEL)
|
||||||
|
channel.invokeMethod("drawWidget", params, object : MethodChannel.Result {
|
||||||
|
override fun success(result: Any?) {
|
||||||
|
cont.resume(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
||||||
|
cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun notImplemented() {
|
||||||
|
if (drawRetry > DRAW_RETRY_MAX) {
|
||||||
|
cont.resumeWithException(Exception("not implemented"))
|
||||||
|
} else {
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
tryDrawWidget(params, cont, drawRetry + 1)
|
||||||
|
}, 2000L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateWidgetImage(
|
private fun updateWidgetImage(
|
||||||
context: Context,
|
context: Context,
|
||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
|
@ -271,6 +295,7 @@ class HomeWidgetProvider : AppWidgetProvider() {
|
||||||
private val LOG_TAG = LogUtils.createTag<HomeWidgetProvider>()
|
private val LOG_TAG = LogUtils.createTag<HomeWidgetProvider>()
|
||||||
private const val WIDGET_DART_ENTRYPOINT = "widgetMain"
|
private const val WIDGET_DART_ENTRYPOINT = "widgetMain"
|
||||||
private const val WIDGET_DRAW_CHANNEL = "deckers.thibault/aves/widget_draw"
|
private const val WIDGET_DRAW_CHANNEL = "deckers.thibault/aves/widget_draw"
|
||||||
|
private const val DRAW_RETRY_MAX = 5
|
||||||
|
|
||||||
private var flutterEngine: FlutterEngine? = null
|
private var flutterEngine: FlutterEngine? = null
|
||||||
private var imageByteFetchJob: Job? = null
|
private var imageByteFetchJob: Job? = null
|
||||||
|
|
|
@ -7,6 +7,8 @@ import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
final AndroidFileUtils androidFileUtils = AndroidFileUtils._private();
|
||||||
|
|
||||||
|
enum _State { uninitialized, initializing, initialized }
|
||||||
|
|
||||||
class AndroidFileUtils {
|
class AndroidFileUtils {
|
||||||
// cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT
|
// cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT
|
||||||
static const contentScheme = 'content';
|
static const contentScheme = 'content';
|
||||||
|
@ -27,13 +29,19 @@ class AndroidFileUtils {
|
||||||
late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath;
|
late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath;
|
||||||
late final Set<String> videoCapturesPaths;
|
late final Set<String> videoCapturesPaths;
|
||||||
Set<StorageVolume> storageVolumes = {};
|
Set<StorageVolume> storageVolumes = {};
|
||||||
bool _initialized = false;
|
_State _initialized = _State.uninitialized;
|
||||||
|
|
||||||
AndroidFileUtils._private();
|
AndroidFileUtils._private();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
if (_initialized) return;
|
if (_initialized == _State.uninitialized) {
|
||||||
|
_initialized = _State.initializing;
|
||||||
|
await _doInit();
|
||||||
|
_initialized = _State.initialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _doInit() async {
|
||||||
separator = pContext.separator;
|
separator = pContext.separator;
|
||||||
await _initStorageVolumes();
|
await _initStorageVolumes();
|
||||||
vaultRoot = await storageService.getVaultRoot();
|
vaultRoot = await storageService.getVaultRoot();
|
||||||
|
@ -50,8 +58,6 @@ class AndroidFileUtils {
|
||||||
// from Aves
|
// from Aves
|
||||||
avesVideoCapturesPath,
|
avesVideoCapturesPath,
|
||||||
};
|
};
|
||||||
|
|
||||||
_initialized = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initStorageVolumes() async {
|
Future<void> _initStorageVolumes() async {
|
||||||
|
|
|
@ -18,11 +18,13 @@ import 'package:flutter/services.dart';
|
||||||
const _widgetDrawChannel = MethodChannel('deckers.thibault/aves/widget_draw');
|
const _widgetDrawChannel = MethodChannel('deckers.thibault/aves/widget_draw');
|
||||||
|
|
||||||
void widgetMainCommon(AppFlavor flavor) async {
|
void widgetMainCommon(AppFlavor flavor) async {
|
||||||
|
debugPrint('Widget main start');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
initPlatformServices();
|
initPlatformServices();
|
||||||
await settings.init(monitorPlatformSettings: false);
|
await settings.init(monitorPlatformSettings: false);
|
||||||
await reportService.init();
|
await reportService.init();
|
||||||
|
|
||||||
|
debugPrint('Widget channel method handling setup');
|
||||||
_widgetDrawChannel.setMethodCallHandler((call) async {
|
_widgetDrawChannel.setMethodCallHandler((call) async {
|
||||||
// widget settings may be modified in a different process after channel setup
|
// widget settings may be modified in a different process after channel setup
|
||||||
await settings.reload();
|
await settings.reload();
|
||||||
|
|
|
@ -36,7 +36,7 @@ class HomeWidgetPainter {
|
||||||
final widthPx = sizeDip.width * devicePixelRatio;
|
final widthPx = sizeDip.width * devicePixelRatio;
|
||||||
final heightPx = sizeDip.height * devicePixelRatio;
|
final heightPx = sizeDip.height * devicePixelRatio;
|
||||||
final widgetSizePx = Size(widthPx, heightPx);
|
final widgetSizePx = Size(widthPx, heightPx);
|
||||||
debugPrint('draw widget for $sizeDip dp ($widgetSizePx px), entry=$entry');
|
debugPrint('Draw widget for ${sizeDip.width}x${sizeDip.height} dp (${widgetSizePx.width}x${widgetSizePx.height} px), entry=$entry');
|
||||||
final ui.Image? entryImage;
|
final ui.Image? entryImage;
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
final extent = shape.extentPx(widgetSizePx, entry!) / devicePixelRatio;
|
final extent = shape.extentPx(widgetSizePx, entry!) / devicePixelRatio;
|
||||||
|
|
Loading…
Reference in a new issue