diff --git a/app/src/main/java/com/huanchengfly/tieba/post/App.kt b/app/src/main/java/com/huanchengfly/tieba/post/App.kt index 42d79a64..3a0cb5e2 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/App.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/App.kt @@ -40,6 +40,7 @@ import com.huanchengfly.tieba.post.utils.AccountUtil import com.huanchengfly.tieba.post.utils.AppIconUtil import com.huanchengfly.tieba.post.utils.AppIconUtil.disableComponent import com.huanchengfly.tieba.post.utils.AppIconUtil.enableComponent +import com.huanchengfly.tieba.post.utils.BlockManager import com.huanchengfly.tieba.post.utils.ClientUtils import com.huanchengfly.tieba.post.utils.EmoticonManager import com.huanchengfly.tieba.post.utils.Icons @@ -60,10 +61,8 @@ import com.microsoft.appcenter.distribute.ReleaseDetails import com.microsoft.appcenter.distribute.UpdateAction import com.microsoft.appcenter.distribute.UpdateTrack import dagger.hilt.android.HiltAndroidApp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import org.litepal.LitePal +import kotlin.concurrent.thread @HiltAndroidApp @@ -141,13 +140,10 @@ class App : Application(), IApp, SketchFactory { registerActivityLifecycleCallbacks(ClipBoardLinkDetector) registerActivityLifecycleCallbacks(OAIDGetter) PluginManager.init(this) - CoroutineScope(Dispatchers.IO).apply { - launch { - EmoticonManager.init(this@App) - } - launch { - ClientUtils.init(this@App) - } + thread { + BlockManager.init() + EmoticonManager.init(this@App) + ClientUtils.init(this@App) } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/api/Utils.kt b/app/src/main/java/com/huanchengfly/tieba/post/api/Utils.kt index a467e520..4894e828 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/api/Utils.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/api/Utils.kt @@ -17,12 +17,15 @@ import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo import com.huanchengfly.tieba.post.arch.wrapImmutable import com.huanchengfly.tieba.post.ui.common.PbContentRender import com.huanchengfly.tieba.post.ui.common.PicContentRender -import com.huanchengfly.tieba.post.ui.common.TextContentRender +import com.huanchengfly.tieba.post.ui.common.TextContentRender.Companion.appendText +import com.huanchengfly.tieba.post.ui.common.VideoContentRender import com.huanchengfly.tieba.post.ui.common.theme.utils.ThemeUtils import com.huanchengfly.tieba.post.ui.utils.getPhotoViewData import com.huanchengfly.tieba.post.utils.EmoticonManager import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString import com.huanchengfly.tieba.post.utils.ImageUtil +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList private val defaultUserAgent: String = @@ -101,6 +104,17 @@ private val PbContent.picUrl: String src ) +val List.plainText: String + get() = joinToString(separator = "") { + when (it.type) { + 0, 1, 4, 9, 27 -> it.text + 2 -> "#(${it.c})" + 3, 20 -> "[图片]" + 5 -> "[视频]" + else -> "" + } + } + @OptIn(ExperimentalTextApi::class) val List.renders: List get() { diff --git a/app/src/main/java/com/huanchengfly/tieba/post/models/database/Block.kt b/app/src/main/java/com/huanchengfly/tieba/post/models/database/Block.kt index c46bb2a5..73916ce4 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/models/database/Block.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/models/database/Block.kt @@ -1,13 +1,14 @@ package com.huanchengfly.tieba.post.models.database +import com.huanchengfly.tieba.post.fromJson import org.litepal.crud.LitePalSupport data class Block @JvmOverloads constructor( - var category: Int = 0, - var type: Int = 0, - var keywords: String? = null, - var username: String? = null, - var uid: String? = null, + val category: Int = 0, + val type: Int = 0, + val keywords: String? = null, + val username: String? = null, + val uid: String? = null, ) : LitePalSupport() { val id: Long = 0L companion object { @@ -16,5 +17,9 @@ data class Block @JvmOverloads constructor( const val TYPE_KEYWORD = 0 const val TYPE_USER = 1 + + fun Block.getKeywords(): List { + return keywords?.fromJson() ?: emptyList() + } } } \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/settings/block/BlockSettingsPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/settings/block/BlockSettingsPage.kt index 890f4106..bb714468 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/settings/block/BlockSettingsPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/settings/block/BlockSettingsPage.kt @@ -27,6 +27,7 @@ import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material.icons.outlined.Block import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -77,10 +78,15 @@ fun BlockSettingsPage( var addBlockCategory by remember { mutableStateOf(Block.CATEGORY_BLACK_LIST) } val dialogState = rememberDialogState() PromptDialog( - dialogState = dialogState, onConfirm = { - viewModel.send(BlockSettingsUiIntent.Add(category = addBlockCategory, keywords = it.split(" "))) + viewModel.send( + BlockSettingsUiIntent.Add( + category = addBlockCategory, + keywords = it.split(" ") + ) + ) }, + dialogState = dialogState, title = { Text( text = if (addBlockCategory == Block.CATEGORY_WHITE_LIST) stringResource(id = R.string.title_add_white) @@ -199,7 +205,11 @@ fun BlockSettingsPage( contentPadding = paddingValues, verticalAlignment = Alignment.Top ) { position -> - val items = if (position == 0) blackList else whiteList + val items by remember { + derivedStateOf { + if (position == 0) blackList else whiteList + } + } StateScreen( isEmpty = items.isEmpty(), isError = false, diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/settings/block/BlockSettingsViewModel.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/settings/block/BlockSettingsViewModel.kt index 5545fccc..d23fa148 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/settings/block/BlockSettingsViewModel.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/settings/block/BlockSettingsViewModel.kt @@ -4,10 +4,9 @@ import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage import com.huanchengfly.tieba.post.arch.* import com.huanchengfly.tieba.post.models.database.Block import com.huanchengfly.tieba.post.toJson +import com.huanchengfly.tieba.post.utils.BlockManager import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.* -import org.litepal.LitePal -import org.litepal.extension.delete class BlockSettingsViewModel : BaseViewModel() { @@ -36,22 +35,27 @@ class BlockSettingsViewModel : ) private fun produceLoadPartialChange(): Flow = - flow { - val blocks = LitePal.findAll(Block::class.java) - val blackList = blocks.filter { it.category == Block.CATEGORY_BLACK_LIST } - val whiteList = blocks.filter { it.category == Block.CATEGORY_WHITE_LIST } - emit(BlockSettingsPartialChange.Load.Success(blackList, whiteList)) - }.onStart { BlockSettingsPartialChange.Load.Start } + flowOf( + BlockSettingsPartialChange.Load.Success( + BlockManager.blackList, + BlockManager.whiteList + ) + ) + .onStart { BlockSettingsPartialChange.Load.Start } .catch { emit(BlockSettingsPartialChange.Load.Failure(it)) } private fun BlockSettingsUiIntent.Add.producePartialChange(): Flow = flow { - emit(BlockSettingsPartialChange.Add.Success(Block(category = category, type = Block.TYPE_KEYWORD, keywords = keywords.toJson()).apply { save() })) + val block = Block( + category = category, type = Block.TYPE_KEYWORD, keywords = keywords.toJson() + ) + BlockManager.addBlock(block) + emit(BlockSettingsPartialChange.Add.Success(block)) }.catch { emit(BlockSettingsPartialChange.Add.Failure(it)) } private fun BlockSettingsUiIntent.Delete.producePartialChange(): Flow = flow { - LitePal.delete(id = id) + BlockManager.removeBlock(id) emit(BlockSettingsPartialChange.Delete.Success(id)) }.catch { emit(BlockSettingsPartialChange.Delete.Failure(it)) } } @@ -91,11 +95,17 @@ sealed interface BlockSettingsPartialChange : PartialChange { val newBlackList = - if (item.category == Block.CATEGORY_BLACK_LIST) oldState.blackList + item - else oldState.blackList + if (item.category == Block.CATEGORY_BLACK_LIST) { + oldState.blackList + item + } else { + oldState.blackList + } val newWhiteList = - if (item.category == Block.CATEGORY_WHITE_LIST) oldState.whiteList + item - else oldState.whiteList + if (item.category == Block.CATEGORY_WHITE_LIST) { + oldState.whiteList + item + } else { + oldState.whiteList + } oldState.copy(blackList = newBlackList, whiteList = newWhiteList) } is Failure -> oldState diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/BlockManager.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/BlockManager.kt new file mode 100644 index 00000000..35b230be --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/BlockManager.kt @@ -0,0 +1,63 @@ +package com.huanchengfly.tieba.post.utils + +import com.huanchengfly.tieba.post.api.abstractText +import com.huanchengfly.tieba.post.api.models.protos.Post +import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo +import com.huanchengfly.tieba.post.api.plainText +import com.huanchengfly.tieba.post.models.database.Block +import com.huanchengfly.tieba.post.models.database.Block.Companion.getKeywords +import org.litepal.LitePal +import org.litepal.extension.delete +import org.litepal.extension.findAllAsync + +object BlockManager { + private val blockList: MutableList = mutableListOf() + + val blackList: List + get() = blockList.filter { it.category == Block.CATEGORY_BLACK_LIST } + + val whiteList: List + get() = blockList.filter { it.category == Block.CATEGORY_WHITE_LIST } + + fun addBlock(block: Block) { + block.save() + blockList.add(block) + } + + fun removeBlock(id: Long) { + LitePal.delete(id) + blockList.removeAll { it.id == id } + } + + fun init() { + LitePal.findAllAsync().listen { blocks -> + blockList.addAll(blocks) + } + } + + fun shouldBlock(content: String): Boolean { + return blackList.any { block -> + block.type == Block.TYPE_KEYWORD + && block.getKeywords().all { content.contains(it) } + } && whiteList.none { block -> + block.type == Block.TYPE_KEYWORD + && block.getKeywords().all { content.contains(it) } + } + } + + fun shouldBlock(userId: Long = 0L, userName: String? = null): Boolean { + return blackList.any { block -> + block.type == Block.TYPE_USER + && (block.uid == userId.toString() || block.username == userName) + } && whiteList.none { block -> + block.type == Block.TYPE_USER + && (block.uid == userId.toString() || block.username == userName) + } + } + + fun ThreadInfo.shouldBlock(): Boolean = + shouldBlock(abstractText) || shouldBlock(authorId, author?.name) + + fun Post.shouldBlock(): Boolean = + shouldBlock(content.plainText) || shouldBlock(author_id, author?.name) +} \ No newline at end of file