diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt index 2143b5ee..6c97a8e8 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/GlobalComponents.kt @@ -45,6 +45,11 @@ object GlobalComponents { .whenSessionsChange() } + @DelicateCoroutinesApi + private fun restoreDownloads(newComponents: Components) = GlobalScope.launch(Dispatchers.Main) { + newComponents.useCases.downloadsUseCases.restoreDownloads() + } + @OptIn(DelicateCoroutinesApi::class) fun setUp( applicationContext: Context, @@ -82,8 +87,7 @@ object GlobalComponents { newComponents.core.engine.warmUp() restoreBrowserState(newComponents) - - //newComponents.useCases.downloadsUseCases.restoreDownloads() + restoreDownloads(newComponents) try { GlobalPlacesDependencyProvider.initialize(newComponents.core.historyStorage) diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoHistoryApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoHistoryApiImpl.kt index fa6f6122..2d57ed86 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoHistoryApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoHistoryApiImpl.kt @@ -1,7 +1,6 @@ package eu.weblibre.flutter_mozilla_components.api import eu.weblibre.flutter_mozilla_components.GlobalComponents -import eu.weblibre.flutter_mozilla_components.pigeons.AutocompleteResult import eu.weblibre.flutter_mozilla_components.pigeons.GeckoHistoryApi import eu.weblibre.flutter_mozilla_components.pigeons.VisitInfo import eu.weblibre.flutter_mozilla_components.pigeons.VisitType @@ -10,6 +9,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import mozilla.components.browser.state.state.content.DownloadState +import kotlin.time.Duration.Companion.milliseconds class GeckoHistoryApiImpl() : GeckoHistoryApi { companion object { @@ -20,6 +21,32 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi { requireNotNull(GlobalComponents.components) { "Components not initialized" } } + private fun Map.toVisitInfoList( + startMillis: Long, + endMillis: Long, + ): List = + values + .filter { isDisplayableItem(it.status) && + it.createdTime >= startMillis && it.createdTime <= endMillis + } + .distinctBy { Pair(it.fileName, it.status) } + .sortedByDescending { it.createdTime } // sort from newest to oldest + .map { it.toVisitInfo() } + + private fun isDisplayableItem(status: DownloadState.Status) = + status != DownloadState.Status.CANCELLED + + private fun DownloadState.toVisitInfo() = + VisitInfo( + url = url, + visitType = VisitType.DOWNLOAD, + visitTime = createdTime, + title = filePath, + previewImageUrl = contentType, + isRemote = false, + contentId = id + ) + override fun getDetailedVisits( startMillis: Long, endMillis: Long, @@ -28,7 +55,7 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi { ) { coroutineScope.launch { withContext(Dispatchers.Main) { - val visits = components.core.historyStorage.getDetailedVisits( + var visits = components.core.historyStorage.getDetailedVisits( startMillis, endMillis, excludeTypes.map { @@ -43,9 +70,7 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi { VisitType.FRAMED_LINK -> mozilla.components.concept.storage.VisitType.FRAMED_LINK VisitType.RELOAD -> mozilla.components.concept.storage.VisitType.RELOAD } - }) - - callback(Result.success(visits.map { + }).map { VisitInfo( url = it.url, title = it.title, @@ -65,6 +90,12 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi { isRemote = it.isRemote ) } + + if (!excludeTypes.contains(VisitType.DOWNLOAD)) { + visits = visits + components.core.store.state.downloads.toVisitInfoList(startMillis, endMillis) + } + + callback(Result.success(visits )) } } @@ -84,6 +115,19 @@ class GeckoHistoryApiImpl() : GeckoHistoryApi { } } + override fun deleteDownload( + id: String, + callback: (Result) -> Unit + ) { + coroutineScope.launch { + withContext(Dispatchers.Main) { + components.useCases.downloadsUseCases.removeDownload(id) + + callback(Result.success(Unit)) + } + } + } + override fun deleteVisitsBetween( startMillis: Long, endMillis: Long, diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt index ea7a8987..1dbbb8f6 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt @@ -1146,7 +1146,8 @@ data class VisitInfo ( val visitTime: Long, val visitType: VisitType, val previewImageUrl: String? = null, - val isRemote: Boolean + val isRemote: Boolean, + val contentId: String? = null ) { companion object { @@ -1157,7 +1158,8 @@ data class VisitInfo ( val visitType = pigeonVar_list[3] as VisitType val previewImageUrl = pigeonVar_list[4] as String? val isRemote = pigeonVar_list[5] as Boolean - return VisitInfo(url, title, visitTime, visitType, previewImageUrl, isRemote) + val contentId = pigeonVar_list[6] as String? + return VisitInfo(url, title, visitTime, visitType, previewImageUrl, isRemote, contentId) } } fun toList(): List { @@ -1168,6 +1170,7 @@ data class VisitInfo ( visitType, previewImageUrl, isRemote, + contentId, ) } override fun equals(other: Any?): Boolean { @@ -4767,6 +4770,7 @@ interface GeckoDeleteBrowsingDataController { interface GeckoHistoryApi { fun getDetailedVisits(startMillis: Long, endMillis: Long, excludeTypes: List, callback: (Result>) -> Unit) fun deleteVisit(url: String, timestamp: Long, callback: (Result) -> Unit) + fun deleteDownload(id: String, callback: (Result) -> Unit) fun deleteVisitsBetween(startMillis: Long, endMillis: Long, callback: (Result) -> Unit) companion object { @@ -4820,6 +4824,25 @@ interface GeckoHistoryApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteDownload$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val idArg = args[0] as String + api.deleteDownload(idArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(GeckoPigeonUtils.wrapError(error)) + } else { + reply.reply(GeckoPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisitsBetween$separatedMessageChannelSuffix", codec) if (api != null) { diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_history.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_history.dart index 2df11baa..f1cb372f 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_history.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_history.dart @@ -26,7 +26,20 @@ class GeckoHistoryService { } Future deleteVisit(VisitInfo info) { - return _api.deleteVisit(info.url, info.visitTime); + switch (info.visitType) { + case VisitType.link: + case VisitType.typed: + case VisitType.embed: + case VisitType.redirectPermanent: + case VisitType.redirectTemporary: + case VisitType.framedLink: + case VisitType.reload: + return _api.deleteVisit(info.url, info.visitTime); + case VisitType.download: + return _api.deleteDownload(info.contentId!); + case VisitType.bookmark: + throw UnimplementedError(); + } } Future deleteVisitsBetween(DateTime start, DateTime end) { diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart index 6ae5f31e..76648a18 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -1257,6 +1257,7 @@ class VisitInfo { required this.visitType, this.previewImageUrl, required this.isRemote, + this.contentId, }); String url; @@ -1271,6 +1272,8 @@ class VisitInfo { bool isRemote; + String? contentId; + List _toList() { return [ url, @@ -1279,6 +1282,7 @@ class VisitInfo { visitType, previewImageUrl, isRemote, + contentId, ]; } @@ -1294,6 +1298,7 @@ class VisitInfo { visitType: result[3]! as VisitType, previewImageUrl: result[4] as String?, isRemote: result[5]! as bool, + contentId: result[6] as String?, ); } @@ -5783,6 +5788,29 @@ class GeckoHistoryApi { } } + Future deleteDownload(String id) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteDownload$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([id]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + Future deleteVisitsBetween(int startMillis, int endMillis) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoHistoryApi.deleteVisitsBetween$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index f2afaf7b..a1428c73 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -425,6 +425,8 @@ class VisitInfo { final String? previewImageUrl; final bool isRemote; + final String? contentId; + VisitInfo( this.url, this.title, @@ -432,6 +434,7 @@ class VisitInfo { this.visitType, this.previewImageUrl, this.isRemote, + this.contentId, ); } @@ -1253,6 +1256,9 @@ abstract class GeckoHistoryApi { @async void deleteVisit(String url, int timestamp); + @async + void deleteDownload(String id); + @async void deleteVisitsBetween(int startMillis, int endMillis); }