diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0432276..81dac8d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,7 +23,7 @@ android { minSdk = 24 targetSdk = 34 versionCode = 67 - versionName = "2.3-alpha02" + versionName = "2.3-alpha03" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/skyd/rays/model/respository/SearchRepository.kt b/app/src/main/java/com/skyd/rays/model/respository/SearchRepository.kt index 17beea7..9e181d2 100644 --- a/app/src/main/java/com/skyd/rays/model/respository/SearchRepository.kt +++ b/app/src/main/java/com/skyd/rays/model/respository/SearchRepository.kt @@ -14,6 +14,7 @@ import com.skyd.rays.model.bean.StickerBean import com.skyd.rays.model.bean.StickerWithTags import com.skyd.rays.model.bean.TagBean import com.skyd.rays.model.db.dao.SearchDomainDao +import com.skyd.rays.model.db.dao.cache.StickerShareTimeDao import com.skyd.rays.model.db.dao.sticker.StickerDao import com.skyd.rays.model.preference.ExportStickerDirPreference import com.skyd.rays.model.preference.search.IntersectSearchBySpacePreference @@ -28,6 +29,7 @@ import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first @@ -39,9 +41,11 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking import javax.inject.Inject +import kotlin.math.pow class SearchRepository @Inject constructor( private val stickerDao: StickerDao, + private val stickerShareTimeDao: StickerShareTimeDao, ) : BaseRepository() { fun requestStickerWithTagsList(keyword: String): List = runBlocking { stickerDao.getStickerWithTagsList(genSql(k = keyword)).first() @@ -118,49 +122,42 @@ class SearchRepository @Inject constructor( } } - fun requestSearchBarPopularTags(count: Int): Flow>> { - return stickerDao.getPopularStickersList(count = count) + fun requestSearchBarPopularTags(count: Int): Flow> { + return combine( + stickerDao.getRecentSharedStickers(count = count shr 1), + stickerDao.getPopularStickersList(count = count shr 1), + ) { recentSharedStickers, popularStickers -> + recentSharedStickers.toMutableSet().apply { + addAll(popularStickers) + }.map { + it to stickerShareTimeDao.getShareTimeByUuid(it.sticker.uuid) + } + } .distinctUntilChanged() - .map { popularStickersList -> - val tagsMap: MutableMap, Long> = mutableMapOf() - val tagsCountMap: MutableMap, Long> = mutableMapOf() - val stickerUuidCountMap: MutableMap = mutableMapOf() - popularStickersList.forEach { - it.tags.forEach { tag -> - val tagString = tag.tag - if (tagString.length < 6) { - tagsCountMap[tagString to it.sticker.uuid] = tagsCountMap - .getOrDefault(tagString to it.sticker.uuid, 0) + 1 - tagsMap[tagString to it.sticker.uuid] = tagsMap - .getOrDefault( - tagString to it.sticker.uuid, - 0 - ) + it.sticker.shareCount - } + .map { stickersList -> + // Step 2: Sort the list + val sortedDataList = stickersList.sortedWith( + compareByDescending>> { + it.second.sum() + }.thenByDescending { it.first.sticker.shareCount }, + ) + + // Step 3: Count tag frequencies and add weights + val tagFrequency = mutableMapOf() + for ((index, data) in sortedDataList.withIndex()) { + val weight = (sortedDataList.size - index).toDouble().pow(4) // weight factor + for (tag in data.first.tags) { + // As the number of times a tag appears increases, + // reduce its new weight to avoid the first few tags being difficult to change + val newWeight = weight * + (1f / tagFrequency.getOrDefault(tag.tag, 1.0) + .coerceAtLeast(1.0)).pow(4) + tagFrequency[tag.tag] = tagFrequency.getOrDefault(tag.tag, 0.0) + newWeight } - stickerUuidCountMap[it.sticker.uuid] = 0 } - tagsCountMap.forEach { (t, u) -> - tagsMap[t] = tagsMap.getOrDefault(t, 0) * u - } - var result = tagsMap.toList().sortedByDescending { (_, value) -> value } - result = result.filter { - val stickUuid = it.first.second - val cnt = stickerUuidCountMap[stickUuid] - if (cnt != null) { - // 限制每个表情包只能推荐两个标签 - if (cnt >= 2) { - false - } else { - stickerUuidCountMap[stickUuid] = cnt + 1 - true - } - } else { - false - } - }.distinctBy { it.first.first } - val maxPopularValue = result.getOrNull(0)?.second ?: 1 - result.map { it.first.first to it.second.toFloat() / maxPopularValue } + + // Step 4: Sort tags by their frequencies + tagFrequency.entries.sortedByDescending { it.value }.map { it.key } }.flowOn(Dispatchers.IO) } diff --git a/app/src/main/java/com/skyd/rays/ui/screen/search/SearchPartialStateChange.kt b/app/src/main/java/com/skyd/rays/ui/screen/search/SearchPartialStateChange.kt index bb26d20..3964ce7 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/search/SearchPartialStateChange.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/search/SearchPartialStateChange.kt @@ -47,7 +47,7 @@ internal sealed interface SearchPartialStateChange { data class Failed(val msg: String) : SearchDataResult data class Success( val stickerWithTagsList: List, - val popularTags: List> + val popularTags: List ) : SearchDataResult } diff --git a/app/src/main/java/com/skyd/rays/ui/screen/search/SearchScreen.kt b/app/src/main/java/com/skyd/rays/ui/screen/search/SearchScreen.kt index 73b54f5..6d86c73 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/search/SearchScreen.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/search/SearchScreen.kt @@ -41,17 +41,13 @@ import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RichTooltip import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.TooltipBox -import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.VerticalDivider -import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -367,33 +363,16 @@ fun TrailingIcon( @Composable fun PopularTagsBar( onTagClicked: (String) -> Unit, - tags: List>, + tags: List, ) { - val eachTag: @Composable (Pair) -> Unit = { item -> - TooltipBox( - positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(), - tooltip = { - RichTooltip( - title = { Text(item.first) }, - text = { - Text( - text = stringResource( - R.string.home_screen_popular_tags_popular_value, item.second - ) - ) - } - ) - }, - state = rememberTooltipState(), - ) { - Text( - modifier = Modifier - .clip(RoundedCornerShape(6.dp)) - .clickable { onTagClicked(item.first) } - .padding(horizontal = 10.dp, vertical = 6.dp), - text = item.first - ) - } + val eachTag: @Composable (String) -> Unit = { item -> + Text( + modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .clickable { onTagClicked(item) } + .padding(horizontal = 10.dp, vertical = 6.dp), + text = item + ) } Box { diff --git a/app/src/main/java/com/skyd/rays/ui/screen/search/SearchState.kt b/app/src/main/java/com/skyd/rays/ui/screen/search/SearchState.kt index 83aef37..61f861d 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/search/SearchState.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/search/SearchState.kt @@ -21,6 +21,6 @@ sealed class SearchDataState { data object Init : SearchDataState() data class Success( val stickerWithTagsList: List, - val popularTags: List>, + val popularTags: List, ) : SearchDataState() } diff --git a/app/src/main/java/com/skyd/rays/ui/screen/settings/appearance/style/SearchScreenPreview.kt b/app/src/main/java/com/skyd/rays/ui/screen/settings/appearance/style/SearchScreenPreview.kt index 1924615..0b311d8 100644 --- a/app/src/main/java/com/skyd/rays/ui/screen/settings/appearance/style/SearchScreenPreview.kt +++ b/app/src/main/java/com/skyd/rays/ui/screen/settings/appearance/style/SearchScreenPreview.kt @@ -71,7 +71,7 @@ private fun RaysSearchBarPreview() { AnimatedVisibility(visible = LocalShowPopularTags.current) { PopularTagsBar( onTagClicked = {}, - tags = listOf("Tag" to 1f, "LOL" to 0.9f), + tags = listOf("Tag", "LOL"), ) } SearchResultList( diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index c195eb7..8f14ccf 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -211,7 +211,6 @@ Preencher limites Vazio~ Lixeira remota - Valor popular: %.2f Enviar sticker Ao enviar dados locais para o remoto, os stickers locais substituirão o conteúdo remoto se houver um conflito. Se um sticker não estiver existente no local, mas no remoto, o sticker será movido para a lixeira remota. Visualizar a lixeira remota (logicamente excluída) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 56a290c..207172c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -217,7 +217,6 @@ 上次修改时间 选择导出文件夹 请选择导出文件夹 - 热门值:%.2f 表情包表 UUID 标题 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f858861..37cfb4d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -292,7 +292,6 @@ 刪除本地資料 + 表情包 沒有軟體包可用:%s 建議的標籤 - 熱門值:%.2f 修改時間 內部 標籤表 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44926bf..be08ba3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -223,7 +223,6 @@ Last modified time Select export folder Please select an export folder - Popular value: %.2f Sticker table UUID Title