Skip to content

Commit 2f6a97e

Browse files
authored
Merge pull request #799 from tunjid/bugfix/1.2.3
Bugfix/1.2.3 Dynamic theme, media alt text
2 parents f23288b + b19382d commit 2f6a97e

File tree

76 files changed

+1704
-481
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1704
-481
lines changed

composeApp/src/commonMain/kotlin/com/tunjid/heron/di/AppGraph.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.tunjid.heron.conversation.di.ConversationBindings
2121
import com.tunjid.heron.data.di.AppCoroutineScope
2222
import com.tunjid.heron.data.di.DataBindings
2323
import com.tunjid.heron.data.repository.AuthRepository
24+
import com.tunjid.heron.data.repository.UserDataRepository
2425
import com.tunjid.heron.data.utilities.writequeue.WriteQueue
2526
import com.tunjid.heron.editprofile.di.EditProfileBindings
2627
import com.tunjid.heron.feed.di.FeedBindings
@@ -95,6 +96,7 @@ interface AppGraph {
9596
@AppCoroutineScope
9697
appScope: CoroutineScope,
9798
authRepository: AuthRepository,
99+
userDataRepository: UserDataRepository,
98100
navigationStateHolder: NavigationStateHolder,
99101
notificationStateHolder: NotificationStateHolder,
100102
imageLoader: ImageLoader,
@@ -103,6 +105,7 @@ interface AppGraph {
103105
): AppState = AppState(
104106
entryMap = entryMap,
105107
authRepository = authRepository,
108+
userDataRepository = userDataRepository,
106109
navigationStateHolder = navigationStateHolder,
107110
notificationStateHolder = notificationStateHolder,
108111
imageLoader = imageLoader,

data/core/src/commonMain/kotlin/com/tunjid/heron/data/network/SessionManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ private val HeronOauthScopes = listOf(
544544

545545
private val ChatProxyPaths = listOf(
546546
"chat.bsky.convo.listConvos",
547+
"chat.bsky.convo.getConvoForMembers",
547548
"chat.bsky.convo.getMessages",
548549
"chat.bsky.convo.getLog",
549550
"chat.bsky.convo.sendMessage",

data/core/src/commonMain/kotlin/com/tunjid/heron/data/network/models/ProfileConversions.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.tunjid.heron.data.network.models
1818

1919
import app.bsky.actor.KnownFollowers
20+
import app.bsky.actor.ProfileAssociated
21+
import app.bsky.actor.ProfileAssociatedChatAllowIncoming
2022
import app.bsky.actor.ProfileView
2123
import app.bsky.actor.ProfileViewBasic
2224
import app.bsky.actor.ProfileViewDetailed
@@ -183,6 +185,9 @@ internal fun ProfileViewBasic.profile() = Profile(
183185
createdListCount = associated?.lists ?: 0,
184186
createdFeedGeneratorCount = associated?.feedgens ?: 0,
185187
createdStarterPackCount = associated?.starterPacks ?: 0,
188+
chat = Profile.ChatInfo(
189+
allowed = associated.allowedChat(),
190+
),
186191
),
187192
labels = labels?.map(com.atproto.label.Label::asExternalModel) ?: emptyList(),
188193
isLabeler = associated?.labeler ?: false,
@@ -205,11 +210,24 @@ internal fun ProfileView.profile() = Profile(
205210
createdListCount = associated?.lists ?: 0,
206211
createdFeedGeneratorCount = associated?.feedgens ?: 0,
207212
createdStarterPackCount = associated?.starterPacks ?: 0,
213+
chat = Profile.ChatInfo(
214+
allowed = associated.allowedChat(),
215+
),
208216
),
209217
labels = labels?.map(com.atproto.label.Label::asExternalModel) ?: emptyList(),
210218
isLabeler = associated?.labeler ?: false,
211219
)
212220

221+
private fun ProfileAssociated?.allowedChat(): Profile.ChatInfo.Allowed =
222+
when (this?.chat?.allowIncoming) {
223+
ProfileAssociatedChatAllowIncoming.All -> Profile.ChatInfo.Allowed.Everyone
224+
ProfileAssociatedChatAllowIncoming.Following -> Profile.ChatInfo.Allowed.Following
225+
ProfileAssociatedChatAllowIncoming.None,
226+
is ProfileAssociatedChatAllowIncoming.Unknown,
227+
null,
228+
-> Profile.ChatInfo.Allowed.NoOne
229+
}
230+
213231
private fun profileViewerStateEntity(
214232
viewingProfileId: ProfileId,
215233
viewedProfileId: ProfileId,

data/core/src/commonMain/kotlin/com/tunjid/heron/data/repository/MessageRepository.kt

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import app.bsky.embed.Record as BskyRecord
2020
import chat.bsky.convo.AddReactionRequest
2121
import chat.bsky.convo.AddReactionResponse
2222
import chat.bsky.convo.DeletedMessageView
23+
import chat.bsky.convo.GetConvoForMembersQueryParams
2324
import chat.bsky.convo.GetLogQueryParams
2425
import chat.bsky.convo.GetLogResponseLogUnion as Log
2526
import chat.bsky.convo.GetMessagesQueryParams
@@ -47,14 +48,10 @@ import com.tunjid.heron.data.core.models.Message
4748
import com.tunjid.heron.data.core.models.offset
4849
import com.tunjid.heron.data.core.models.value
4950
import com.tunjid.heron.data.core.types.ConversationId
51+
import com.tunjid.heron.data.core.types.ProfileId
5052
import com.tunjid.heron.data.core.utilities.Outcome
51-
import com.tunjid.heron.data.database.daos.FeedGeneratorDao
52-
import com.tunjid.heron.data.database.daos.LabelDao
53-
import com.tunjid.heron.data.database.daos.ListDao
5453
import com.tunjid.heron.data.database.daos.MessageDao
55-
import com.tunjid.heron.data.database.daos.PostDao
5654
import com.tunjid.heron.data.database.daos.ProfileDao
57-
import com.tunjid.heron.data.database.daos.StarterPackDao
5855
import com.tunjid.heron.data.database.entities.PopulatedConversationEntity
5956
import com.tunjid.heron.data.database.entities.PopulatedMessageEntity
6057
import com.tunjid.heron.data.database.entities.asExternalModel
@@ -82,6 +79,7 @@ import kotlinx.coroutines.flow.scan
8279
import kotlinx.serialization.Serializable
8380
import sh.christian.ozone.api.AtUri
8481
import sh.christian.ozone.api.Cid
82+
import sh.christian.ozone.api.Did
8583

8684
@Serializable
8785
data class ConversationQuery(
@@ -108,6 +106,10 @@ interface MessageRepository {
108106

109107
suspend fun monitorConversationLogs()
110108

109+
suspend fun resolveConversation(
110+
with: ProfileId,
111+
): Result<ConversationId>
112+
111113
suspend fun sendMessage(
112114
message: Message.Create,
113115
): Outcome
@@ -319,6 +321,32 @@ internal class OfflineMessageRepository @Inject constructor(
319321
}
320322
}
321323

324+
override suspend fun resolveConversation(
325+
with: ProfileId,
326+
): Result<ConversationId> = savedStateDataSource.inCurrentProfileSession { signedInProfileId ->
327+
if (signedInProfileId == null) return@inCurrentProfileSession expiredSessionResult()
328+
networkService.runCatchingWithMonitoredNetworkRetry {
329+
getConvoForMembers(
330+
params = GetConvoForMembersQueryParams(
331+
members = listOf(
332+
with.id.let(::Did),
333+
),
334+
),
335+
)
336+
}
337+
.onSuccess {
338+
multipleEntitySaverProvider.saveInTransaction {
339+
add(
340+
viewingProfileId = signedInProfileId,
341+
convoView = it.convo,
342+
)
343+
}
344+
}
345+
.map {
346+
ConversationId(it.convo.id)
347+
}
348+
} ?: expiredSessionResult()
349+
322350
override suspend fun sendMessage(
323351
message: Message.Create,
324352
): Outcome = savedStateDataSource.inCurrentProfileSession { signedInProfileId ->

data/core/src/commonMain/kotlin/com/tunjid/heron/data/repository/SavedStateDataSource.kt

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import com.tunjid.heron.data.core.models.Preferences
2626
import com.tunjid.heron.data.core.models.Server
2727
import com.tunjid.heron.data.core.types.ProfileHandle
2828
import com.tunjid.heron.data.core.types.ProfileId
29-
import com.tunjid.heron.data.core.types.Uri
3029
import com.tunjid.heron.data.core.utilities.Outcome
3130
import com.tunjid.heron.data.datastore.migrations.VersionedSavedState
3231
import com.tunjid.heron.data.datastore.migrations.VersionedSavedStateOkioSerializer
@@ -101,6 +100,7 @@ abstract class SavedState {
101100
didDoc.service
102101
.firstOrNull()
103102
?.serviceEndpoint
103+
104104
is DPoP -> pdsUrl
105105
}
106106

@@ -216,9 +216,13 @@ abstract class SavedState {
216216
}
217217
}
218218

219-
val InitialSavedState: SavedState = VersionedSavedState.Initial
219+
private val InitialSavedState: SavedState = VersionedSavedState.Initial
220+
221+
private val EmptySavedState: SavedState = VersionedSavedState.Empty
220222

221-
val EmptySavedState: SavedState = VersionedSavedState.Empty
223+
val InitialNavigation: SavedState.Navigation = InitialSavedState.navigation
224+
225+
val EmptyNavigation: SavedState.Navigation = EmptySavedState.navigation
222226

223227
fun SavedState.isSignedIn(): Boolean =
224228
auth.ifSignedIn() != null
@@ -266,7 +270,7 @@ private fun preferencesForUrl(url: String) =
266270
else -> Preferences.BlueSkyGuestPreferences
267271
}
268272

269-
sealed class SavedStateDataSource {
273+
internal sealed class SavedStateDataSource {
270274
abstract val savedState: StateFlow<SavedState>
271275

272276
abstract suspend fun setNavigationState(
@@ -280,14 +284,6 @@ sealed class SavedStateDataSource {
280284
internal abstract suspend fun updateSignedInProfileData(
281285
block: SavedState.ProfileData.(signedInProfileId: ProfileId?) -> SavedState.ProfileData,
282286
)
283-
284-
abstract suspend fun setLastViewedHomeTimelineUri(
285-
uri: Uri,
286-
)
287-
288-
abstract suspend fun setRefreshedHomeTimelineOnLaunch(
289-
refreshOnLaunch: Boolean,
290-
)
291287
}
292288

293289
@Inject
@@ -333,6 +329,7 @@ internal class DataStoreSavedStateDataSource(
333329
update = { copy(auth = null) },
334330
)
335331
}
332+
336333
is SavedState.AuthTokens.Guest -> profileData.updateOrPutValue(
337334
key = auth.authProfileId,
338335
update = {
@@ -343,6 +340,7 @@ internal class DataStoreSavedStateDataSource(
343340
},
344341
put = { SavedState.ProfileData.fromTokens(auth) },
345342
)
343+
346344
else -> profileData.updateOrPutValue(
347345
key = auth.authProfileId,
348346
update = { copy(auth = auth) },
@@ -367,18 +365,6 @@ internal class DataStoreSavedStateDataSource(
367365
)
368366
}
369367

370-
override suspend fun setLastViewedHomeTimelineUri(
371-
uri: Uri,
372-
) = updateSignedInProfileData {
373-
copy(preferences = preferences.copy(lastViewedHomeTimelineUri = uri))
374-
}
375-
376-
override suspend fun setRefreshedHomeTimelineOnLaunch(
377-
refreshOnLaunch: Boolean,
378-
) = updateSignedInProfileData {
379-
copy(preferences = preferences.copy(refreshHomeTimelineOnLaunch = refreshOnLaunch))
380-
}
381-
382368
private suspend fun updateState(update: VersionedSavedState.() -> VersionedSavedState) {
383369
dataStore.updateData(update)
384370
}

data/core/src/commonMain/kotlin/com/tunjid/heron/data/repository/UserDataRepository.kt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import app.bsky.actor.PutPreferencesRequest
55
import com.tunjid.heron.data.core.models.MutedWordPreference
66
import com.tunjid.heron.data.core.models.Preferences
77
import com.tunjid.heron.data.core.models.Timeline
8+
import com.tunjid.heron.data.core.types.Uri
89
import com.tunjid.heron.data.core.utilities.Outcome
910
import com.tunjid.heron.data.network.NetworkService
1011
import com.tunjid.heron.data.utilities.preferenceupdater.PreferenceUpdater
12+
import com.tunjid.heron.data.utilities.runCatchingUnlessCancelled
13+
import com.tunjid.heron.data.utilities.toOutcome
1114
import dev.zacsweers.metro.Inject
1215
import kotlinx.coroutines.flow.Flow
1316
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -17,6 +20,28 @@ interface UserDataRepository {
1720

1821
val preferences: Flow<Preferences>
1922

23+
val navigation: Flow<SavedState.Navigation>
24+
25+
suspend fun persistNavigationState(
26+
navigation: SavedState.Navigation,
27+
): Outcome
28+
29+
suspend fun setLastViewedHomeTimelineUri(
30+
uri: Uri,
31+
): Outcome
32+
33+
suspend fun setRefreshedHomeTimelineOnLaunch(
34+
refreshOnLaunch: Boolean,
35+
): Outcome
36+
37+
suspend fun setDynamicTheming(
38+
dynamicTheming: Boolean,
39+
): Outcome
40+
41+
suspend fun setCompactNavigation(
42+
compactNavigation: Boolean,
43+
): Outcome
44+
2045
suspend fun updateMutedWords(
2146
mutedWordPreferences: List<MutedWordPreference>,
2247
): Outcome
@@ -39,6 +64,42 @@ internal class OfflineUserDataRepository @Inject constructor(
3964
}
4065
.distinctUntilChanged()
4166

67+
override val navigation: Flow<SavedState.Navigation>
68+
get() = savedStateDataSource.savedState
69+
.map { it.navigation }
70+
71+
override suspend fun persistNavigationState(
72+
navigation: SavedState.Navigation,
73+
): Outcome = runCatchingUnlessCancelled {
74+
if (navigation != InitialNavigation) savedStateDataSource.setNavigationState(
75+
navigation = navigation,
76+
)
77+
}.toOutcome()
78+
79+
override suspend fun setLastViewedHomeTimelineUri(
80+
uri: Uri,
81+
): Outcome = updatePreferences {
82+
copy(lastViewedHomeTimelineUri = uri)
83+
}
84+
85+
override suspend fun setRefreshedHomeTimelineOnLaunch(
86+
refreshOnLaunch: Boolean,
87+
): Outcome = updatePreferences {
88+
copy(refreshHomeTimelineOnLaunch = refreshOnLaunch)
89+
}
90+
91+
override suspend fun setDynamicTheming(
92+
dynamicTheming: Boolean,
93+
): Outcome = updatePreferences {
94+
copy(useDynamicTheming = dynamicTheming)
95+
}
96+
97+
override suspend fun setCompactNavigation(
98+
compactNavigation: Boolean,
99+
): Outcome = updatePreferences {
100+
copy(useCompactNavigation = compactNavigation)
101+
}
102+
42103
override suspend fun updateMutedWords(
43104
mutedWordPreferences: List<MutedWordPreference>,
44105
): Outcome {
@@ -91,4 +152,13 @@ internal class OfflineUserDataRepository @Inject constructor(
91152
copy(preferences = updatedPreferences)
92153
}
93154
}
155+
156+
private suspend fun updatePreferences(
157+
updater: Preferences.() -> Preferences,
158+
): Outcome =
159+
runCatchingUnlessCancelled {
160+
savedStateDataSource.updateSignedInProfileData {
161+
copy(preferences = preferences.updater())
162+
}
163+
}.toOutcome()
94164
}

data/database/src/commonMain/kotlin/com/tunjid/heron/data/database/daos/MessageDao.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ interface MessageDao {
6060
lastReaction.createdAt AS lastReaction_createdAt,
6161
MAX(COALESCE(lastReaction.createdAt, lastMessage.sentAt), lastMessage.sentAt) AS sort
6262
FROM conversations
63-
LEFT JOIN messages AS lastMessage
63+
INNER JOIN messages AS lastMessage
6464
ON lastMessageId = lastMessage.id
6565
LEFT JOIN messages AS lastMessageReactedTo
6666
ON lastReactedToMessageId = lastMessageReactedTo.id

0 commit comments

Comments
 (0)