Skip to content

Commit 850f025

Browse files
authored
Merge pull request #1240 from DimensionDev/feature/bsky_bookmarks
add bookmark support for bluesky
2 parents 0be3b0b + 9faa24c commit 850f025

File tree

5 files changed

+182
-0
lines changed

5 files changed

+182
-0
lines changed

compose-ui/src/commonMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ public sealed interface TimelineTabItem : TabItem {
490490
icon = IconType.Mixed(IconType.Material.MaterialIcon.Feeds, accountKey),
491491
),
492492
),
493+
Bluesky.BookmarkTimelineTabItem(
494+
accountType = AccountType.Specific(accountKey),
495+
),
493496
DirectMessageTabItem(
494497
account = AccountType.Specific(accountKey),
495498
metaData =
@@ -887,6 +890,30 @@ public object Bluesky {
887890

888891
override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData)
889892
}
893+
894+
@Serializable
895+
public data class BookmarkTimelineTabItem(
896+
override val account: AccountType,
897+
override val metaData: TabMetaData,
898+
) : TimelineTabItem {
899+
override val key: String = "bookmark_$account"
900+
901+
override fun createPresenter(): TimelinePresenter =
902+
dev.dimension.flare.ui.presenter.home.bluesky
903+
.BlueskyBookmarkTimelinePresenter(account)
904+
905+
override fun update(metaData: TabMetaData): TabItem = copy(metaData = metaData)
906+
907+
public constructor(accountType: AccountType) :
908+
this(
909+
account = accountType,
910+
metaData =
911+
TabMetaData(
912+
title = TitleType.Localized(TitleType.Localized.LocalizedKey.Bookmark),
913+
icon = IconType.Material(IconType.Material.MaterialIcon.Bookmark),
914+
),
915+
)
916+
}
890917
}
891918

892919
@Serializable

shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/bluesky/BlueskyDataSource.kt

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import app.bsky.actor.PreferencesUnion
1313
import app.bsky.actor.PutPreferencesRequest
1414
import app.bsky.actor.SavedFeed
1515
import app.bsky.actor.SavedFeedType
16+
import app.bsky.bookmark.CreateBookmarkRequest
17+
import app.bsky.bookmark.DeleteBookmarkRequest
1618
import app.bsky.embed.Images
1719
import app.bsky.embed.ImagesImage
1820
import app.bsky.embed.Record
@@ -59,6 +61,7 @@ import com.atproto.repo.PutRecordRequest
5961
import com.atproto.repo.StrongRef
6062
import dev.dimension.flare.common.BasePagingSource
6163
import dev.dimension.flare.common.BaseRemoteMediator
64+
import dev.dimension.flare.common.BaseTimelineLoader
6265
import dev.dimension.flare.common.CacheData
6366
import dev.dimension.flare.common.Cacheable
6467
import dev.dimension.flare.common.FileItem
@@ -2349,6 +2352,101 @@ internal class BlueskyDataSource(
23492352
null
23502353
},
23512354
).toPersistentList()
2355+
2356+
fun bookmarkTimeline(): BaseTimelineLoader =
2357+
BookmarkTimelineRemoteMediator(
2358+
service = service,
2359+
accountKey = accountKey,
2360+
database = database,
2361+
)
2362+
2363+
override fun bookmark(
2364+
statusKey: MicroBlogKey,
2365+
uri: String,
2366+
cid: String,
2367+
) {
2368+
coroutineScope.launch {
2369+
updateStatusUseCase<StatusContent.Bluesky>(
2370+
statusKey = statusKey,
2371+
accountKey = accountKey,
2372+
cacheDatabase = database,
2373+
) { content ->
2374+
content.copy(
2375+
data =
2376+
content.data.copy(
2377+
viewer = content.data.viewer?.copy(bookmarked = true),
2378+
),
2379+
)
2380+
}
2381+
tryRun {
2382+
service
2383+
.createBookmark(
2384+
CreateBookmarkRequest(
2385+
uri = AtUri(uri),
2386+
cid = Cid(cid),
2387+
),
2388+
).requireResponse()
2389+
}.onFailure {
2390+
it.printStackTrace()
2391+
// rollback
2392+
updateStatusUseCase<StatusContent.Bluesky>(
2393+
statusKey = statusKey,
2394+
accountKey = accountKey,
2395+
cacheDatabase = database,
2396+
) { content ->
2397+
content.copy(
2398+
data =
2399+
content.data.copy(
2400+
viewer = content.data.viewer?.copy(bookmarked = false),
2401+
),
2402+
)
2403+
}
2404+
}
2405+
}
2406+
}
2407+
2408+
override fun unbookmark(
2409+
statusKey: MicroBlogKey,
2410+
uri: String,
2411+
) {
2412+
coroutineScope.launch {
2413+
updateStatusUseCase<StatusContent.Bluesky>(
2414+
statusKey = statusKey,
2415+
accountKey = accountKey,
2416+
cacheDatabase = database,
2417+
) { content ->
2418+
content.copy(
2419+
data =
2420+
content.data.copy(
2421+
viewer = content.data.viewer?.copy(bookmarked = false),
2422+
),
2423+
)
2424+
}
2425+
tryRun {
2426+
service
2427+
.deleteBookmark(
2428+
DeleteBookmarkRequest(
2429+
uri = AtUri(uri),
2430+
),
2431+
).requireResponse()
2432+
}.onFailure {
2433+
it.printStackTrace()
2434+
// rollback
2435+
updateStatusUseCase<StatusContent.Bluesky>(
2436+
statusKey = statusKey,
2437+
accountKey = accountKey,
2438+
cacheDatabase = database,
2439+
) { content ->
2440+
content.copy(
2441+
data =
2442+
content.data.copy(
2443+
viewer = content.data.viewer?.copy(bookmarked = true),
2444+
),
2445+
)
2446+
}
2447+
}
2448+
}
2449+
}
23522450
}
23532451

