Skip to content

Commit

Permalink
Fix several detail issues (#32)
Browse files Browse the repository at this point in the history
* fix: nested scroll pullRefresh

* fix: the poll status was not synchronized for repost's post

* chore: change function name

* feat: add new component (TextWithEmoji), add height limit when ReplyTextField is not expanded

* chore: delete ripple of emoji group picker

* feat: fix searchBar animation

* chore: add emoji size parameter for TextWithEmoji

* chore: add min height for preview card

* fix: inconsistent font sizes

* chore: temporary push

* fix: fix background color of a PNG avatar

* test: update some unit test modules

* chore: remove unnecessary code

* fix: multi-account changing

* feat: add localized strings for mention text

---------

Co-authored-by: lazzzis <[email protected]>
  • Loading branch information
whitescent and lazzzis authored Feb 6, 2024
1 parent a5fcdca commit 27da952
Show file tree
Hide file tree
Showing 37 changed files with 1,043 additions and 533 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.junit.runner.RunWith

// issue:java.lang.IllegalArgumentException: Unable to read any metrics during benchmark
// (metric list: [androidx.benchmark.macro.StartupTimingMetric@849c8a0]
// https://github.com/android/performance-samples/issues/268

@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
Expand All @@ -40,7 +41,7 @@ class StartupBenchmark {
val benchmarkRule = MacrobenchmarkRule()

@Test
fun generalTest() {
fun startBenchmark() {
benchmarkRule.measureRepeated(
packageName = "com.github.whitescent.mastify",
metrics = listOf(StartupTimingMetric()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import logcat.LogPriority

@HiltAndroidApp
class MastifyApp : Application(), ImageLoaderFactory {

override fun newImageLoader(): ImageLoader {
val context = this.applicationContext
return ImageLoader.Builder(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ data class StatusUiData(
val parsedContent: String = Jsoup.parse(content).body().text()
val isInReplyTo inline get() = inReplyToId != null

val isInReplyToSomeone inline get() = mentions.size == 1 && isInReplyTo

enum class ReplyChainType {
Start, Continue, End, Null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ class HomeRepository @Inject constructor(
private val accountDao = db.accountDao()
private val timelineDao = db.timelineDao()

suspend fun refreshTimelineFromApi(oldItems: List<Status>, newItems: List<Status>) {
suspend fun refreshTimelineFromApi(
accountId: Long,
oldItems: List<Status>,
newItems: List<Status>
) {
if (newItems.isEmpty()) return
if (oldItems.size < FETCHNUMBER) saveLatestTimelineToDb(newItems)
if (oldItems.size < FETCH_NUMBER) saveLatestTimelineToDb(newItems, accountId)
else {
val lastStatusOfNewItems = newItems.last()
// If the db contains the last status returned by the API,
Expand All @@ -67,7 +71,7 @@ class HomeRepository @Inject constructor(

// Update the timeline to the database by splicing the latest data
// from the API with the latest data from the database.
saveLatestTimelineToDb(statusListBeforeFetchNumber + statusListAfterFetchNumber)
saveLatestTimelineToDb(statusListBeforeFetchNumber + statusListAfterFetchNumber, accountId)
} else {
// If the last currentStatus returned by the API cannot be found in the db,
// This means that the number of statuses in the user's timeline exceeds
Expand All @@ -84,11 +88,11 @@ class HomeRepository @Inject constructor(
val removeIndex = oldItems.indexOfFirst { it.hasUnloadedStatus }.let {
if (it == -1) 0 else it + 1
}
saveLatestTimelineToDb(newStatusList + oldItems.subList(removeIndex, oldItems.size))
saveLatestTimelineToDb(newStatusList + oldItems.subList(removeIndex, oldItems.size), accountId)
return
}
}
saveLatestTimelineToDb(newStatusList + oldItems)
saveLatestTimelineToDb(newStatusList + oldItems, accountId)
}
}
}
Expand Down Expand Up @@ -131,11 +135,11 @@ class HomeRepository @Inject constructor(
tempList[insertIndex] = tempList[insertIndex].copy(hasUnloadedStatus = false)
tempList.addAll(insertIndex + 1, list)
}
saveLatestTimelineToDb(tempList)
saveLatestTimelineToDb(tempList, accountDao.getActiveAccount()!!.id)
}
}

suspend fun fetchTimeline(maxId: String? = null, limit: Int = FETCHNUMBER): Result<List<Status>> {
suspend fun fetchTimeline(maxId: String? = null, limit: Int = FETCH_NUMBER): Result<List<Status>> {
val response = api.homeTimeline(maxId = maxId, limit = limit)
return if (response.isSuccessful && !response.body().isNullOrEmpty()) {
val body = response.body()!!
Expand All @@ -151,7 +155,7 @@ class HomeRepository @Inject constructor(
}
}

suspend fun storeLastViewedTimelineOffset(index: Int, offset: Int) {
suspend fun saveLastViewedTimelineOffset(index: Int, offset: Int) {
val activeAccount = accountDao.getActiveAccount()!!
db.withTransaction {
accountRepository.updateActiveAccount(
Expand All @@ -160,19 +164,14 @@ class HomeRepository @Inject constructor(
}
}

private suspend fun saveLatestTimelineToDb(statuses: List<Status>) {
db.withTransaction {
val accountId = accountDao.getActiveAccount()!!.id
timelineDao.clearAll(accountId)
timelineDao.insertOrUpdate(statuses.map { it.toEntity(accountId) })
}
}
private suspend fun saveLatestTimelineToDb(statuses: List<Status>, accountId: Long) =
timelineDao.cleanAndReinsert(statuses.map { it.toEntity(accountId) }, accountId)

private suspend fun fetchTimelineFlow(maxId: String?, limit: Int = FETCHNUMBER) = flow {
private suspend fun fetchTimelineFlow(maxId: String?, limit: Int = FETCH_NUMBER) = flow {
emit(api.homeTimeline(maxId, limit = limit).getOrThrow())
}

companion object {
const val FETCHNUMBER = 40
const val FETCH_NUMBER = 40
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ interface AccountDao {
fun getActiveAccountFlow(): Flow<AccountEntity?>

@Query("SELECT * FROM ACCOUNTENTITY WHERE isActive = 1 LIMIT 1")
@Transaction
suspend fun getActiveAccount(): AccountEntity?

@Query("SELECT * FROM ACCOUNTENTITY WHERE accountId = :accountId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy.Companion.REPLACE
import androidx.room.Query
import androidx.room.RewriteQueriesToDropUnusedColumns
import androidx.room.Transaction
import com.github.whitescent.mastify.database.model.TimelineEntity
import com.github.whitescent.mastify.network.model.status.Status
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -71,4 +72,10 @@ interface TimelineDao {

@Query("DELETE FROM timelineentity WHERE timelineUserId = :accountId")
suspend fun clearAll(accountId: Long)

@Transaction
suspend fun cleanAndReinsert(timelineEntity: List<TimelineEntity>, id: Long) {
clearAll(id)
insertOrUpdate(timelineEntity)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2024 WhiteScent
*
* This file is a part of Mastify.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Mastify is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Mastify; if not,
* see <http://www.gnu.org/licenses>.
*/

package com.github.whitescent.mastify.di

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import javax.inject.Qualifier
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
class DispatcherModule {

@DefaultDispatcher
@Provides
fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default

@IoDispatcher
@Provides
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO

@MainDispatcher
@Provides
fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
}

@InstallIn(SingletonComponent::class)
@Module
class CoroutinesScopesModule {
@Singleton
@Provides
@ApplicationScope
fun providesCoroutineScope(
@DefaultDispatcher defaultDispatcher: CoroutineDispatcher
): CoroutineScope = CoroutineScope(SupervisorJob() + defaultDispatcher)
}

@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class ApplicationScope

@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class DefaultDispatcher

@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class IoDispatcher

@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class MainDispatcher
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import com.github.whitescent.mastify.data.model.StatusBackResult
import com.github.whitescent.mastify.data.model.ui.StatusUiData
Expand Down Expand Up @@ -83,19 +84,31 @@ fun List<StatusUiData>.updateStatusActionData(newStatus: StatusBackResult): List
fun List<Status>.updateStatusActionData(newStatus: StatusBackResult): List<Status> {
return if (this.any { it.actionableId == newStatus.id }) {
val result = this.toMutableList()
val index = result.indexOfFirst { it.actionableId == newStatus.id }
if (index != -1) {
result[index] = result[index].copy(
favorited = newStatus.favorited,
favouritesCount = newStatus.favouritesCount,
reblogged = newStatus.reblogged,
reblogsCount = newStatus.reblogsCount,
repliesCount = newStatus.repliesCount,
bookmarked = newStatus.bookmarked,
poll = newStatus.poll
)
result
} else this
forEachIndexed { index, status ->
if (status.actionableId == newStatus.id) {
result[index] = result[index].copy(
favorited = newStatus.favorited,
favouritesCount = newStatus.favouritesCount,
reblogged = newStatus.reblogged,
reblogsCount = newStatus.reblogsCount,
repliesCount = newStatus.repliesCount,
bookmarked = newStatus.bookmarked,
poll = newStatus.poll,
reblog = if (status.reblog != null) {
status.reblog.copy(
favorited = newStatus.favorited,
favouritesCount = newStatus.favouritesCount,
reblogged = newStatus.reblogged,
reblogsCount = newStatus.reblogsCount,
repliesCount = newStatus.repliesCount,
bookmarked = newStatus.bookmarked,
poll = newStatus.poll
)
} else null
)
}
}
result
} else this
}

Expand Down Expand Up @@ -147,12 +160,13 @@ fun List<StatusUiData>.getReplyChainType(index: Int): StatusUiData.ReplyChainTyp
fun String.buildTextWithLimit(
maxLength: Int,
textColor: Color,
warningBackgroundColor: Color
warningBackgroundColor: Color,
fontSize: TextUnit = 18.sp
): AnnotatedString {
val text = this
return buildAnnotatedString {
withStyle(
style = SpanStyle(fontSize = 18.sp, color = textColor)
style = SpanStyle(fontSize = fontSize, color = textColor)
) {
append(
text = text.substring(
Expand All @@ -165,7 +179,8 @@ fun String.buildTextWithLimit(
withStyle(
style = SpanStyle(
color = textColor,
background = warningBackgroundColor
background = warningBackgroundColor,
fontSize = fontSize
)
) {
append(text.substring(startIndex = maxLength, endIndex = text.length))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastMap
import com.github.whitescent.mastify.paging.PageLoadState.Error
import com.github.whitescent.mastify.paging.PageLoadState.NotLoading
import com.github.whitescent.mastify.paging.PageLoadState.Refresh
import com.github.whitescent.mastify.ui.component.StatusAppendingIndicator
import com.github.whitescent.mastify.ui.component.StatusEndIndicator
import com.github.whitescent.mastify.ui.component.status.paging.EmptyStatusListPlaceholder
Expand All @@ -67,6 +70,7 @@ fun <T : Any> LazyPagingList(
list: ImmutableList<T>,
lazyListState: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
pagePlaceholderType: PagePlaceholderType,
reverseLayout: Boolean = false,
enablePullRefresh: Boolean = false,
verticalArrangement: Arrangement.Vertical =
Expand Down Expand Up @@ -97,26 +101,27 @@ fun <T : Any> LazyPagingList(
.fillMaxSize()
.let {
if (enablePullRefresh) it.pullRefresh(pullRefreshState) else it
}
},
) {
when (list.size) {
0 -> {
if (placeholder == null) {
when {
paginatorUiState.loadState is PageLoadState.Error ->
paginatorUiState.loadState is Error ->
StatusListLoadError(paginatorUiState.loadState.throwable.localizedMessage) {
scope.launch(SupervisorJob()) {
paginator.refresh()
}
}
paginatorUiState.loadState is PageLoadState.NotLoading && paginatorUiState.endReached -> {
paginatorUiState.loadState is NotLoading && paginatorUiState.endReached -> {
EmptyStatusListPlaceholder(
pagePlaceholderType = PagePlaceholderType.Home,
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())
pagePlaceholderType = pagePlaceholderType,
modifier = Modifier.fillMaxSize()
.verticalScroll(rememberScrollState())
.pullRefresh(pullRefreshState),
)
}
paginatorUiState.loadState is PageLoadState.Refresh ->
StatusListLoading(Modifier.fillMaxSize())
paginatorUiState.loadState is Refresh -> StatusListLoading(Modifier.fillMaxSize())
}
} else placeholder()
}
Expand All @@ -142,7 +147,7 @@ fun <T : Any> LazyPagingList(
item {
when (paginatorUiState.loadState) {
is PageLoadState.Append -> StatusAppendingIndicator()
is PageLoadState.Error -> {
is Error -> {
// TODO Localization
Toast.makeText(
context,
Expand All @@ -156,7 +161,6 @@ fun <T : Any> LazyPagingList(
}
}
}
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
if (
paginatorUiState.canPaging && list.size > 0 && visibleItemsIndex.contains(list.size - 6)
) {
Expand All @@ -166,5 +170,6 @@ fun <T : Any> LazyPagingList(
}
}
}
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
}
}
Loading

0 comments on commit 27da952

Please sign in to comment.