/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.fenix.components import android.content.Context import android.os.StrictMode import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.createTab import mozilla.components.feature.tab.collections.Tab import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollectionStorage import mozilla.components.support.base.observer.Observable import mozilla.components.support.base.observer.ObserverRegistry import mozilla.components.support.ktx.kotlin.toShortUrl import org.mozilla.fenix.ext.components import org.mozilla.fenix.perf.StrictModeManager private const val COLLECTION_MAX_TITLE_LENGTH = 20 class TabCollectionStorage( private val context: Context, strictMode: StrictModeManager, private val delegate: Observable = ObserverRegistry(), ) : Observable by delegate { /** * Interface to be implemented by classes that want to observe the storage */ interface Observer { /** * A collection has been created */ fun onCollectionCreated(title: String, sessions: List, id: Long?) = Unit /** * Tab(s) have been added to collection */ fun onTabsAdded(tabCollection: TabCollection, sessions: List) = Unit /** * Collection has been renamed */ fun onCollectionRenamed(tabCollection: TabCollection, title: String) = Unit } private val ioScope = CoroutineScope(Dispatchers.IO) var cachedTabCollections = listOf() private val collectionStorage by lazy { strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { TabCollectionStorage(context) } } suspend fun createCollection(title: String, sessions: List): Long? { return withContext(ioScope.coroutineContext) { val id = collectionStorage.createCollection(title, sessions) notifyObservers { onCollectionCreated(title, sessions, id) } id } } suspend fun createCollection(tabCollection: TabCollection): Long? { return withContext(ioScope.coroutineContext) { val sessions = tabCollection.tabs.map { createTab(url = it.url, title = it.title) } val id = collectionStorage.createCollection(tabCollection.title, sessions) notifyObservers { onCollectionCreated(tabCollection.title, sessions, id) } id } } suspend fun addTabsToCollection(tabCollection: TabCollection, sessions: List): Long? { return withContext(ioScope.coroutineContext) { val id = collectionStorage.addTabsToCollection(tabCollection, sessions) notifyObservers { onTabsAdded(tabCollection, sessions) } id } } fun getCollections(): LiveData> { return collectionStorage.getCollections().asLiveData() } suspend fun removeCollection(tabCollection: TabCollection) = ioScope.launch { collectionStorage.removeCollection(tabCollection) }.join() suspend fun removeTabFromCollection(tabCollection: TabCollection, tab: Tab) = ioScope.launch { collectionStorage.removeTabFromCollection(tabCollection, tab) }.join() suspend fun renameCollection(tabCollection: TabCollection, title: String) = ioScope.launch { collectionStorage.renameCollection(tabCollection, title) notifyObservers { onCollectionRenamed(tabCollection, title) } }.join() } fun TabCollection.description(context: Context): String { return this.tabs .map { it.url.toShortUrl(context.components.publicSuffixList) } .map { if (it.length > COLLECTION_MAX_TITLE_LENGTH) { it.substring( 0, COLLECTION_MAX_TITLE_LENGTH, ) + "…" } else { it } } .distinct() .joinToString(", ") }