Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement bookmarks #944

Merged
merged 34 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2c83710
Make service-db KMP
ZacSweers Sep 4, 2023
f43dca9
Create bookmarks:db project
ZacSweers Sep 4, 2023
8cf570b
First pass at BookmarkRepository
ZacSweers Sep 4, 2023
6c94379
First pass integrating BookmarkIconScreen
ZacSweers Sep 10, 2023
c3b1507
Simplify DI
ZacSweers Sep 10, 2023
32c7dab
Add missing dep
ZacSweers Sep 10, 2023
72e901b
Move packages
ZacSweers Sep 10, 2023
46158d1
Add itemsByIds
ZacSweers Sep 10, 2023
4fee786
Implement BookmarksScreen
ZacSweers Sep 10, 2023
296fc05
Split DBs to make this at least work
ZacSweers Sep 10, 2023
88fa0d1
Share a single db again
ZacSweers Sep 10, 2023
47ec62f
Add click handling
ZacSweers Sep 10, 2023
053d061
Add service colors
ZacSweers Sep 10, 2023
b498aaf
Show service origin in start -> end drag
ZacSweers Sep 10, 2023
ca5a022
Merge branch 'main' into z/bookmarks
ZacSweers Sep 10, 2023
f2dfa41
Dependency sort
ZacSweers Sep 10, 2023
3c7eab9
Merge branch 'main' into z/bookmarks
ZacSweers Sep 10, 2023
181a26d
Clean up exit animation
ZacSweers Sep 10, 2023
372ad9c
Animate bookmark icon on changes
ZacSweers Sep 10, 2023
ca5c862
Fill height
ZacSweers Sep 11, 2023
4e3644d
Ignore sorting in bookmarks
ZacSweers Sep 11, 2023
a6fcf22
Scale up in wiggle too
ZacSweers Sep 11, 2023
fcfa22c
Selectively wiggle
ZacSweers Sep 11, 2023
48ab58a
Empty state
ZacSweers Sep 11, 2023
4570150
Animate bookmark icon changes
ZacSweers Sep 11, 2023
a37a653
Add export TODO
ZacSweers Sep 11, 2023
4f313ab
Update bookmarks/src/commonMain/kotlin/catchup/bookmarks/BookmarkRepo…
ZacSweers Sep 12, 2023
d0dc415
Another TODO
ZacSweers Sep 11, 2023
7abf66f
Fixes
ZacSweers Sep 12, 2023
69ea353
Suspenders
ZacSweers Sep 12, 2023
b7c40ac
Spotless
ZacSweers Sep 12, 2023
ddb9ba8
Another update
ZacSweers Sep 12, 2023
15e0042
Add export option
ZacSweers Sep 12, 2023
476f0f1
Posterity note
ZacSweers Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ dependencies {
implementation(libs.sqldelight.runtime)
implementation(libs.telephoto.zoomableImageCoil)
implementation(libs.xmlutil.serialization)
implementation(projects.bookmarks)
implementation(projects.libraries.appconfig)
implementation(projects.libraries.auth)
implementation(projects.libraries.baseUi)
Expand Down
47 changes: 31 additions & 16 deletions app/src/main/kotlin/catchup/app/data/CatchUpDatabaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,50 @@ import app.cash.sqldelight.ColumnAdapter
import app.cash.sqldelight.adapter.primitive.FloatColumnAdapter
import app.cash.sqldelight.adapter.primitive.IntColumnAdapter
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import catchup.bookmarks.db.Bookmark
import catchup.bookmarks.db.CatchUpDatabase as BookmarksDatabase
import catchup.di.AppScope
import catchup.di.SingleIn
import catchup.service.CatchUpDbItem
import catchup.service.db.CatchUpDatabase
import catchup.service.db.CatchUpDbItem
import catchup.util.injection.qualifiers.ApplicationContext
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import dagger.Provides
import kotlinx.datetime.Instant

/**
* This setup is a little weird but apparently how SqlDelight works.
*
* [BookmarksDatabase] is the "real" db instance, but they all implement the same base interface.
*/
@ContributesTo(AppScope::class)
@Module
object CatchUpDatabaseModule {
@Provides
abstract class CatchUpDatabaseModule {
@Binds
@SingleIn(AppScope::class)
fun provideCatchUpDatabase(@ApplicationContext context: Context): CatchUpDatabase =
CatchUpDatabase(
AndroidSqliteDriver(CatchUpDatabase.Schema, context, "catchUpItems.db"),
CatchUpDbItem.Adapter(
InstantColumnAdapter,
IntColumnAdapter,
IntColumnAdapter,
IntColumnAdapter,
IntColumnAdapter,
IntColumnAdapter,
FloatColumnAdapter,
IntColumnAdapter,
abstract fun provideCatchUpDatabase(real: BookmarksDatabase): CatchUpDatabase

companion object {
@Provides
@SingleIn(AppScope::class)
fun provideBookmarksDatabase(@ApplicationContext context: Context): BookmarksDatabase =
BookmarksDatabase(
AndroidSqliteDriver(BookmarksDatabase.Schema, context, "catchup.db"),
Bookmark.Adapter(InstantColumnAdapter),
CatchUpDbItem.Adapter(
InstantColumnAdapter,
IntColumnAdapter,
IntColumnAdapter,
IntColumnAdapter,
IntColumnAdapter,
IntColumnAdapter,
FloatColumnAdapter,
IntColumnAdapter,
),
)
)
}
}

object InstantColumnAdapter : ColumnAdapter<Instant, Long> {
Expand Down
56 changes: 48 additions & 8 deletions app/src/main/kotlin/catchup/app/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package catchup.app.home
import androidx.activity.compose.ReportDrawnWhen
import androidx.annotation.ColorRes
import androidx.compose.animation.Animatable
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
Expand Down Expand Up @@ -41,6 +43,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
Expand All @@ -61,16 +64,21 @@ import androidx.window.layout.FoldingFeature
import catchup.app.CatchUpPreferences
import catchup.app.changes.ChangelogHelper
import catchup.app.home.HomeScreen.Event.NestedNavEvent
import catchup.app.home.HomeScreen.Event.OpenBookmarks
import catchup.app.home.HomeScreen.Event.OpenSettings
import catchup.app.home.HomeScreen.Event.Selected
import catchup.app.home.HomeScreen.Event.ShowChangelog
import catchup.app.home.HomeScreen.State
import catchup.app.service.ServiceScreen
import catchup.app.service.bookmarks.Bookmark
import catchup.app.service.bookmarks.BookmarksScreen
import catchup.app.ui.activity.SettingsScreen
import catchup.bookmarks.BookmarkRepository
import catchup.compose.LocalDisplayFeatures
import catchup.compose.LocalDynamicTheme
import catchup.compose.LocalScrollToTop
import catchup.compose.MutableScrollToTop
import catchup.compose.Wigglable
import catchup.compose.rememberStableCoroutineScope
import catchup.deeplink.DeepLinkable
import catchup.di.AppScope
Expand Down Expand Up @@ -122,12 +130,15 @@ object HomeScreen : Screen, DeepLinkable {
val serviceMetas: ImmutableList<ServiceMeta>,
val changelogAvailable: Boolean,
val selectedIndex: Int,
val bookmarksCount: Long,
val eventSink: (Event) -> Unit = {}
) : CircuitUiState

sealed interface Event : CircuitUiEvent {
data object OpenSettings : Event

data object OpenBookmarks : Event

data object ShowChangelog : Event

data class NestedNavEvent(val navEvent: NavEvent) : Event
Expand All @@ -143,6 +154,7 @@ constructor(
private val serviceMetaMap: Map<String, ServiceMeta>,
private val catchUpPreferences: CatchUpPreferences,
private val changelogHelper: ChangelogHelper,
private val bookmarkRepository: BookmarkRepository
) : Presenter<State> {

@CircuitInject(HomeScreen::class, AppScope::class)
Expand Down Expand Up @@ -174,17 +186,24 @@ constructor(
val context = LocalContext.current
val changelogAvailable by changelogHelper.changelogAvailable(context).collectAsState(false)

val countFlow = remember { bookmarkRepository.bookmarksCountFlow() }
val bookmarksCount by countFlow.collectAsState(0L)

val scope = rememberStableCoroutineScope()
val overlayHost = LocalOverlayHost.current
return State(
serviceMetas = serviceMetas,
changelogAvailable = changelogAvailable,
selectedIndex = selectedIndex,
bookmarksCount = bookmarksCount
) { event ->
when (event) {
OpenSettings -> {
navigator.goTo(SettingsScreen)
}
OpenBookmarks -> {
navigator.goTo(BookmarksScreen)
}
is NestedNavEvent -> {
navigator.onNavEvent(event.navEvent)
}
Expand Down Expand Up @@ -284,7 +303,6 @@ fun Home(state: State, modifier: Modifier = Modifier) {
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalFoundationApi::class,
ExperimentalLayoutApi::class
)
@Composable
fun HomePager(state: State, modifier: Modifier = Modifier) {
Expand Down Expand Up @@ -358,6 +376,8 @@ fun HomePager(state: State, modifier: Modifier = Modifier) {
val nestedScrollModifier = remember {
modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
}
val serviceMetas by rememberUpdatedState(state.serviceMetas)
val eventSink by rememberUpdatedState(state.eventSink)
Scaffold(
modifier = nestedScrollModifier,
contentWindowInsets = WindowInsets(0, 0, 0, 0),
Expand All @@ -370,10 +390,30 @@ fun HomePager(state: State, modifier: Modifier = Modifier) {
actions = {
// TODO wire with Syllabus
if (state.changelogAvailable) {
ChangelogButton { state.eventSink(ShowChangelog) }
ChangelogButton { eventSink(ShowChangelog) }
}

AnimatedVisibility(
state.bookmarksCount > 0,
enter = fadeIn(),
exit = fadeOut(),
) {
Wigglable(
state.bookmarksCount,
shouldWiggle = { old, new -> new > old },
) {
IconButton(
onClick = { eventSink(OpenBookmarks) },
) {
Icon(
imageVector = Icons.Filled.Bookmark,
contentDescription = "Bookmarks",
)
}
}
}
IconButton(
onClick = { state.eventSink(OpenSettings) },
onClick = { eventSink(OpenSettings) },
) {
Icon(
imageVector = Icons.Default.Settings,
Expand Down Expand Up @@ -409,7 +449,7 @@ fun HomePager(state: State, modifier: Modifier = Modifier) {
) {
// Add tabs for all of our pages
val coroutineScope = rememberCoroutineScope()
state.serviceMetas.forEachIndexed { index, serviceMeta ->
serviceMetas.forEachIndexed { index, serviceMeta ->
Tab(
icon = {
Icon(
Expand Down Expand Up @@ -443,7 +483,7 @@ fun HomePager(state: State, modifier: Modifier = Modifier) {
HorizontalPager(
modifier = Modifier.weight(1f),
beyondBoundsPageCount = 1,
key = { state.serviceMetas[it].id },
key = { serviceMetas[it].id },
state = pagerState,
verticalAlignment = Alignment.Top,
) { page ->
Expand All @@ -452,8 +492,8 @@ fun HomePager(state: State, modifier: Modifier = Modifier) {
LocalScrollToTop provides scrollToTop.takeIf { pagerState.currentPage == page }
) {
CircuitContent(
screen = ServiceScreen(state.serviceMetas[page].id),
onNavEvent = { state.eventSink(NestedNavEvent(it)) }
screen = ServiceScreen(serviceMetas[page].id),
onNavEvent = { eventSink(NestedNavEvent(it)) }
)
}
}
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/kotlin/catchup/app/service/ServiceMediator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import androidx.paging.LoadType.REFRESH
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import catchup.app.data.lastUpdated
import catchup.service.CatchUpDbItem
import catchup.service.api.DataRequest
import catchup.service.api.Service
import catchup.service.api.toCatchUpDbItem
import catchup.service.db.CatchUpDatabase
import catchup.service.db.CatchUpDbItem
import com.apollographql.apollo3.exception.ApolloException
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
Expand Down Expand Up @@ -139,7 +140,7 @@ constructor(
catchUpDatabase.transaction {
if (loadType == REFRESH) {
Timber.tag("ServiceMediator").d("Clearing DB $serviceId")
catchUpDatabase.serviceQueries.deleteItemByService(serviceId)
catchUpDatabase.serviceQueries.deleteItemsByService(serviceId)
catchUpDatabase.serviceQueries.deleteOperationsByService(serviceId)
catchUpDatabase.serviceQueries.deleteRemoteKeyByService(serviceId)
}
Expand Down
7 changes: 1 addition & 6 deletions app/src/main/kotlin/catchup/app/service/ServiceScreen.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package catchup.app.service

import android.content.Intent
import android.widget.Toast
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
Expand Down Expand Up @@ -48,7 +47,6 @@ import app.cash.sqldelight.paging3.QueryPagingSource
import catchup.app.data.LinkManager
import catchup.app.service.ServiceScreen.Event
import catchup.app.service.ServiceScreen.Event.ItemActionClicked
import catchup.app.service.ServiceScreen.Event.ItemActionClicked.Action.FAVORITE
import catchup.app.service.ServiceScreen.Event.ItemActionClicked.Action.SHARE
import catchup.app.service.ServiceScreen.Event.ItemActionClicked.Action.SUMMARIZE
import catchup.app.service.ServiceScreen.Event.ItemClicked
Expand All @@ -65,6 +63,7 @@ import catchup.pullrefresh.rememberPullRefreshState
import catchup.service.api.CatchUpItem
import catchup.service.api.ContentType
import catchup.service.api.Service
import catchup.service.api.toCatchUpItem
import catchup.service.db.CatchUpDatabase
import catchup.summarizer.SummarizerScreen
import com.slack.circuit.codegen.annotations.CircuitInject
Expand Down Expand Up @@ -113,7 +112,6 @@ data class ServiceScreen(val serviceKey: String) : Screen {

data class ItemActionClicked(val item: CatchUpItem, val action: Action) : Event {
enum class Action {
FAVORITE,
SHARE,
SUMMARIZE
}
Expand Down Expand Up @@ -219,9 +217,6 @@ constructor(
is ItemActionClicked -> {
val url = event.item.clickUrl!!
when (event.action) {
FAVORITE -> {
Toast.makeText(context, "Not implemented", Toast.LENGTH_SHORT).show()
}
SHARE -> {
val shareIntent =
Intent().apply {
Expand Down
33 changes: 33 additions & 0 deletions app/src/main/kotlin/catchup/app/service/TextActionItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package catchup.app.service

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp

@Composable
fun TextActionItem(
icon: ImageVector,
tint: Color,
contentDescription: String,
modifier: Modifier = Modifier,
enabled: Boolean = true,
onClick: () -> Unit,
) {
IconButton(enabled = enabled, modifier = modifier, onClick = onClick) {
// TODO this always crossfades the initial load for something like bookmarks
Crossfade(icon, label = "Action item crossfade") {
Icon(
imageVector = it,
contentDescription = contentDescription,
tint = tint,
modifier = Modifier.size(24.dp)
)
}
}
}
Loading