Add favorites part to clock widget
This commit is contained in:
parent
93bd62545d
commit
5075c368a2
@ -18,8 +18,8 @@ interface SearchDao {
|
|||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun insertSkipExisting(items: FavoritesItemEntity)
|
fun insertSkipExisting(items: FavoritesItemEntity)
|
||||||
|
|
||||||
@Query("SELECT * FROM Searchable WHERE pinned > 0 AND (NOT :excludeCalendarEvents OR NOT `key` LIKE 'calendar://%') ORDER BY pinned DESC, launchCount DESC")
|
@Query("SELECT * FROM Searchable WHERE pinned > 0 AND (NOT :excludeCalendarEvents OR NOT `key` LIKE 'calendar://%') ORDER BY pinned DESC, launchCount DESC LIMIT :limit")
|
||||||
fun getFavorites(excludeCalendarEvents: Boolean = false): Flow<List<FavoritesItemEntity>>
|
fun getFavorites(excludeCalendarEvents: Boolean = false, limit: Int): Flow<List<FavoritesItemEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM Searchable WHERE pinned > 0 AND `key` LIKE 'calendar://%' ORDER BY pinned DESC, launchCount DESC")
|
@Query("SELECT * FROM Searchable WHERE pinned > 0 AND `key` LIKE 'calendar://%' ORDER BY pinned DESC, launchCount DESC")
|
||||||
fun getPinnedCalendarEvents(): Flow<List<FavoritesItemEntity>>
|
fun getPinnedCalendarEvents(): Flow<List<FavoritesItemEntity>>
|
||||||
|
|||||||
@ -17,8 +17,8 @@ data class FavoritesItem(
|
|||||||
var launchCount: Int,
|
var launchCount: Int,
|
||||||
var pinPosition: Int,
|
var pinPosition: Int,
|
||||||
var hidden: Boolean
|
var hidden: Boolean
|
||||||
) : KoinComponent {
|
) {
|
||||||
private val serializer: SearchableSerializer by inject { parametersOf(searchable) }
|
private val serializer: SearchableSerializer = getSerializer(searchable)
|
||||||
|
|
||||||
fun toDatabaseEntity(): FavoritesItemEntity? {
|
fun toDatabaseEntity(): FavoritesItemEntity? {
|
||||||
val serializer = serializer
|
val serializer = serializer
|
||||||
|
|||||||
@ -1,21 +1,41 @@
|
|||||||
package de.mm20.launcher2.favorites
|
package de.mm20.launcher2.favorites
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import de.mm20.launcher2.appshortcuts.AppShortcutDeserializer
|
||||||
|
import de.mm20.launcher2.appshortcuts.AppShortcutSerializer
|
||||||
|
import de.mm20.launcher2.calendar.CalendarEventDeserializer
|
||||||
|
import de.mm20.launcher2.calendar.CalendarEventSerializer
|
||||||
|
import de.mm20.launcher2.contacts.ContactDeserializer
|
||||||
|
import de.mm20.launcher2.contacts.ContactSerializer
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
import de.mm20.launcher2.database.entities.FavoritesItemEntity
|
import de.mm20.launcher2.database.entities.FavoritesItemEntity
|
||||||
|
import de.mm20.launcher2.files.*
|
||||||
import de.mm20.launcher2.ktx.ceilToInt
|
import de.mm20.launcher2.ktx.ceilToInt
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.search.NullDeserializer
|
||||||
|
import de.mm20.launcher2.search.NullSerializer
|
||||||
import de.mm20.launcher2.search.SearchableDeserializer
|
import de.mm20.launcher2.search.SearchableDeserializer
|
||||||
import de.mm20.launcher2.search.data.CalendarEvent
|
import de.mm20.launcher2.search.SearchableSerializer
|
||||||
import de.mm20.launcher2.search.data.Searchable
|
import de.mm20.launcher2.search.data.*
|
||||||
|
import de.mm20.launcher2.websites.WebsiteDeserializer
|
||||||
|
import de.mm20.launcher2.websites.WebsiteSerializer
|
||||||
|
import de.mm20.launcher2.wikipedia.WikipediaDeserializer
|
||||||
|
import de.mm20.launcher2.wikipedia.WikipediaSerializer
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.get
|
import org.koin.core.component.get
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
interface FavoritesRepository {
|
interface FavoritesRepository {
|
||||||
fun getFavorites(excludeCalendarEvents: Boolean = false): Flow<List<Searchable>>
|
fun getFavorites(
|
||||||
|
columns: Int,
|
||||||
|
maxRows: Int? = null,
|
||||||
|
excludeCalendarEvents: Boolean = false
|
||||||
|
): Flow<List<Searchable>>
|
||||||
|
|
||||||
fun getPinnedCalendarEvents(): Flow<List<Searchable>>
|
fun getPinnedCalendarEvents(): Flow<List<Searchable>>
|
||||||
fun isPinned(searchable: Searchable): Flow<Boolean>
|
fun isPinned(searchable: Searchable): Flow<Boolean>
|
||||||
fun pinItem(searchable: Searchable)
|
fun pinItem(searchable: Searchable)
|
||||||
@ -32,42 +52,42 @@ interface FavoritesRepository {
|
|||||||
internal class FavoritesRepositoryImpl(
|
internal class FavoritesRepositoryImpl(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val database: AppDatabase,
|
private val database: AppDatabase,
|
||||||
private val dataStore: LauncherDataStore
|
|
||||||
) : FavoritesRepository, KoinComponent {
|
) : FavoritesRepository, KoinComponent {
|
||||||
|
|
||||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
|
||||||
override fun getFavorites(excludeCalendarEvents: Boolean): Flow<List<Searchable>> =
|
override fun getFavorites(
|
||||||
|
columns: Int,
|
||||||
|
maxRows: Int?,
|
||||||
|
excludeCalendarEvents: Boolean
|
||||||
|
): Flow<List<Searchable>> =
|
||||||
channelFlow {
|
channelFlow {
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
|
||||||
val gridColumns = dataStore.data.map { it.grid.columnCount }.distinctUntilChanged()
|
|
||||||
val dao = database.searchDao()
|
val dao = database.searchDao()
|
||||||
|
val pinnedFavorites =
|
||||||
val pinnedFavorites = dao.getFavorites(excludeCalendarEvents).map {
|
dao.getFavorites(excludeCalendarEvents, columns * (maxRows ?: 20)).map {
|
||||||
it.mapNotNull {
|
it.mapNotNull {
|
||||||
val item = fromDatabaseEntity(it).searchable
|
|
||||||
if (item == null) {
|
|
||||||
dao.deleteByKey(it.key)
|
|
||||||
}
|
|
||||||
return@mapNotNull item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pinnedFavorites.collectLatest { pinned ->
|
|
||||||
gridColumns.collectLatest { columns ->
|
|
||||||
var favCount = (pinned.size.toDouble() / columns).ceilToInt() * columns
|
|
||||||
if (pinned.size < columns) favCount += columns
|
|
||||||
val autoFavs = dao.getAutoFavorites(favCount - pinned.size).mapNotNull {
|
|
||||||
val item = fromDatabaseEntity(it).searchable
|
val item = fromDatabaseEntity(it).searchable
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
dao.deleteByKey(it.key)
|
dao.deleteByKey(it.key)
|
||||||
}
|
}
|
||||||
return@mapNotNull item
|
return@mapNotNull item
|
||||||
}
|
}
|
||||||
send(pinned + autoFavs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pinnedFavorites.collectLatest { pinned ->
|
||||||
|
var favCount = (pinned.size.toDouble() / columns).ceilToInt() * columns
|
||||||
|
if (pinned.size < columns) favCount += columns
|
||||||
|
val autoFavs = dao.getAutoFavorites(
|
||||||
|
favCount.coerceAtMost((maxRows ?: 20) * columns) - pinned.size
|
||||||
|
).mapNotNull {
|
||||||
|
val item = fromDatabaseEntity(it).searchable
|
||||||
|
if (item == null) {
|
||||||
|
dao.deleteByKey(it.key)
|
||||||
|
}
|
||||||
|
return@mapNotNull item
|
||||||
|
}
|
||||||
|
send(pinned + autoFavs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,7 +193,7 @@ internal class FavoritesRepositoryImpl(
|
|||||||
|
|
||||||
|
|
||||||
private fun fromDatabaseEntity(entity: FavoritesItemEntity): FavoritesItem {
|
private fun fromDatabaseEntity(entity: FavoritesItemEntity): FavoritesItem {
|
||||||
val deserializer: SearchableDeserializer = get { parametersOf(entity.serializedSearchable) }
|
val deserializer: SearchableDeserializer = getDeserializer(context, entity.serializedSearchable)
|
||||||
return FavoritesItem(
|
return FavoritesItem(
|
||||||
key = entity.key,
|
key = entity.key,
|
||||||
searchable = deserializer.deserialize(entity.serializedSearchable.substringAfter("#")),
|
searchable = deserializer.deserialize(entity.serializedSearchable.substringAfter("#")),
|
||||||
|
|||||||
@ -18,80 +18,5 @@ import org.koin.android.ext.koin.androidContext
|
|||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val favoritesModule = module {
|
val favoritesModule = module {
|
||||||
factory { (searchable: Searchable) ->
|
single<FavoritesRepository> { FavoritesRepositoryImpl(androidContext(), get()) }
|
||||||
if (searchable is LauncherApp) {
|
|
||||||
return@factory LauncherAppSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is AppShortcut) {
|
|
||||||
return@factory AppShortcutSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is CalendarEvent) {
|
|
||||||
return@factory CalendarEventSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is Contact) {
|
|
||||||
return@factory ContactSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is Wikipedia) {
|
|
||||||
return@factory WikipediaSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is GDriveFile) {
|
|
||||||
return@factory GDriveFileSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is OneDriveFile) {
|
|
||||||
return@factory OneDriveFileSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is OwncloudFile) {
|
|
||||||
return@factory OwncloudFileSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is NextcloudFile) {
|
|
||||||
return@factory NextcloudFileSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is LocalFile) {
|
|
||||||
return@factory LocalFileSerializer()
|
|
||||||
}
|
|
||||||
if (searchable is Website) {
|
|
||||||
return@factory WebsiteSerializer()
|
|
||||||
}
|
|
||||||
return@factory NullSerializer()
|
|
||||||
}
|
|
||||||
|
|
||||||
factory { (serialized: String) ->
|
|
||||||
val type = serialized.substringBefore("#")
|
|
||||||
if (type == "app") {
|
|
||||||
return@factory LauncherAppDeserializer(androidContext())
|
|
||||||
}
|
|
||||||
if (type == "shortcut") {
|
|
||||||
return@factory AppShortcutDeserializer(androidContext())
|
|
||||||
}
|
|
||||||
if (type == "calendar") {
|
|
||||||
return@factory CalendarEventDeserializer(androidContext())
|
|
||||||
}
|
|
||||||
if (type == "contact") {
|
|
||||||
return@factory ContactDeserializer(androidContext())
|
|
||||||
}
|
|
||||||
if (type == "wikipedia") {
|
|
||||||
return@factory WikipediaDeserializer(androidContext())
|
|
||||||
}
|
|
||||||
if (type == "gdrive") {
|
|
||||||
return@factory GDriveFileDeserializer()
|
|
||||||
}
|
|
||||||
if (type == "onedrive") {
|
|
||||||
return@factory OneDriveFileDeserializer()
|
|
||||||
}
|
|
||||||
if (type == "nextcloud") {
|
|
||||||
return@factory NextcloudFileDeserializer()
|
|
||||||
}
|
|
||||||
if (type == "owncloud") {
|
|
||||||
return@factory OwncloudFileDeserializer()
|
|
||||||
}
|
|
||||||
if (type == "file") {
|
|
||||||
return@factory LocalFileDeserializer(androidContext())
|
|
||||||
}
|
|
||||||
if (type == "website") {
|
|
||||||
return@factory WebsiteDeserializer()
|
|
||||||
}
|
|
||||||
return@factory NullDeserializer()
|
|
||||||
}
|
|
||||||
|
|
||||||
single<FavoritesRepository> { FavoritesRepositoryImpl(androidContext(), get(), get()) }
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
package de.mm20.launcher2.favorites
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import de.mm20.launcher2.appshortcuts.AppShortcutDeserializer
|
||||||
|
import de.mm20.launcher2.appshortcuts.AppShortcutSerializer
|
||||||
|
import de.mm20.launcher2.calendar.CalendarEventDeserializer
|
||||||
|
import de.mm20.launcher2.calendar.CalendarEventSerializer
|
||||||
|
import de.mm20.launcher2.contacts.ContactDeserializer
|
||||||
|
import de.mm20.launcher2.contacts.ContactSerializer
|
||||||
|
import de.mm20.launcher2.files.*
|
||||||
|
import de.mm20.launcher2.search.NullDeserializer
|
||||||
|
import de.mm20.launcher2.search.NullSerializer
|
||||||
|
import de.mm20.launcher2.search.SearchableDeserializer
|
||||||
|
import de.mm20.launcher2.search.SearchableSerializer
|
||||||
|
import de.mm20.launcher2.search.data.*
|
||||||
|
import de.mm20.launcher2.websites.WebsiteDeserializer
|
||||||
|
import de.mm20.launcher2.websites.WebsiteSerializer
|
||||||
|
import de.mm20.launcher2.wikipedia.WikipediaDeserializer
|
||||||
|
import de.mm20.launcher2.wikipedia.WikipediaSerializer
|
||||||
|
|
||||||
|
|
||||||
|
internal fun getSerializer(searchable: Searchable?): SearchableSerializer {
|
||||||
|
if (searchable is LauncherApp) {
|
||||||
|
return LauncherAppSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is AppShortcut) {
|
||||||
|
return AppShortcutSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is CalendarEvent) {
|
||||||
|
return CalendarEventSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is Contact) {
|
||||||
|
return ContactSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is Wikipedia) {
|
||||||
|
return WikipediaSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is GDriveFile) {
|
||||||
|
return GDriveFileSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is OneDriveFile) {
|
||||||
|
return OneDriveFileSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is OwncloudFile) {
|
||||||
|
return OwncloudFileSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is NextcloudFile) {
|
||||||
|
return NextcloudFileSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is LocalFile) {
|
||||||
|
return LocalFileSerializer()
|
||||||
|
}
|
||||||
|
if (searchable is Website) {
|
||||||
|
return WebsiteSerializer()
|
||||||
|
}
|
||||||
|
return NullSerializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getDeserializer(context: Context, serialized: String): SearchableDeserializer {
|
||||||
|
val type = serialized.substringBefore("#")
|
||||||
|
if (type == "app") {
|
||||||
|
return LauncherAppDeserializer(context)
|
||||||
|
}
|
||||||
|
if (type == "shortcut") {
|
||||||
|
return AppShortcutDeserializer(context)
|
||||||
|
}
|
||||||
|
if (type == "calendar") {
|
||||||
|
return CalendarEventDeserializer(context)
|
||||||
|
}
|
||||||
|
if (type == "contact") {
|
||||||
|
return ContactDeserializer(context)
|
||||||
|
}
|
||||||
|
if (type == "wikipedia") {
|
||||||
|
return WikipediaDeserializer(context)
|
||||||
|
}
|
||||||
|
if (type == "gdrive") {
|
||||||
|
return GDriveFileDeserializer()
|
||||||
|
}
|
||||||
|
if (type == "onedrive") {
|
||||||
|
return OneDriveFileDeserializer()
|
||||||
|
}
|
||||||
|
if (type == "nextcloud") {
|
||||||
|
return NextcloudFileDeserializer()
|
||||||
|
}
|
||||||
|
if (type == "owncloud") {
|
||||||
|
return OwncloudFileDeserializer()
|
||||||
|
}
|
||||||
|
if (type == "file") {
|
||||||
|
return LocalFileDeserializer(context)
|
||||||
|
}
|
||||||
|
if (type == "website") {
|
||||||
|
return WebsiteDeserializer()
|
||||||
|
}
|
||||||
|
return NullDeserializer()
|
||||||
|
}
|
||||||
@ -485,6 +485,8 @@
|
|||||||
<string name="preference_clock_widget_style_summary">Select a clock</string>
|
<string name="preference_clock_widget_style_summary">Select a clock</string>
|
||||||
<string name="preference_clockwidget_date_part">Date</string>
|
<string name="preference_clockwidget_date_part">Date</string>
|
||||||
<string name="preference_clockwidget_date_part_summary">Show the current date</string>
|
<string name="preference_clockwidget_date_part_summary">Show the current date</string>
|
||||||
|
<string name="preference_clockwidget_favorites_part">Favorites</string>
|
||||||
|
<string name="preference_clockwidget_favorites_part_summary">Show the first row of pinned items</string>
|
||||||
<string name="preference_clockwidget_music_part">Media</string>
|
<string name="preference_clockwidget_music_part">Media</string>
|
||||||
<string name="preference_clockwidget_music_part_summary">Show media controls when there is an active media session</string>
|
<string name="preference_clockwidget_music_part_summary">Show media controls when there is an active media session</string>
|
||||||
<string name="preference_clockwidget_battery_part">Battery</string>
|
<string name="preference_clockwidget_battery_part">Battery</string>
|
||||||
|
|||||||
@ -39,6 +39,7 @@ fun createFactorySettings(context: Context): Settings {
|
|||||||
.setBatteryPart(true)
|
.setBatteryPart(true)
|
||||||
.setDatePart(true)
|
.setDatePart(true)
|
||||||
.setMusicPart(true)
|
.setMusicPart(true)
|
||||||
|
.setFavoritesPart(false)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setFavorites(
|
.setFavorites(
|
||||||
|
|||||||
@ -56,6 +56,7 @@ message Settings {
|
|||||||
bool music_part = 4;
|
bool music_part = 4;
|
||||||
bool battery_part = 5;
|
bool battery_part = 5;
|
||||||
bool alarm_part = 6;
|
bool alarm_part = 6;
|
||||||
|
bool favorites_part = 7;
|
||||||
}
|
}
|
||||||
ClockWidgetSettings clock_widget = 7;
|
ClockWidgetSettings clock_widget = 7;
|
||||||
|
|
||||||
|
|||||||
@ -35,8 +35,9 @@ fun ProvideSettings(
|
|||||||
val favoritesEnabled by remember {
|
val favoritesEnabled by remember {
|
||||||
combine(
|
combine(
|
||||||
widgetRepository.isFavoritesWidgetEnabled(),
|
widgetRepository.isFavoritesWidgetEnabled(),
|
||||||
dataStore.data.map { it.favorites.enabled }
|
dataStore.data.map { it.favorites.enabled },
|
||||||
) { a, b -> a || b }.distinctUntilChanged()
|
dataStore.data.map { it.clockWidget.favoritesPart },
|
||||||
|
) { a, b, c -> a || b || c }.distinctUntilChanged()
|
||||||
}.collectAsState(true)
|
}.collectAsState(true)
|
||||||
|
|
||||||
val gridColumns by remember {
|
val gridColumns by remember {
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package de.mm20.launcher2.ui.launcher.search
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.asLiveData
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.applications.AppRepository
|
import de.mm20.launcher2.applications.AppRepository
|
||||||
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
||||||
@ -75,8 +74,13 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
return@collectLatest
|
return@collectLatest
|
||||||
}
|
}
|
||||||
widgetRepository.isCalendarWidgetEnabled().collectLatest { excludeCalendar ->
|
widgetRepository.isCalendarWidgetEnabled().collectLatest { excludeCalendar ->
|
||||||
favoritesRepository.getFavorites(excludeCalendarEvents = excludeCalendar).collectLatest {
|
dataStore.data.map { it.grid.columnCount }.collectLatest { columns ->
|
||||||
favorites.value = it
|
favoritesRepository.getFavorites(
|
||||||
|
columns = columns,
|
||||||
|
excludeCalendarEvents = excludeCalendar
|
||||||
|
).collectLatest {
|
||||||
|
favorites.value = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.search.common
|
package de.mm20.launcher2.ui.launcher.search.common
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
@ -42,7 +41,7 @@ import kotlinx.coroutines.delay
|
|||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GridItem(modifier: Modifier = Modifier, item: Searchable) {
|
fun GridItem(modifier: Modifier = Modifier, item: Searchable, showLabels: Boolean = true) {
|
||||||
val viewModel = remember(item.key) { GridItemVM(item) }
|
val viewModel = remember(item.key) { GridItemVM(item) }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -74,16 +73,18 @@ fun GridItem(modifier: Modifier = Modifier, item: Searchable) {
|
|||||||
showPopup = true
|
showPopup = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Text(
|
if (showLabels) {
|
||||||
modifier = Modifier
|
Text(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.padding(top = 8.dp),
|
.fillMaxWidth()
|
||||||
text = item.label,
|
.padding(top = 8.dp),
|
||||||
textAlign = TextAlign.Center,
|
text = item.label,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
textAlign = TextAlign.Center,
|
||||||
maxLines = 1,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
overflow = TextOverflow.Ellipsis
|
maxLines = 1,
|
||||||
)
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
if (showPopup) {
|
if (showPopup) {
|
||||||
ItemPopup(origin = bounds, searchable = item, onDismissRequest = { showPopup = false })
|
ItemPopup(origin = bounds, searchable = item, onDismissRequest = { showPopup = false })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,9 +14,9 @@ import kotlin.math.ceil
|
|||||||
fun SearchResultGrid(
|
fun SearchResultGrid(
|
||||||
items: List<Searchable>,
|
items: List<Searchable>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
showLabels: Boolean = true,
|
||||||
|
columns: Int = LocalGridColumns.current,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val columns = LocalGridColumns.current
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
@ -31,7 +31,9 @@ fun SearchResultGrid(
|
|||||||
GridItem(
|
GridItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(4.dp, 8.dp), item = item
|
.padding(4.dp, 8.dp),
|
||||||
|
item = item,
|
||||||
|
showLabels = showLabels
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|||||||
@ -56,7 +56,6 @@ fun ClockWidget(
|
|||||||
if (layout == ClockWidgetLayout.Vertical) {
|
if (layout == ClockWidgetLayout.Vertical) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier.height(IntrinsicSize.Min),
|
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.clickable(
|
modifier = Modifier.clickable(
|
||||||
|
|||||||
@ -32,6 +32,7 @@ class ClockWidgetVM : ViewModel(), KoinComponent {
|
|||||||
if (it.musicPart) providers += MusicPartProvider()
|
if (it.musicPart) providers += MusicPartProvider()
|
||||||
if (it.batteryPart) providers += BatteryPartProvider()
|
if (it.batteryPart) providers += BatteryPartProvider()
|
||||||
if (it.alarmPart) providers += AlarmPartProvider()
|
if (it.alarmPart) providers += AlarmPartProvider()
|
||||||
|
if (it.favoritesPart) providers += FavoritesPartProvider()
|
||||||
partProviders.value = providers
|
partProviders.value = providers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,9 +49,11 @@ class ClockWidgetVM : ViewModel(), KoinComponent {
|
|||||||
val rankings = providers.map { it.getRanking(context) }
|
val rankings = providers.map { it.getRanking(context) }
|
||||||
combine(rankings) { r ->
|
combine(rankings) { r ->
|
||||||
var prov = providers[0]
|
var prov = providers[0]
|
||||||
|
var ranking = r[0]
|
||||||
for (i in 1 until providers.size) {
|
for (i in 1 until providers.size) {
|
||||||
if (r[i - 1] < r[i]) {
|
if (ranking < r[i]) {
|
||||||
prov = providers[i]
|
prov = providers[i]
|
||||||
|
ranking = r[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@combine prov
|
return@combine prov
|
||||||
|
|||||||
@ -0,0 +1,69 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.widgets.clock.parts
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||||
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
|
import de.mm20.launcher2.preferences.Settings.ClockWidgetSettings.ClockWidgetLayout
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||||
|
import de.mm20.launcher2.widgets.WidgetRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
class FavoritesPartProvider : PartProvider, KoinComponent {
|
||||||
|
|
||||||
|
private val favoritesRepository: FavoritesRepository by inject()
|
||||||
|
private val widgetRepository: WidgetRepository by inject()
|
||||||
|
private val dataStore: LauncherDataStore by inject()
|
||||||
|
|
||||||
|
override fun getRanking(context: Context): Flow<Int> = flow {
|
||||||
|
emit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Component(layout: ClockWidgetLayout) {
|
||||||
|
val columns by remember(layout) {
|
||||||
|
dataStore.data.map {
|
||||||
|
val c = it.grid.columnCount
|
||||||
|
if (layout == ClockWidgetLayout.Horizontal) c - 2 else c
|
||||||
|
}
|
||||||
|
}.collectAsState(0)
|
||||||
|
val excludeCalendar by remember { widgetRepository.isCalendarWidgetEnabled() }.collectAsState(
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
val favorites by remember(columns, excludeCalendar, layout) {
|
||||||
|
favoritesRepository.getFavorites(
|
||||||
|
columns = columns,
|
||||||
|
maxRows = 1,
|
||||||
|
excludeCalendarEvents = excludeCalendar
|
||||||
|
)
|
||||||
|
}.collectAsState(emptyList())
|
||||||
|
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.wrapContentHeight()
|
||||||
|
) {
|
||||||
|
SearchResultGrid(
|
||||||
|
items = favorites, showLabels = false, columns = columns,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,13 +16,19 @@ import org.koin.core.component.inject
|
|||||||
class FavoritesWidgetVM: ViewModel(), KoinComponent {
|
class FavoritesWidgetVM: ViewModel(), KoinComponent {
|
||||||
private val favoritesRepository: FavoritesRepository by inject()
|
private val favoritesRepository: FavoritesRepository by inject()
|
||||||
private val widgetRepository: WidgetRepository by inject()
|
private val widgetRepository: WidgetRepository by inject()
|
||||||
|
private val dataStore: LauncherDataStore by inject()
|
||||||
val favorites = MutableLiveData<List<Searchable>>(emptyList())
|
val favorites = MutableLiveData<List<Searchable>>(emptyList())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
widgetRepository.isCalendarWidgetEnabled().collectLatest { excludeCalendar ->
|
widgetRepository.isCalendarWidgetEnabled().collectLatest { excludeCalendar ->
|
||||||
favoritesRepository.getFavorites(excludeCalendarEvents = excludeCalendar).collectLatest {
|
dataStore.data.map { it.grid.columnCount }.collectLatest { columns ->
|
||||||
favorites.value = it
|
favoritesRepository.getFavorites(
|
||||||
|
columns = columns,
|
||||||
|
excludeCalendarEvents = excludeCalendar
|
||||||
|
).collectLatest {
|
||||||
|
favorites.value = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,20 @@ fun ClockWidgetSettingsScreen() {
|
|||||||
viewModel.setDatePart(it)
|
viewModel.setDatePart(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
val favoritesPart by viewModel.favoritesPart.observeAsState()
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.preference_clockwidget_favorites_part),
|
||||||
|
summary = stringResource(R.string.preference_clockwidget_favorites_part_summary),
|
||||||
|
icon = Icons.Rounded.Star,
|
||||||
|
value = favoritesPart == true,
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.setFavoritesPart(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
val musicPart by viewModel.musicPart.observeAsState()
|
val musicPart by viewModel.musicPart.observeAsState()
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = stringResource(R.string.preference_clockwidget_music_part),
|
title = stringResource(R.string.preference_clockwidget_music_part),
|
||||||
|
|||||||
@ -46,6 +46,29 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
.setClockWidget(
|
.setClockWidget(
|
||||||
it.clockWidget.toBuilder()
|
it.clockWidget.toBuilder()
|
||||||
.setDatePart(datePart)
|
.setDatePart(datePart)
|
||||||
|
.also {
|
||||||
|
if (datePart) {
|
||||||
|
it.setFavoritesPart(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val favoritesPart = dataStore.data.map { it.clockWidget.favoritesPart }.asLiveData()
|
||||||
|
fun setFavoritesPart(favoritesPart: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setClockWidget(
|
||||||
|
it.clockWidget.toBuilder()
|
||||||
|
.setFavoritesPart(favoritesPart)
|
||||||
|
.also {
|
||||||
|
if (favoritesPart) {
|
||||||
|
it.setDatePart(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
).build()
|
).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user