Skip to content

Commit

Permalink
Fix up article list (#80)
Browse files Browse the repository at this point in the history
* Show relative dates in article list

* Add favicon support

* Fix minute-level time formatting

* Add proguard rules, enable minify

* Refresh on initialization

* Add min width to status menu

* Add feed placeholder icon

* Handle missing starred or unread articles

* Fix counts query

* Call refresh all from any list
  • Loading branch information
jocmp authored Apr 6, 2024
1 parent 37264f3 commit c9d5f52
Show file tree
Hide file tree
Showing 51 changed files with 735 additions and 199 deletions.
2 changes: 1 addition & 1 deletion .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/androidTestResultsUserPreferences.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@ android {
}
}

signingConfigs {
getByName("debug") {
storeFile = file("${project.rootDir}/debug.keystore")
}
}

buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("debug")
}
}
compileOptions {
Expand Down Expand Up @@ -83,7 +91,7 @@ dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.okhttp.client)
implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation(platform("androidx.compose:compose-bom:2024.04.00"))
implementation(platform("io.insert-koin:koin-bom:3.5.1"))
implementation(project(":basil"))
testImplementation("junit:junit:4.13.2")
Expand Down
16 changes: 14 additions & 2 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,20 @@

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile

-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue
-dontwarn com.google.errorprone.annotations.CheckReturnValue
-dontwarn com.google.errorprone.annotations.Immutable
-dontwarn com.google.errorprone.annotations.RestrictedApi

# https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md
-keepattributes Signature
-keep class kotlin.coroutines.Continuation

-keep class retrofit2.** { *; }
-keep interface retrofit2.** { *; }
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
package com.jocmp.basilreader.refresher

import com.jocmp.basil.Account
import com.jocmp.basil.AccountManager
import com.jocmp.basilreader.common.AppPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class FeedRefresher(
private val accountManager: AccountManager,
private val appPreferences: AppPreferences,
private val account: Account,
) {
suspend fun refresh() {
return withContext(Dispatchers.IO) {
val account = accountManager
.findByID(appPreferences.accountID.get()) ?: return@withContext

account.refreshAll()
account.refresh()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.jocmp.basilreader.refresher

import android.content.Context
import androidx.work.Constraints
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.jocmp.basilreader.common.AppPreferences
Expand All @@ -26,12 +29,19 @@ class RefreshScheduler(
val (repeatInterval, timeUnit) = interval.toTime ?: return

val request = PeriodicWorkRequestBuilder<RefreshFeedsWorker>(repeatInterval, timeUnit)
.setConstraints(constraints)
.addTag(WORK_TAG)
.build()

workManager.enqueue(request)
}

private val constraints
get() = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()


companion object {
const val WORK_TAG = "refresher"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.koin.androidx.workmanager.dsl.worker
import org.koin.dsl.module

val refresherModule = module {
single { FeedRefresher(accountManager = get(), appPreferences = get()) }
single { FeedRefresher(account = get()) }
single { RefreshScheduler(get(), get()) }
worker { RefreshFeedsWorker(get(), get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import com.jocmp.basil.Folder
import com.jocmp.basil.buildPager
import com.jocmp.basil.countAll
import com.jocmp.basilreader.common.AppPreferences
import com.jocmp.basilreader.refresher.RefreshScheduler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
Expand All @@ -26,8 +29,11 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
class AccountViewModel(
private val account: Account,
private val refreshScheduler: RefreshScheduler,
private val appPreferences: AppPreferences,
) : ViewModel() {
private var refreshJob: Job? = null

val filter = MutableStateFlow(appPreferences.filter.get())

private val articleState = mutableStateOf(account.findArticle(appPreferences.articleID.get()))
Expand Down Expand Up @@ -103,15 +109,13 @@ class AccountViewModel(
}
}

fun refreshFeed(onSuccess: () -> Unit) {
viewModelScope.launch {
when (val currentFilter = filter.value) {
is ArticleFilter.Feeds -> account.refreshFeed(currentFilter.feed)
is ArticleFilter.Folders -> account.refreshFeeds(currentFilter.folder.feeds)
is ArticleFilter.Articles -> account.refreshAll()
}
fun refreshFeed(onComplete: () -> Unit) {
refreshJob?.cancel()

onSuccess()
refreshJob = viewModelScope.launch(Dispatchers.IO) {
account.refresh()

onComplete()
}
}

Expand Down Expand Up @@ -160,9 +164,6 @@ class AccountViewModel(
}
}

fun reload() {
}

private fun resetToDefaultFilter() {
selectArticleFilter(ArticleFilter.default().copy(filterStatus))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
Expand All @@ -43,6 +45,7 @@ import com.jocmp.basilreader.ui.components.rememberWebViewNavigator
import com.jocmp.basilreader.ui.fixtures.FeedPreviewFixture
import com.jocmp.basilreader.ui.fixtures.FolderPreviewFixture
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
Expand Down Expand Up @@ -76,7 +79,7 @@ fun ArticleLayout(
onToggleArticleStar: () -> Unit,
drawerValue: DrawerValue = DrawerValue.Closed
) {
val filterStatus = filter.status
val (isInitialized, setInitialized) = rememberSaveable { mutableStateOf(false) }
val drawerState = rememberDrawerState(drawerValue)
val coroutineScope = rememberCoroutineScope()
val navigator = rememberListDetailPaneScaffoldNavigator<ListDetailPaneScaffoldRole>(
Expand Down Expand Up @@ -139,7 +142,14 @@ fun ArticleLayout(
coroutineScope.launch {
onSelectFeed(feedID)
navigateToList()
snackbarHost.showSnackbar(addFeedSuccessMessage)

launch {
snackbarHost.showSnackbar(addFeedSuccessMessage)
}

launch {
state.startRefresh()
}
}
},
onNavigateToAccounts = onNavigateToAccounts,
Expand Down Expand Up @@ -224,6 +234,14 @@ fun ArticleLayout(
}
)

LaunchedEffect(Unit) {
if (!isInitialized) {
state.startRefresh()

setInitialized(true)
}
}

LaunchedEffect(webViewNavigator) {
val html = ArticleRenderer.render(article, context)

Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemKey
import com.jocmp.basil.Article
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.LocalDateTime

@Composable
fun ArticleList(
Expand All @@ -20,6 +28,7 @@ fun ArticleList(
listState: LazyListState
) {
val composableScope = rememberCoroutineScope()
val currentTime = cachedCurrentTime()

val selectArticle = { articleID: String ->
composableScope.launch {
Expand All @@ -45,9 +54,31 @@ fun ArticleList(
article = item,
selected = selectedArticleKey == item.id,
onSelect = { selectArticle(it) },
currentTime = currentTime
)
}
}
}
}
}

@Composable
fun cachedCurrentTime(): LocalDateTime {
val (currentTime, setCurrentTime) = remember { mutableStateOf(LocalDateTime.now()) }
val coroutineScope = rememberCoroutineScope()

DisposableEffect(Unit) {
val job = coroutineScope.launch {
while(true) {
delay(30_000)
setCurrentTime(LocalDateTime.now())
}
}

onDispose {
job.cancel()
}
}

return currentTime
}
Loading

0 comments on commit c9d5f52

Please sign in to comment.