更加智能的阅读记录逻辑
This commit is contained in:
@@ -92,6 +92,19 @@ interface ReadRecordDao {
|
||||
@Query("DELETE FROM readRecordDetail WHERE bookName = :bookName")
|
||||
fun deleteDetailByName(bookName: String)
|
||||
|
||||
/**
|
||||
* 获取指定书籍的最后一条阅读会话
|
||||
* 用于判断是否可以合并
|
||||
*/
|
||||
@Query("SELECT * FROM readRecordSession WHERE bookName = :bookName ORDER BY endTime DESC LIMIT 1")
|
||||
suspend fun getLatestSessionByBook(bookName: String): ReadRecordSession?
|
||||
|
||||
/**
|
||||
* 更新现有的会话
|
||||
*/
|
||||
@Update
|
||||
suspend fun updateSession(session: ReadRecordSession)
|
||||
|
||||
/**
|
||||
* 插入阅读会话记录。
|
||||
*/
|
||||
|
||||
@@ -15,10 +15,64 @@ class ReadRecordRepository(
|
||||
private val dao: ReadRecordDao
|
||||
) {
|
||||
|
||||
companion object {
|
||||
// 2分钟内重新打开书本,视为同一次阅读
|
||||
private const val CONTINUE_THRESHOLD = 120 * 1000L
|
||||
// 会话总时长小于10秒则不记录
|
||||
private const val MIN_READ_DURATION = 0 * 1000L
|
||||
}
|
||||
|
||||
private fun getCurrentDeviceId(): String = ""
|
||||
|
||||
/**
|
||||
* 保存一个完整的阅读会话,并同步更新 ReadRecordDetail 和 ReadRecord。
|
||||
* 智能保存阅读会话。
|
||||
* 自动判断是插入新会话还是合并到上一次会话
|
||||
*/
|
||||
@Transaction
|
||||
suspend fun saveOrMergeReadSession(newSession: ReadRecordSession) {
|
||||
|
||||
val segmentDuration = newSession.endTime - newSession.startTime
|
||||
|
||||
if (segmentDuration < MIN_READ_DURATION) {
|
||||
return
|
||||
}
|
||||
|
||||
val latestSession = dao.getLatestSessionByBook(newSession.bookName)
|
||||
|
||||
if (latestSession != null) {
|
||||
val timeGap = newSession.startTime - latestSession.endTime
|
||||
|
||||
if (timeGap <= CONTINUE_THRESHOLD && timeGap >= 0) {
|
||||
|
||||
val durationDelta = segmentDuration
|
||||
val wordsDelta = newSession.words
|
||||
|
||||
val mergedSession = latestSession.copy(
|
||||
endTime = newSession.endTime,
|
||||
words = latestSession.words + wordsDelta
|
||||
)
|
||||
|
||||
dao.updateSession(mergedSession)
|
||||
|
||||
val dateString = DateUtil.format(Date(mergedSession.startTime), DatePattern.NORM_DATE_PATTERN)
|
||||
updateReadRecordDetail(mergedSession, durationDelta, wordsDelta, dateString)
|
||||
updateReadRecord(mergedSession, durationDelta)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 处理新会话
|
||||
dao.insertSession(newSession)
|
||||
|
||||
val dateString = DateUtil.format(Date(newSession.startTime), DatePattern.NORM_DATE_PATTERN)
|
||||
updateReadRecordDetail(newSession, segmentDuration, newSession.words, dateString)
|
||||
updateReadRecord(newSession, segmentDuration)
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存一个完整的阅读会话
|
||||
* 没几把用
|
||||
*/
|
||||
@Transaction
|
||||
suspend fun saveReadSession(session: ReadRecordSession) {
|
||||
@@ -26,17 +80,23 @@ class ReadRecordRepository(
|
||||
|
||||
val sessionDuration = session.endTime - session.startTime
|
||||
val dateString = DateUtil.format(Date(session.startTime), DatePattern.NORM_DATE_PATTERN)
|
||||
updateReadRecordDetail(session, sessionDuration, session.words, dateString)
|
||||
|
||||
updateReadRecordDetail(session, sessionDuration, dateString)
|
||||
updateReadRecord(session, sessionDuration)
|
||||
}
|
||||
|
||||
private suspend fun updateReadRecord(session: ReadRecordSession, sessionDuration: Long) {
|
||||
/**
|
||||
* 更新总记录表 (ReadRecord)
|
||||
* @param durationDelta 增加的时长
|
||||
*/
|
||||
private suspend fun updateReadRecord(session: ReadRecordSession, durationDelta: Long) {
|
||||
if (durationDelta <= 0) return
|
||||
|
||||
val existingRecord = dao.getReadRecord(session.deviceId, session.bookName)
|
||||
|
||||
if (existingRecord != null) {
|
||||
val updatedRecord = existingRecord.copy(
|
||||
readTime = existingRecord.readTime + sessionDuration,
|
||||
readTime = existingRecord.readTime + durationDelta,
|
||||
lastRead = session.endTime
|
||||
)
|
||||
dao.update(updatedRecord)
|
||||
@@ -44,19 +104,31 @@ class ReadRecordRepository(
|
||||
val newRecord = ReadRecord(
|
||||
deviceId = session.deviceId,
|
||||
bookName = session.bookName,
|
||||
readTime = sessionDuration,
|
||||
readTime = durationDelta,
|
||||
lastRead = session.endTime
|
||||
)
|
||||
dao.insert(newRecord)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateReadRecordDetail(session: ReadRecordSession, sessionDuration: Long, dateString: String) {
|
||||
/**
|
||||
* 更新每日详情表 (ReadRecordDetail)
|
||||
* @param durationDelta 增加的时长
|
||||
* @param wordsDelta 增加的字数
|
||||
*/
|
||||
private suspend fun updateReadRecordDetail(
|
||||
session: ReadRecordSession,
|
||||
durationDelta: Long,
|
||||
wordsDelta: Long,
|
||||
dateString: String
|
||||
) {
|
||||
if (durationDelta <= 0 && wordsDelta <= 0) return
|
||||
|
||||
val existingDetail = dao.getDetail(session.deviceId, session.bookName, dateString)
|
||||
|
||||
if (existingDetail != null) {
|
||||
existingDetail.readTime += sessionDuration
|
||||
existingDetail.readWords += session.words
|
||||
existingDetail.readTime += durationDelta
|
||||
existingDetail.readWords += wordsDelta
|
||||
existingDetail.firstReadTime = min(existingDetail.firstReadTime, session.startTime)
|
||||
existingDetail.lastReadTime = max(existingDetail.lastReadTime, session.endTime)
|
||||
dao.insertDetail(existingDetail)
|
||||
@@ -65,8 +137,8 @@ class ReadRecordRepository(
|
||||
deviceId = session.deviceId,
|
||||
bookName = session.bookName,
|
||||
date = dateString,
|
||||
readTime = sessionDuration,
|
||||
readWords = session.words,
|
||||
readTime = durationDelta,
|
||||
readWords = wordsDelta,
|
||||
firstReadTime = session.startTime,
|
||||
lastReadTime = session.endTime
|
||||
)
|
||||
@@ -115,5 +187,4 @@ class ReadRecordRepository(
|
||||
// 暴露总时长
|
||||
val allTime: Long
|
||||
get() = dao.allTime
|
||||
|
||||
}
|
||||
@@ -47,6 +47,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
@@ -99,8 +100,20 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
var contentProcessor: ContentProcessor? = null
|
||||
val downloadScope = CoroutineScope(SupervisorJob() + IO)
|
||||
val preDownloadSemaphore = Semaphore(2)
|
||||
|
||||
val executor = globalExecutor
|
||||
|
||||
private val ioScope = CoroutineScope(IO)
|
||||
|
||||
private var autoSaveJob: Job? = null
|
||||
|
||||
private var currentActiveSession: ReadRecordSession? = null
|
||||
//占位
|
||||
private var currentReadLength: Long = 10L
|
||||
private const val AUTO_SAVE_INTERVAL = 120 * 1000L
|
||||
|
||||
private const val MIN_READ_DURATION = 10 * 1000L
|
||||
|
||||
fun resetData(book: Book) {
|
||||
ReadBook.book = book
|
||||
readRecord.bookName = book.name
|
||||
@@ -286,91 +299,84 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// 阅读暂停的阈值
|
||||
private const val PAUSE_THRESHOLD = 5 * 60 * 1000L
|
||||
// 存储当前正在累积的阅读会话对象
|
||||
private var currentActiveSession: ReadRecordSession? = null
|
||||
//占位
|
||||
private var currentReadLength: Long = 10L
|
||||
|
||||
fun upReadTime() {
|
||||
val currentLength = currentReadLength
|
||||
if (!AppConfig.enableReadRecord) {
|
||||
return
|
||||
}
|
||||
fun initReadTime() {
|
||||
val currentBookName = book?.name ?: return
|
||||
val endTime = System.currentTimeMillis()
|
||||
|
||||
//计算本次片段的时长和字数
|
||||
val duration = endTime - readStartTime
|
||||
var wordChange = currentLength - lastReadLength
|
||||
if (wordChange < 0) wordChange = 0
|
||||
|
||||
//过滤无效记录
|
||||
if (duration < 1000L && wordChange == 0L) {
|
||||
readStartTime = endTime
|
||||
lastReadLength = currentLength
|
||||
return
|
||||
if (currentActiveSession != null && currentActiveSession!!.bookName != currentBookName) {
|
||||
commitReadSession()
|
||||
}
|
||||
|
||||
//检查是否中断
|
||||
if (currentActiveSession != null) {
|
||||
// 计算从上一个片段结束到当前片段开始的时间间隔
|
||||
val timeSinceLastUpdate = readStartTime - currentActiveSession!!.endTime
|
||||
|
||||
if (timeSinceLastUpdate > PAUSE_THRESHOLD) {
|
||||
// 如果间隔时间过长,说明用户暂停阅读后又回来了,提交旧会话
|
||||
commitReadSession()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//累加或创建新会话
|
||||
if (currentActiveSession == null) {
|
||||
// 创建新的会话对象,使用 readStartTime 作为整个会话的起始时间
|
||||
lastReadLength = currentReadLength
|
||||
currentActiveSession = ReadRecordSession(
|
||||
deviceId = "",
|
||||
bookName = currentBookName,
|
||||
startTime = readStartTime,
|
||||
endTime = endTime,
|
||||
endTime = readStartTime,
|
||||
words = 0
|
||||
)
|
||||
} else {
|
||||
// 累加数据:只更新结束时间和总字数
|
||||
currentActiveSession = currentActiveSession!!.copy(
|
||||
endTime = endTime,
|
||||
words = currentActiveSession!!.words + wordChange
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun upReadTime() {
|
||||
val currentLength = currentReadLength
|
||||
val currentBookName = book?.name ?: return
|
||||
val endTime = System.currentTimeMillis()
|
||||
|
||||
if (currentActiveSession == null || currentActiveSession!!.bookName != currentBookName) {
|
||||
initReadTime()
|
||||
return
|
||||
}
|
||||
|
||||
var wordChange = currentLength - lastReadLength
|
||||
if (wordChange < 0) wordChange = 0
|
||||
|
||||
currentActiveSession = currentActiveSession!!.copy(
|
||||
endTime = endTime,
|
||||
words = currentActiveSession!!.words + wordChange
|
||||
)
|
||||
|
||||
readStartTime = endTime
|
||||
lastReadLength = currentLength
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前累积的阅读会话(ReadRecordSession)写入数据库,并重置状态。
|
||||
* 此方法必须在后台线程上调用。
|
||||
*/
|
||||
fun commitReadSession() {
|
||||
// 检查是否有需要保存的数据
|
||||
val sessionToSave = currentActiveSession ?: return
|
||||
fun startAutoSaveSession() {
|
||||
autoSaveJob?.cancel()
|
||||
autoSaveJob = ioScope.launch {
|
||||
while (isActive) {
|
||||
delay(AUTO_SAVE_INTERVAL)
|
||||
commitSessionInternal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保时长和字数有效
|
||||
if (sessionToSave.words <= 0 && (sessionToSave.endTime - sessionToSave.startTime) < 10L) {
|
||||
fun stopAutoSaveSession() {
|
||||
autoSaveJob?.cancel()
|
||||
autoSaveJob = null
|
||||
}
|
||||
|
||||
fun commitReadSession() {
|
||||
ioScope.launch {
|
||||
commitSessionInternal()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部提交逻辑
|
||||
*/
|
||||
private suspend fun commitSessionInternal() {
|
||||
val sessionToSave = currentActiveSession ?: return
|
||||
val sessionDuration = sessionToSave.endTime - sessionToSave.startTime
|
||||
if (sessionDuration < MIN_READ_DURATION) {
|
||||
currentActiveSession = null
|
||||
return
|
||||
}
|
||||
|
||||
//使用 runBlocking 确保数据库保存操作在当前线程上同步完成
|
||||
try {
|
||||
kotlinx.coroutines.runBlocking {
|
||||
readRecordRepository.saveReadSession(sessionToSave)
|
||||
}
|
||||
readRecordRepository.saveOrMergeReadSession(sessionToSave)
|
||||
} catch (e: Exception) {
|
||||
AppLog.put("保存阅读会话出错: ${sessionToSave.bookName}", e)
|
||||
} finally {
|
||||
currentActiveSession = null
|
||||
}
|
||||
|
||||
currentActiveSession = null
|
||||
}
|
||||
|
||||
fun upMsg(msg: String?) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import org.koin.core.component.KoinComponent
|
||||
@@ -75,6 +76,16 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
||||
|
||||
private val readRecordRepository: ReadRecordRepository by inject()
|
||||
|
||||
private val ioScope = CoroutineScope(IO)
|
||||
|
||||
private var autoSaveJob: Job? = null
|
||||
|
||||
private var currentActiveSession: ReadRecordSession? = null
|
||||
private var currentReadLength: Long = 10L
|
||||
private const val AUTO_SAVE_INTERVAL = 120 * 1000L
|
||||
|
||||
private const val MIN_READ_DURATION = 10 * 1000L
|
||||
|
||||
private var lastReadLength: Long = 0
|
||||
fun resetData(book: Book) {
|
||||
ReadManga.book = book
|
||||
@@ -134,92 +145,84 @@ object ReadManga : CoroutineScope by MainScope() , KoinComponent{
|
||||
nextMangaChapter = null
|
||||
}
|
||||
|
||||
//每次切换章节更新阅读记录
|
||||
// 阅读暂停的阈值
|
||||
private const val PAUSE_THRESHOLD = 5 * 60 * 1000L
|
||||
// 存储当前正在累积的阅读会话对象
|
||||
private var currentActiveSession: ReadRecordSession? = null
|
||||
//占位
|
||||
private var currentReadLength: Long = 10L
|
||||
|
||||
fun upReadTime() {
|
||||
val currentLength = currentReadLength
|
||||
if (!AppConfig.enableReadRecord) {
|
||||
return
|
||||
}
|
||||
fun initReadTime() {
|
||||
val currentBookName = book?.name ?: return
|
||||
val endTime = System.currentTimeMillis()
|
||||
|
||||
//计算本次片段的时长和字数
|
||||
val duration = endTime - readStartTime
|
||||
var wordChange = currentLength - lastReadLength
|
||||
if (wordChange < 0) wordChange = 0
|
||||
|
||||
//过滤无效记录
|
||||
if (duration < 1000L && wordChange == 0L) {
|
||||
readStartTime = endTime
|
||||
lastReadLength = currentLength
|
||||
return
|
||||
if (currentActiveSession != null && currentActiveSession!!.bookName != currentBookName) {
|
||||
commitReadSession()
|
||||
}
|
||||
|
||||
//检查是否中断
|
||||
if (currentActiveSession != null) {
|
||||
// 计算从上一个片段结束到当前片段开始的时间间隔
|
||||
val timeSinceLastUpdate = readStartTime - currentActiveSession!!.endTime
|
||||
|
||||
if (timeSinceLastUpdate > PAUSE_THRESHOLD) {
|
||||
// 如果间隔时间过长,说明用户暂停阅读后又回来了,提交旧会话
|
||||
commitReadSession()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//累加或创建新会话
|
||||
if (currentActiveSession == null) {
|
||||
// 创建新的会话对象,使用 readStartTime 作为整个会话的起始时间
|
||||
lastReadLength = currentReadLength
|
||||
currentActiveSession = ReadRecordSession(
|
||||
deviceId = "",
|
||||
bookName = currentBookName,
|
||||
startTime = readStartTime,
|
||||
endTime = endTime,
|
||||
endTime = readStartTime,
|
||||
words = 0
|
||||
)
|
||||
} else {
|
||||
// 累加数据:只更新结束时间和总字数
|
||||
currentActiveSession = currentActiveSession!!.copy(
|
||||
endTime = endTime,
|
||||
words = currentActiveSession!!.words + wordChange
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun upReadTime() {
|
||||
val currentLength = currentReadLength
|
||||
val currentBookName = book?.name ?: return
|
||||
val endTime = System.currentTimeMillis()
|
||||
|
||||
if (currentActiveSession == null || currentActiveSession!!.bookName != currentBookName) {
|
||||
initReadTime()
|
||||
return
|
||||
}
|
||||
|
||||
var wordChange = currentLength - lastReadLength
|
||||
if (wordChange < 0) wordChange = 0
|
||||
|
||||
currentActiveSession = currentActiveSession!!.copy(
|
||||
endTime = endTime,
|
||||
words = currentActiveSession!!.words + wordChange
|
||||
)
|
||||
|
||||
readStartTime = endTime
|
||||
lastReadLength = currentLength
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前累积的阅读会话(ReadRecordSession)写入数据库,并重置状态。
|
||||
* 此方法必须在后台线程上调用。
|
||||
*/
|
||||
fun commitReadSession() {
|
||||
// 检查是否有需要保存的数据
|
||||
val sessionToSave = currentActiveSession ?: return
|
||||
fun startAutoSaveSession() {
|
||||
autoSaveJob?.cancel()
|
||||
autoSaveJob = ioScope.launch {
|
||||
while (isActive) {
|
||||
delay(AUTO_SAVE_INTERVAL)
|
||||
commitSessionInternal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保时长和字数有效
|
||||
if (sessionToSave.words <= 0 && (sessionToSave.endTime - sessionToSave.startTime) < 10L) {
|
||||
fun stopAutoSaveSession() {
|
||||
autoSaveJob?.cancel()
|
||||
autoSaveJob = null
|
||||
}
|
||||
|
||||
fun commitReadSession() {
|
||||
ioScope.launch {
|
||||
commitSessionInternal()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部提交逻辑
|
||||
*/
|
||||
private suspend fun commitSessionInternal() {
|
||||
val sessionToSave = currentActiveSession ?: return
|
||||
val sessionDuration = sessionToSave.endTime - sessionToSave.startTime
|
||||
if (sessionDuration < MIN_READ_DURATION) {
|
||||
currentActiveSession = null
|
||||
return
|
||||
}
|
||||
|
||||
//使用 runBlocking 确保数据库保存操作在当前线程上同步完成
|
||||
try {
|
||||
kotlinx.coroutines.runBlocking {
|
||||
readRecordRepository.saveReadSession(sessionToSave)
|
||||
}
|
||||
readRecordRepository.saveOrMergeReadSession(sessionToSave)
|
||||
} catch (e: Exception) {
|
||||
AppLog.put("保存阅读会话出错: ${sessionToSave.bookName}", e)
|
||||
} finally {
|
||||
currentActiveSession = null
|
||||
}
|
||||
|
||||
currentActiveSession = null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
||||
@@ -429,6 +429,8 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
if (enableAutoScroll) {
|
||||
mScrollTimer.isEnabled = true
|
||||
}
|
||||
ReadManga.initReadTime()
|
||||
ReadManga.startAutoSaveSession()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@@ -444,6 +446,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
Backup.autoBack(this)
|
||||
}
|
||||
}
|
||||
ReadManga.stopAutoSaveSession()
|
||||
ReadManga.commitReadSession()
|
||||
ReadManga.cancelPreDownloadTask()
|
||||
networkChangedListener.unRegister()
|
||||
|
||||
@@ -392,6 +392,8 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
ReadBook.readStartTime = System.currentTimeMillis()
|
||||
ReadBook.initReadTime()
|
||||
ReadBook.startAutoSaveSession()
|
||||
if (bookChanged) {
|
||||
bookChanged = false
|
||||
ReadBook.callBack = this
|
||||
@@ -423,6 +425,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
autoPageStop()
|
||||
backupJob?.cancel()
|
||||
ReadBook.saveRead()
|
||||
ReadBook.stopAutoSaveSession()
|
||||
ReadBook.commitReadSession()
|
||||
ReadBook.cancelPreDownloadTask()
|
||||
unregisterReceiver(timeBatteryReceiver)
|
||||
|
||||
@@ -325,7 +325,7 @@ fun TimelineSessionItem(
|
||||
val timelineX = 24.dp
|
||||
val contentPaddingStart = 32.dp
|
||||
|
||||
val lineColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
val lineColor = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||
val nodeColor = MaterialTheme.colorScheme.primary
|
||||
|
||||
Box(
|
||||
@@ -364,7 +364,7 @@ fun TimelineSessionItem(
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = startTimeText,
|
||||
text = endTimeText,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
@@ -385,7 +385,8 @@ fun TimelineSessionItem(
|
||||
|
||||
Text(
|
||||
"时长: ${formatDuring(duration)}",
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.Gray
|
||||
)
|
||||
// Text(
|
||||
// "字数: ${session.words}",
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.legado.app.ui.widget.compose
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun EmptyMessageView(
|
||||
message: String,
|
||||
modifier: Modifier = Modifier,
|
||||
faces: List<String> = listOf(
|
||||
"(;′⌒`)", "(つ﹏⊂)", "(•̀ᴗ•́)و", "(๑•́ ₃ •̀๑)",
|
||||
"(눈‸눈)", "(ಥ﹏ಥ)", "(。•́︿•̀。)"
|
||||
),
|
||||
faceTextSize: TextUnit = 32.sp,
|
||||
onFaceClick: (() -> Unit)? = null
|
||||
) {
|
||||
var currentFace by remember { mutableStateOf(faces.random()) }
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.wrapContentSize()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
AnimatedTextLine(
|
||||
text = currentFace,
|
||||
fontSize = faceTextSize,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
currentFace = faces.random()
|
||||
onFaceClick?.invoke()
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
AnimatedTextLine(
|
||||
text = message,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
softWrap = true,
|
||||
modifier = Modifier.widthIn(max = 240.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmptyMessageView(
|
||||
@StringRes messageResId: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
faces: List<String> = listOf(
|
||||
"(;′⌒`)", "(つ﹏⊂)", "(•̀ᴗ•́)و", "(๑•́ ₃ •̀๑)",
|
||||
"(눈‸눈)", "(ಥ﹏ಥ)", "(。•́︿•̀。)"
|
||||
),
|
||||
faceTextSize: TextUnit = 32.sp,
|
||||
onFaceClick: (() -> Unit)? = null
|
||||
) {
|
||||
val message = stringResource(id = messageResId)
|
||||
EmptyMessageView(
|
||||
message = message,
|
||||
modifier = modifier,
|
||||
faces = faces,
|
||||
faceTextSize = faceTextSize,
|
||||
onFaceClick = onFaceClick
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user