pref: 屏蔽管理
This commit is contained in:
parent
ce39a5fdf2
commit
972d6b2ee6
|
|
@ -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
|
||||||
import com.huanchengfly.tieba.post.utils.AppIconUtil.disableComponent
|
import com.huanchengfly.tieba.post.utils.AppIconUtil.disableComponent
|
||||||
import com.huanchengfly.tieba.post.utils.AppIconUtil.enableComponent
|
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.ClientUtils
|
||||||
import com.huanchengfly.tieba.post.utils.EmoticonManager
|
import com.huanchengfly.tieba.post.utils.EmoticonManager
|
||||||
import com.huanchengfly.tieba.post.utils.Icons
|
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.UpdateAction
|
||||||
import com.microsoft.appcenter.distribute.UpdateTrack
|
import com.microsoft.appcenter.distribute.UpdateTrack
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.litepal.LitePal
|
import org.litepal.LitePal
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
|
|
@ -141,13 +140,10 @@ class App : Application(), IApp, SketchFactory {
|
||||||
registerActivityLifecycleCallbacks(ClipBoardLinkDetector)
|
registerActivityLifecycleCallbacks(ClipBoardLinkDetector)
|
||||||
registerActivityLifecycleCallbacks(OAIDGetter)
|
registerActivityLifecycleCallbacks(OAIDGetter)
|
||||||
PluginManager.init(this)
|
PluginManager.init(this)
|
||||||
CoroutineScope(Dispatchers.IO).apply {
|
thread {
|
||||||
launch {
|
BlockManager.init()
|
||||||
EmoticonManager.init(this@App)
|
EmoticonManager.init(this@App)
|
||||||
}
|
ClientUtils.init(this@App)
|
||||||
launch {
|
|
||||||
ClientUtils.init(this@App)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.arch.wrapImmutable
|
||||||
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
import com.huanchengfly.tieba.post.ui.common.PbContentRender
|
||||||
import com.huanchengfly.tieba.post.ui.common.PicContentRender
|
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.common.theme.utils.ThemeUtils
|
||||||
import com.huanchengfly.tieba.post.ui.utils.getPhotoViewData
|
import com.huanchengfly.tieba.post.ui.utils.getPhotoViewData
|
||||||
import com.huanchengfly.tieba.post.utils.EmoticonManager
|
import com.huanchengfly.tieba.post.utils.EmoticonManager
|
||||||
import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString
|
import com.huanchengfly.tieba.post.utils.EmoticonUtil.emoticonString
|
||||||
import com.huanchengfly.tieba.post.utils.ImageUtil
|
import com.huanchengfly.tieba.post.utils.ImageUtil
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
|
||||||
private val defaultUserAgent: String =
|
private val defaultUserAgent: String =
|
||||||
|
|
@ -101,6 +104,17 @@ private val PbContent.picUrl: String
|
||||||
src
|
src
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val List<PbContent>.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)
|
@OptIn(ExperimentalTextApi::class)
|
||||||
val List<PbContent>.renders: List<PbContentRender>
|
val List<PbContent>.renders: List<PbContentRender>
|
||||||
get() {
|
get() {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
package com.huanchengfly.tieba.post.models.database
|
package com.huanchengfly.tieba.post.models.database
|
||||||
|
|
||||||
|
import com.huanchengfly.tieba.post.fromJson
|
||||||
import org.litepal.crud.LitePalSupport
|
import org.litepal.crud.LitePalSupport
|
||||||
|
|
||||||
data class Block @JvmOverloads constructor(
|
data class Block @JvmOverloads constructor(
|
||||||
var category: Int = 0,
|
val category: Int = 0,
|
||||||
var type: Int = 0,
|
val type: Int = 0,
|
||||||
var keywords: String? = null,
|
val keywords: String? = null,
|
||||||
var username: String? = null,
|
val username: String? = null,
|
||||||
var uid: String? = null,
|
val uid: String? = null,
|
||||||
) : LitePalSupport() {
|
) : LitePalSupport() {
|
||||||
val id: Long = 0L
|
val id: Long = 0L
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -16,5 +17,9 @@ data class Block @JvmOverloads constructor(
|
||||||
|
|
||||||
const val TYPE_KEYWORD = 0
|
const val TYPE_KEYWORD = 0
|
||||||
const val TYPE_USER = 1
|
const val TYPE_USER = 1
|
||||||
|
|
||||||
|
fun Block.getKeywords(): List<String> {
|
||||||
|
return keywords?.fromJson() ?: emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +27,7 @@ import androidx.compose.material.icons.outlined.AccountCircle
|
||||||
import androidx.compose.material.icons.outlined.Block
|
import androidx.compose.material.icons.outlined.Block
|
||||||
import androidx.compose.material.icons.outlined.CheckCircle
|
import androidx.compose.material.icons.outlined.CheckCircle
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
|
@ -77,10 +78,15 @@ fun BlockSettingsPage(
|
||||||
var addBlockCategory by remember { mutableStateOf(Block.CATEGORY_BLACK_LIST) }
|
var addBlockCategory by remember { mutableStateOf(Block.CATEGORY_BLACK_LIST) }
|
||||||
val dialogState = rememberDialogState()
|
val dialogState = rememberDialogState()
|
||||||
PromptDialog(
|
PromptDialog(
|
||||||
dialogState = dialogState,
|
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
viewModel.send(BlockSettingsUiIntent.Add(category = addBlockCategory, keywords = it.split(" ")))
|
viewModel.send(
|
||||||
|
BlockSettingsUiIntent.Add(
|
||||||
|
category = addBlockCategory,
|
||||||
|
keywords = it.split(" ")
|
||||||
|
)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
dialogState = dialogState,
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = if (addBlockCategory == Block.CATEGORY_WHITE_LIST) stringResource(id = R.string.title_add_white)
|
text = if (addBlockCategory == Block.CATEGORY_WHITE_LIST) stringResource(id = R.string.title_add_white)
|
||||||
|
|
@ -199,7 +205,11 @@ fun BlockSettingsPage(
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
verticalAlignment = Alignment.Top
|
verticalAlignment = Alignment.Top
|
||||||
) { position ->
|
) { position ->
|
||||||
val items = if (position == 0) blackList else whiteList
|
val items by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
if (position == 0) blackList else whiteList
|
||||||
|
}
|
||||||
|
}
|
||||||
StateScreen(
|
StateScreen(
|
||||||
isEmpty = items.isEmpty(),
|
isEmpty = items.isEmpty(),
|
||||||
isError = false,
|
isError = false,
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,9 @@ import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
||||||
import com.huanchengfly.tieba.post.arch.*
|
import com.huanchengfly.tieba.post.arch.*
|
||||||
import com.huanchengfly.tieba.post.models.database.Block
|
import com.huanchengfly.tieba.post.models.database.Block
|
||||||
import com.huanchengfly.tieba.post.toJson
|
import com.huanchengfly.tieba.post.toJson
|
||||||
|
import com.huanchengfly.tieba.post.utils.BlockManager
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.litepal.LitePal
|
|
||||||
import org.litepal.extension.delete
|
|
||||||
|
|
||||||
class BlockSettingsViewModel :
|
class BlockSettingsViewModel :
|
||||||
BaseViewModel<BlockSettingsUiIntent, BlockSettingsPartialChange, BlockSettingsUiState, BlockSettingsUiEvent>() {
|
BaseViewModel<BlockSettingsUiIntent, BlockSettingsPartialChange, BlockSettingsUiState, BlockSettingsUiEvent>() {
|
||||||
|
|
@ -36,22 +35,27 @@ class BlockSettingsViewModel :
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun produceLoadPartialChange(): Flow<BlockSettingsPartialChange.Load> =
|
private fun produceLoadPartialChange(): Flow<BlockSettingsPartialChange.Load> =
|
||||||
flow<BlockSettingsPartialChange.Load> {
|
flowOf<BlockSettingsPartialChange.Load>(
|
||||||
val blocks = LitePal.findAll(Block::class.java)
|
BlockSettingsPartialChange.Load.Success(
|
||||||
val blackList = blocks.filter { it.category == Block.CATEGORY_BLACK_LIST }
|
BlockManager.blackList,
|
||||||
val whiteList = blocks.filter { it.category == Block.CATEGORY_WHITE_LIST }
|
BlockManager.whiteList
|
||||||
emit(BlockSettingsPartialChange.Load.Success(blackList, whiteList))
|
)
|
||||||
}.onStart { BlockSettingsPartialChange.Load.Start }
|
)
|
||||||
|
.onStart { BlockSettingsPartialChange.Load.Start }
|
||||||
.catch { emit(BlockSettingsPartialChange.Load.Failure(it)) }
|
.catch { emit(BlockSettingsPartialChange.Load.Failure(it)) }
|
||||||
|
|
||||||
private fun BlockSettingsUiIntent.Add.producePartialChange(): Flow<BlockSettingsPartialChange.Add> =
|
private fun BlockSettingsUiIntent.Add.producePartialChange(): Flow<BlockSettingsPartialChange.Add> =
|
||||||
flow<BlockSettingsPartialChange.Add> {
|
flow<BlockSettingsPartialChange.Add> {
|
||||||
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)) }
|
}.catch { emit(BlockSettingsPartialChange.Add.Failure(it)) }
|
||||||
|
|
||||||
private fun BlockSettingsUiIntent.Delete.producePartialChange(): Flow<BlockSettingsPartialChange.Delete> =
|
private fun BlockSettingsUiIntent.Delete.producePartialChange(): Flow<BlockSettingsPartialChange.Delete> =
|
||||||
flow<BlockSettingsPartialChange.Delete> {
|
flow<BlockSettingsPartialChange.Delete> {
|
||||||
LitePal.delete<Block>(id = id)
|
BlockManager.removeBlock(id)
|
||||||
emit(BlockSettingsPartialChange.Delete.Success(id))
|
emit(BlockSettingsPartialChange.Delete.Success(id))
|
||||||
}.catch { emit(BlockSettingsPartialChange.Delete.Failure(it)) }
|
}.catch { emit(BlockSettingsPartialChange.Delete.Failure(it)) }
|
||||||
}
|
}
|
||||||
|
|
@ -91,11 +95,17 @@ sealed interface BlockSettingsPartialChange : PartialChange<BlockSettingsUiState
|
||||||
when (this) {
|
when (this) {
|
||||||
is Success -> {
|
is Success -> {
|
||||||
val newBlackList =
|
val newBlackList =
|
||||||
if (item.category == Block.CATEGORY_BLACK_LIST) oldState.blackList + item
|
if (item.category == Block.CATEGORY_BLACK_LIST) {
|
||||||
else oldState.blackList
|
oldState.blackList + item
|
||||||
|
} else {
|
||||||
|
oldState.blackList
|
||||||
|
}
|
||||||
val newWhiteList =
|
val newWhiteList =
|
||||||
if (item.category == Block.CATEGORY_WHITE_LIST) oldState.whiteList + item
|
if (item.category == Block.CATEGORY_WHITE_LIST) {
|
||||||
else oldState.whiteList
|
oldState.whiteList + item
|
||||||
|
} else {
|
||||||
|
oldState.whiteList
|
||||||
|
}
|
||||||
oldState.copy(blackList = newBlackList, whiteList = newWhiteList)
|
oldState.copy(blackList = newBlackList, whiteList = newWhiteList)
|
||||||
}
|
}
|
||||||
is Failure -> oldState
|
is Failure -> oldState
|
||||||
|
|
|
||||||
|
|
@ -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<Block> = mutableListOf()
|
||||||
|
|
||||||
|
val blackList: List<Block>
|
||||||
|
get() = blockList.filter { it.category == Block.CATEGORY_BLACK_LIST }
|
||||||
|
|
||||||
|
val whiteList: List<Block>
|
||||||
|
get() = blockList.filter { it.category == Block.CATEGORY_WHITE_LIST }
|
||||||
|
|
||||||
|
fun addBlock(block: Block) {
|
||||||
|
block.save()
|
||||||
|
blockList.add(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeBlock(id: Long) {
|
||||||
|
LitePal.delete<Block>(id)
|
||||||
|
blockList.removeAll { it.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
LitePal.findAllAsync<Block>().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)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue