diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index 8fe051681..5911fffa2 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -302,6 +302,7 @@ open class MainActivity : FlutterFragmentActivity() { INTENT_DATA_KEY_PAGE to intent.getStringExtra(EXTRA_KEY_PAGE), INTENT_DATA_KEY_FILTERS to extractFiltersFromIntent(intent), INTENT_DATA_KEY_EXPLORER_PATH to intent.getStringExtra(EXTRA_KEY_EXPLORER_PATH), + INTENT_DATA_KEY_DEBUG to intent.getBooleanExtra(EXTRA_KEY_DEBUG, false), ) } @@ -533,7 +534,17 @@ open class MainActivity : FlutterFragmentActivity() { ) .build() - val shortcutInfoList = listOf(videos, search, map) + val debug = ShortcutInfoCompat.Builder(this, "debug") + .setShortLabel("debug") + .setIntent( + Intent(Intent.ACTION_MAIN, null, this, MainActivity::class.java) + .putExtra(EXTRA_KEY_DEBUG, true) + ) + .build() + +// val shortcutInfoList = listOf(videos, search, map) + val shortcutInfoList = listOf(debug) + ShortcutManagerCompat.setDynamicShortcuts(this, shortcutInfoList) Log.i(LOG_TAG, "set shortcuts: ${shortcutInfoList.joinToString(", ") { v -> v.id }}") } @@ -579,12 +590,14 @@ open class MainActivity : FlutterFragmentActivity() { const val INTENT_DATA_KEY_SECURE_URIS = "secureUris" const val INTENT_DATA_KEY_URI = "uri" const val INTENT_DATA_KEY_WIDGET_ID = "widgetId" + const val INTENT_DATA_KEY_DEBUG = "debug" const val EXTRA_KEY_PAGE = "page" const val EXTRA_KEY_EXPLORER_PATH = "explorerPath" const val EXTRA_KEY_FILTERS_ARRAY = "filters" const val EXTRA_KEY_FILTERS_STRING = "filtersString" const val EXTRA_KEY_WIDGET_ID = "widgetId" + const val EXTRA_KEY_DEBUG = "debug" // dart page routes const val COLLECTION_PAGE_ROUTE_NAME = "/collection" diff --git a/lib/model/app/intent.dart b/lib/model/app/intent.dart index ee4985b0e..4d118bd0e 100644 --- a/lib/model/app/intent.dart +++ b/lib/model/app/intent.dart @@ -24,4 +24,7 @@ class IntentDataKeys { static const secureUris = 'secureUris'; static const uri = 'uri'; static const widgetId = 'widgetId'; + + // #977 + static const debug = 'debug'; } diff --git a/lib/model/db/db.dart b/lib/model/db/db.dart index 7121361f2..c89718d8f 100644 --- a/lib/model/db/db.dart +++ b/lib/model/db/db.dart @@ -22,6 +22,12 @@ abstract class LocalMediaDb { Future removeIds(Set ids, {Set? dataTypes}); + // debug + + Future logCatalog(String message); + + Future> getCatalogLog(); + // entries Future clearEntries(); diff --git a/lib/model/db/db_sqflite.dart b/lib/model/db/db_sqflite.dart index 7907f588f..ce9984d24 100644 --- a/lib/model/db/db_sqflite.dart +++ b/lib/model/db/db_sqflite.dart @@ -34,6 +34,7 @@ class SqfliteLocalMediaDb implements LocalMediaDb { static const vaultTable = SqfliteLocalMediaDbSchema.vaultTable; static const trashTable = SqfliteLocalMediaDbSchema.trashTable; static const videoPlaybackTable = SqfliteLocalMediaDbSchema.videoPlaybackTable; + static const debugTable = SqfliteLocalMediaDbSchema.debugTable; static const _entryInsertSliceMaxCount = 10000; // number of entries static const _queryCursorBufferSize = 1000; // number of rows @@ -48,7 +49,7 @@ class SqfliteLocalMediaDb implements LocalMediaDb { await path, onCreate: (db, version) => SqfliteLocalMediaDbSchema.createLatestVersion(db), onUpgrade: LocalMediaDbUpgrader.upgradeDb, - version: 15, + version: 16, ); final maxIdRows = await _db.rawQuery('SELECT MAX(id) AS maxId FROM $entryTable'); @@ -101,6 +102,19 @@ class SqfliteLocalMediaDb implements LocalMediaDb { await batch.commit(noResult: true); } + // debug + + @override + Future logCatalog(String message) async { + await _db.insert(debugTable, {'message': message}); + } + + @override + Future> getCatalogLog() async { + final rows = await _db.query(debugTable); + return rows.map((row) => row['message'] as String).toList(); + } + // entries @override diff --git a/lib/model/db/db_sqflite_schema.dart b/lib/model/db/db_sqflite_schema.dart index 1d1d81054..e27398d77 100644 --- a/lib/model/db/db_sqflite_schema.dart +++ b/lib/model/db/db_sqflite_schema.dart @@ -11,6 +11,7 @@ class SqfliteLocalMediaDbSchema { static const vaultTable = 'vaults'; static const trashTable = 'trash'; static const videoPlaybackTable = 'videoPlayback'; + static const debugTable = 'debug'; static const allTables = [ entryTable, @@ -23,6 +24,7 @@ class SqfliteLocalMediaDbSchema { vaultTable, trashTable, videoPlaybackTable, + debugTable, ]; static Future createLatestVersion(Database db) async { @@ -111,6 +113,11 @@ class SqfliteLocalMediaDbSchema { 'id INTEGER PRIMARY KEY' ', resumeTimeMillis INTEGER' ')'); + case debugTable: + return db.execute('CREATE TABLE $debugTable(' + 'id INTEGER PRIMARY KEY AUTOINCREMENT' + ', message TEXT' + ')'); default: throw Exception('unknown table=$table'); } diff --git a/lib/model/db/db_sqflite_upgrade.dart b/lib/model/db/db_sqflite_upgrade.dart index 4796ea17d..df6212d25 100644 --- a/lib/model/db/db_sqflite_upgrade.dart +++ b/lib/model/db/db_sqflite_upgrade.dart @@ -18,6 +18,7 @@ class LocalMediaDbUpgrader { static const vaultTable = SqfliteLocalMediaDbSchema.vaultTable; static const trashTable = SqfliteLocalMediaDbSchema.trashTable; static const videoPlaybackTable = SqfliteLocalMediaDbSchema.videoPlaybackTable; + static const debugTable = SqfliteLocalMediaDbSchema.debugTable; // warning: "ALTER TABLE ... RENAME COLUMN ..." is not supported // on SQLite <3.25.0, bundled on older Android devices @@ -53,6 +54,8 @@ class LocalMediaDbUpgrader { await _upgradeFrom13(db); case 14: await _upgradeFrom14(db); + case 15: + await _upgradeFrom15(db); } oldVersion++; } @@ -500,4 +503,13 @@ class LocalMediaDbUpgrader { // (dateTakenTable, metadataTable, addressTable, trashTable, videoPlaybackTable) // for users with a potentially corrupted DB following upgrade to v1.12.4 } + + static Future _upgradeFrom15(Database db) async { + debugPrint('upgrading DB from v15'); + + await db.execute('CREATE TABLE $debugTable(' + 'id INTEGER PRIMARY KEY AUTOINCREMENT' + ', message TEXT' + ')'); + } } diff --git a/lib/services/metadata/metadata_fetch_service.dart b/lib/services/metadata/metadata_fetch_service.dart index 9f10284e6..bbc6e5cef 100644 --- a/lib/services/metadata/metadata_fetch_service.dart +++ b/lib/services/metadata/metadata_fetch_service.dart @@ -68,6 +68,9 @@ class PlatformMetadataFetchService implements MetadataFetchService { Future getCatalogMetadata(AvesEntry entry, {bool background = false}) async { if (entry.isSvg) return null; + // #977 + await localMediaDb.logCatalog('${DateTime.now().toIso8601String()} ${entry.path ?? entry.uri}'); + Future call() async { try { // returns map with: diff --git a/lib/widgets/home/home_page.dart b/lib/widgets/home/home_page.dart index cc4cd1875..350d49ee4 100644 --- a/lib/widgets/home/home_page.dart +++ b/lib/widgets/home/home_page.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; import 'package:aves/app_mode.dart'; import 'package:aves/geo/uri.dart'; @@ -14,6 +16,8 @@ import 'package:aves/model/settings/enums/home_page.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/ref/locales.dart'; +import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/analysis_service.dart'; import 'package:aves/services/common/services.dart'; import 'package:aves/services/global_search.dart'; @@ -22,6 +26,7 @@ import 'package:aves/services/widget_service.dart'; import 'package:aves/theme/themes.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; import 'package:aves/widgets/common/basic/scaffold.dart'; import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; @@ -42,6 +47,7 @@ import 'package:aves/widgets/wallpaper_page.dart'; import 'package:aves_model/aves_model.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:latlong2/latlong.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; @@ -61,7 +67,7 @@ class HomePage extends StatefulWidget { State createState() => _HomePageState(); } -class _HomePageState extends State { +class _HomePageState extends State with FeedbackMixin { AvesEntry? _viewerEntry; int? _widgetId; String? _initialRouteName, _initialSearchQuery; @@ -108,11 +114,35 @@ class _HomePageState extends State { var appMode = AppMode.main; var error = false; final intentData = widget.intentData ?? await IntentService.getIntentData(); + final debug = intentData[IntentDataKeys.debug] ?? false; final intentAction = intentData[IntentDataKeys.action] as String?; _initialFilters = null; _initialExplorerPath = null; _secureUris = null; + if (debug) { + await localMediaDb.init(); + final logs = await localMediaDb.getCatalogLog(); + + final success = await storageService.createFile( + 'aves_issue977_logs-${DateFormat('yyyyMMdd_HHmmss', asciiLocale).format(DateTime.now())}.txt', + MimeTypes.plainText, + Uint8List.fromList(utf8.encode(logs.join('\n'))), + ); + if (success != null) { + if (success) { + showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback); + } else { + showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback); + } + } + unawaited(Navigator.maybeOf(context)?.pushAndRemoveUntil( + await _getRedirectRoute(appMode), + (route) => false, + )); + return; + } + await availability.onNewIntent(); await androidFileUtils.init(); if (!{