23542452
internal inline fun <reified T : Any> T.bskyJson(): JsonContent = bskyJson.encodeAsJsonContent(this)

shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/microblog/StatusEvent.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ internal sealed interface StatusEvent {
116116
likedUri: String?,
117117
)
118118

119+
fun bookmark(
120+
statusKey: MicroBlogKey,
121+
uri: String,
122+
cid: String,
123+
)
124+
125+
fun unbookmark(
126+
statusKey: MicroBlogKey,
127+
uri: String,
128+
)
129+
119130
fun likeWithResult(
120131
statusKey: MicroBlogKey,
121132
cid: String,

shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,24 @@ internal fun PostView.renderStatus(
504504
displayItem = StatusAction.Item.More,
505505
actions =
506506
listOfNotNull(
507+
StatusAction.Item.Bookmark(
508+
count = 0,
509+
bookmarked = viewer?.bookmarked == true,
510+
onClicked = {
511+
if (viewer?.bookmarked == true) {
512+
event.unbookmark(
513+
statusKey = statusKey,
514+
uri = uri.atUri,
515+
)
516+
} else {
517+
event.bookmark(
518+
statusKey = statusKey,
519+
uri = uri.atUri,
520+
cid = cid.cid,
521+
)
522+
}
523+
},
524+
),
507525
if (isFromMe) {
508526
StatusAction.Item.Delete(
509527
onClicked = {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.dimension.flare.ui.presenter.home.bluesky
2+
3+
import dev.dimension.flare.common.BaseTimelineLoader
4+
import dev.dimension.flare.data.datasource.bluesky.BlueskyDataSource
5+
import dev.dimension.flare.data.repository.AccountRepository
6+
import dev.dimension.flare.data.repository.accountServiceFlow
7+
import dev.dimension.flare.model.AccountType
8+
import dev.dimension.flare.ui.presenter.home.TimelinePresenter
9+
import kotlinx.coroutines.flow.Flow
10+
import kotlinx.coroutines.flow.map
11+
import org.koin.core.component.KoinComponent
12+
import org.koin.core.component.inject
13+
14+
public class BlueskyBookmarkTimelinePresenter(
15+
private val accountType: AccountType,
16+
) : TimelinePresenter(),
17+
KoinComponent {
18+
private val accountRepository: AccountRepository by inject()
19+
override val loader: Flow<BaseTimelineLoader> by lazy {
20+
accountServiceFlow(
21+
accountType = accountType,
22+
repository = accountRepository,
23+
).map { value ->
24+
require(value is BlueskyDataSource)
25+
value.bookmarkTimeline()
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)