minor fixes
This commit is contained in:
parent
a71a30130b
commit
3ea5ddd753
7 changed files with 74 additions and 41 deletions
|
@ -77,29 +77,34 @@ class SearchSuggestionsProvider : MethodChannel.MethodCallHandler, ContentProvid
|
||||||
val backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL)
|
val backgroundChannel = MethodChannel(messenger, BACKGROUND_CHANNEL)
|
||||||
backgroundChannel.setMethodCallHandler(this)
|
backgroundChannel.setMethodCallHandler(this)
|
||||||
|
|
||||||
return suspendCoroutine { cont ->
|
try {
|
||||||
GlobalScope.launch {
|
return suspendCoroutine { cont ->
|
||||||
FlutterUtils.runOnUiThread {
|
GlobalScope.launch {
|
||||||
backgroundChannel.invokeMethod("getSuggestions", hashMapOf(
|
FlutterUtils.runOnUiThread {
|
||||||
"query" to query,
|
backgroundChannel.invokeMethod("getSuggestions", hashMapOf(
|
||||||
"locale" to Locale.getDefault().toString(),
|
"query" to query,
|
||||||
"use24hour" to DateFormat.is24HourFormat(context),
|
"locale" to Locale.getDefault().toString(),
|
||||||
), object : MethodChannel.Result {
|
"use24hour" to DateFormat.is24HourFormat(context),
|
||||||
override fun success(result: Any?) {
|
), object : MethodChannel.Result {
|
||||||
@Suppress("unchecked_cast")
|
override fun success(result: Any?) {
|
||||||
cont.resume(result as List<FieldMap>)
|
@Suppress("unchecked_cast")
|
||||||
}
|
cont.resume(result as List<FieldMap>)
|
||||||
|
}
|
||||||
|
|
||||||
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
|
||||||
cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
|
cont.resumeWithException(Exception("$errorCode: $errorMessage\n$errorDetails"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notImplemented() {
|
override fun notImplemented() {
|
||||||
cont.resumeWithException(NotImplementedError("getSuggestions"))
|
cont.resumeWithException(NotImplementedError("getSuggestions"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(LOG_TAG, "failed to get suggestions", e)
|
||||||
|
return ArrayList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,6 @@ import kotlinx.coroutines.launch
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
|
@ -412,19 +411,19 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
|
|
||||||
// File type
|
// File type
|
||||||
for (dir in metadata.getDirectoriesOfType(FileTypeDirectory::class.java)) {
|
for (dir in metadata.getDirectoriesOfType(FileTypeDirectory::class.java)) {
|
||||||
// * `metadata-extractor` sometimes detects the wrong MIME type (e.g. `pef` file as `tiff`, `mpeg` as `dvd`)
|
// * `metadata-extractor` sometimes detects the wrong MIME type (e.g. `pef` file as `tiff`, `mpeg` as `dvd`, `avif` as `mov`)
|
||||||
// * the content resolver / media store sometimes reports the wrong MIME type (e.g. `png` file as `jpeg`, `tiff` as `srw`)
|
// * the content resolver / media store sometimes reports the wrong MIME type (e.g. `png` file as `jpeg`, `tiff` as `srw`)
|
||||||
// * `context.getContentResolver().getType()` sometimes returns an incorrect value
|
// * `context.getContentResolver().getType()` sometimes returns an incorrect value
|
||||||
// * `MediaMetadataRetriever.setDataSource()` sometimes fails with `status = 0x80000000`
|
// * `MediaMetadataRetriever.setDataSource()` sometimes fails with `status = 0x80000000`
|
||||||
// * file extension is unreliable
|
// * file extension is unreliable
|
||||||
// In the end, `metadata-extractor` is the most reliable, except for `tiff`/`dvd` (false positives, false negatives),
|
// In the end, `metadata-extractor` is the most reliable, except for `tiff`/`dvd`/`mov` (false positives, false negatives),
|
||||||
// in which case we trust the file extension
|
// in which case we trust the file extension
|
||||||
// cf https://github.com/drewnoakes/metadata-extractor/issues/296
|
// cf https://github.com/drewnoakes/metadata-extractor/issues/296
|
||||||
if (path?.matches(TIFF_EXTENSION_PATTERN) == true) {
|
if (path?.matches(TIFF_EXTENSION_PATTERN) == true) {
|
||||||
metadataMap[KEY_MIME_TYPE] = MimeTypes.TIFF
|
metadataMap[KEY_MIME_TYPE] = MimeTypes.TIFF
|
||||||
} else {
|
} else {
|
||||||
dir.getSafeString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE) {
|
dir.getSafeString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE) {
|
||||||
if (it != MimeTypes.TIFF && it != MimeTypes.DVD) {
|
if (it != MimeTypes.TIFF && it != MimeTypes.DVD && it != MimeTypes.MOV) {
|
||||||
metadataMap[KEY_MIME_TYPE] = it
|
metadataMap[KEY_MIME_TYPE] = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ class GSpherical(xmlBytes: ByteArray) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun describe(): Map<String, String?> = hashMapOf(
|
fun describe(): Map<String, String> = hashMapOf(
|
||||||
"Spherical" to spherical.toString(),
|
"Spherical" to spherical.toString(),
|
||||||
"Stitched" to stitched.toString(),
|
"Stitched" to stitched.toString(),
|
||||||
"Stitching Software" to stitchingSoftware,
|
"Stitching Software" to stitchingSoftware,
|
||||||
|
@ -79,7 +79,7 @@ class GSpherical(xmlBytes: ByteArray) {
|
||||||
"Cropped Area Image Height Pixels" to croppedAreaImageHeightPixels?.toString(),
|
"Cropped Area Image Height Pixels" to croppedAreaImageHeightPixels?.toString(),
|
||||||
"Cropped Area Left Pixels" to croppedAreaLeftPixels?.toString(),
|
"Cropped Area Left Pixels" to croppedAreaLeftPixels?.toString(),
|
||||||
"Cropped Area Top Pixels" to croppedAreaTopPixels?.toString(),
|
"Cropped Area Top Pixels" to croppedAreaTopPixels?.toString(),
|
||||||
).filterValues { it != null }
|
).filterValues { it != null }.mapValues { it.value as String }
|
||||||
|
|
||||||
companion object SphericalVideo {
|
companion object SphericalVideo {
|
||||||
private val LOG_TAG = LogUtils.createTag<SphericalVideo>()
|
private val LOG_TAG = LogUtils.createTag<SphericalVideo>()
|
||||||
|
|
|
@ -6,6 +6,7 @@ object MimeTypes {
|
||||||
const val ANY = "*/*"
|
const val ANY = "*/*"
|
||||||
|
|
||||||
// generic raster
|
// generic raster
|
||||||
|
private const val AVIF = "image/avif"
|
||||||
const val BMP = "image/bmp"
|
const val BMP = "image/bmp"
|
||||||
private const val DJVU = "image/vnd.djvu"
|
private const val DJVU = "image/vnd.djvu"
|
||||||
const val GIF = "image/gif"
|
const val GIF = "image/gif"
|
||||||
|
@ -49,7 +50,7 @@ object MimeTypes {
|
||||||
private const val AVI_VND = "video/vnd.avi"
|
private const val AVI_VND = "video/vnd.avi"
|
||||||
const val DVD = "video/dvd"
|
const val DVD = "video/dvd"
|
||||||
private const val MKV = "video/x-matroska"
|
private const val MKV = "video/x-matroska"
|
||||||
private const val MOV = "video/quicktime"
|
const val MOV = "video/quicktime"
|
||||||
private const val MP2T = "video/mp2t"
|
private const val MP2T = "video/mp2t"
|
||||||
private const val MP2TS = "video/mp2ts"
|
private const val MP2TS = "video/mp2ts"
|
||||||
const val MP4 = "video/mp4"
|
const val MP4 = "video/mp4"
|
||||||
|
@ -72,7 +73,7 @@ object MimeTypes {
|
||||||
// returns whether the specified MIME type represents
|
// returns whether the specified MIME type represents
|
||||||
// a raster image format that allows an alpha channel
|
// a raster image format that allows an alpha channel
|
||||||
fun canHaveAlpha(mimeType: String?) = when (mimeType) {
|
fun canHaveAlpha(mimeType: String?) = when (mimeType) {
|
||||||
BMP, GIF, ICO, PNG, SVG, TIFF, WEBP -> true
|
AVIF, BMP, GIF, ICO, PNG, SVG, TIFF, WEBP -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,6 +151,7 @@ object MimeTypes {
|
||||||
fun extensionFor(mimeType: String): String? = when (mimeType) {
|
fun extensionFor(mimeType: String): String? = when (mimeType) {
|
||||||
ARW -> ".arw"
|
ARW -> ".arw"
|
||||||
AVI, AVI_VND -> ".avi"
|
AVI, AVI_VND -> ".avi"
|
||||||
|
AVIF -> ".avif"
|
||||||
BMP -> ".bmp"
|
BMP -> ".bmp"
|
||||||
CR2 -> ".cr2"
|
CR2 -> ".cr2"
|
||||||
CRW -> ".crw"
|
CRW -> ".crw"
|
||||||
|
|
|
@ -158,15 +158,35 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setup before the first page is displayed. keep it short
|
||||||
Future<void> _setup() async {
|
Future<void> _setup() async {
|
||||||
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
// TODO TLAD [init] init settings/device w/o platform calls (first platform channel call takes ~800ms):
|
||||||
|
// 1) use cached values if any,
|
||||||
|
// 2a) call platform w/ delay if cached
|
||||||
|
// 2b) call platform w/o delay if not cached
|
||||||
|
// 3) cache platform call results across app restarts
|
||||||
|
|
||||||
|
await device.init();
|
||||||
|
final isRotationLocked = await windowService.isRotationLocked();
|
||||||
|
final areAnimationsRemoved = await AccessibilityService.areAnimationsRemoved();
|
||||||
|
|
||||||
|
// TODO TLAD [init] migrate settings away from `shared_preferences` to a platform-free solution
|
||||||
await settings.init(
|
await settings.init(
|
||||||
monitorPlatformSettings: true,
|
monitorPlatformSettings: true,
|
||||||
isRotationLocked: await windowService.isRotationLocked(),
|
isRotationLocked: isRotationLocked,
|
||||||
areAnimationsRemoved: await AccessibilityService.areAnimationsRemoved(),
|
areAnimationsRemoved: areAnimationsRemoved,
|
||||||
);
|
);
|
||||||
await device.init();
|
_monitorSettings();
|
||||||
FijkLog.setLevel(FijkLogLevel.Warn);
|
|
||||||
|
|
||||||
|
FijkLog.setLevel(FijkLogLevel.Warn);
|
||||||
|
unawaited(_setupErrorReporting());
|
||||||
|
|
||||||
|
debugPrint('App setup in ${stopwatch.elapsed.inMilliseconds}ms');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _monitorSettings() {
|
||||||
// keep screen on
|
// keep screen on
|
||||||
settings.updateStream.where((key) => key == Settings.keepScreenOnKey).listen(
|
settings.updateStream.where((key) => key == Settings.keepScreenOnKey).listen(
|
||||||
(_) => settings.keepScreenOn.apply(),
|
(_) => settings.keepScreenOn.apply(),
|
||||||
|
@ -183,8 +203,9 @@ class _AvesAppState extends State<AvesApp> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// error reporting
|
Future<void> _setupErrorReporting() async {
|
||||||
await reportService.init();
|
await reportService.init();
|
||||||
settings.updateStream.where((key) => key == Settings.isErrorReportingAllowedKey).listen(
|
settings.updateStream.where((key) => key == Settings.isErrorReportingAllowedKey).listen(
|
||||||
(_) => reportService.setCollectionEnabled(settings.isErrorReportingAllowed),
|
(_) => reportService.setCollectionEnabled(settings.isErrorReportingAllowed),
|
||||||
|
|
|
@ -44,7 +44,7 @@ class AvesDialog extends StatelessWidget {
|
||||||
// and overflow feedback ignores the dialog shape,
|
// and overflow feedback ignores the dialog shape,
|
||||||
// so we restrict scrolling to the content instead
|
// so we restrict scrolling to the content instead
|
||||||
content: _buildContent(context),
|
content: _buildContent(context),
|
||||||
contentPadding: scrollableContent != null ? EdgeInsets.zero : EdgeInsets.fromLTRB(horizontalContentPadding, 20, horizontalContentPadding, 0),
|
contentPadding: scrollableContent != null ? EdgeInsets.zero : EdgeInsets.only(left: horizontalContentPadding, top: 20, right: horizontalContentPadding),
|
||||||
actions: actions,
|
actions: actions,
|
||||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
|
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
shape: shape(context),
|
shape: shape(context),
|
||||||
|
|
|
@ -51,12 +51,14 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
|
const contentHorizontalPadding = EdgeInsets.symmetric(horizontal: AvesDialog.defaultHorizontalContentPadding);
|
||||||
|
|
||||||
return AvesDialog(
|
return AvesDialog(
|
||||||
content: Column(
|
scrollableContent: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
const SizedBox(height: 16),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Padding(
|
||||||
children: [
|
padding: contentHorizontalPadding,
|
||||||
Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.exportEntryDialogFormat),
|
Text(l10n.exportEntryDialogFormat),
|
||||||
|
@ -77,7 +79,10 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
),
|
||||||
|
Padding(
|
||||||
|
padding: contentHorizontalPadding,
|
||||||
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
textBaseline: TextBaseline.alphabetic,
|
textBaseline: TextBaseline.alphabetic,
|
||||||
children: [
|
children: [
|
||||||
|
@ -108,8 +113,9 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
|
|
Loading…
Reference in a new issue