Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
200 commits
Select commit Hold shift + click to select a range
49cbd23
Update video upload timeout
tunjid Feb 5, 2026
2b353d4
Merge pull request #957 from tunjid/tj/video-upload-timeout
tunjid Feb 5, 2026
966f596
Add graze feedbuilder models
tunjid Feb 6, 2026
60c0b31
Merge pull request #958 from tunjid/tj/graze-1
tunjid Feb 6, 2026
c2ff768
Boilerplate for Graze editor UI
tunjid Feb 6, 2026
aeb3e8f
Merge pull request #959 from tunjid/tj/graze-2
tunjid Feb 6, 2026
c0ebb6d
Publish APKs to GitHub releases in CI
CompeyDev Feb 6, 2026
84a402d
Start filling out graze editor UI
tunjid Feb 6, 2026
565e734
Add graze filter UI components
tunjid Feb 6, 2026
3bf099a
UI for graze editor filters
tunjid Feb 6, 2026
b5ab8d5
Update UI
tunjid Feb 6, 2026
708dbb4
UI tweaks
tunjid Feb 6, 2026
535abf8
Clean up code
tunjid Feb 6, 2026
901c7cc
Add logic to add filter
tunjid Feb 7, 2026
78c2f8d
Update code
tunjid Feb 7, 2026
dd996ca
Update broken code
tunjid Feb 7, 2026
216127d
Wire up adding a filter
tunjid Feb 7, 2026
c916484
Add destination for graze builder
tunjid Feb 7, 2026
9c73b8d
Code clean up
tunjid Feb 7, 2026
ee21efd
More edits to filters
tunjid Feb 7, 2026
e459231
Add updates and removes
tunjid Feb 7, 2026
5f21936
Code clean up
tunjid Feb 7, 2026
1cae7cb
Add root filter flipping action
tunjid Feb 7, 2026
221a54c
Fix path for row click
tunjid Feb 7, 2026
d855ec6
Minor clean up
tunjid Feb 7, 2026
87514cf
Clean up imports
tunjid Feb 7, 2026
a4dae18
Merge pull request #961 from tunjid/tj/graze-3
tunjid Feb 7, 2026
67fffc9
Use bottom sheet for graze editor
tunjid Feb 8, 2026
5eff26a
Merge pull request #962 from tunjid/tj/graze-4
tunjid Feb 8, 2026
7c1bef0
Clean up code
tunjid Feb 8, 2026
f830012
limit comparator for attribute compare
tunjid Feb 8, 2026
e5c1c3b
Clean up code
tunjid Feb 8, 2026
6cd674c
Standardize fileter components
tunjid Feb 8, 2026
32f0f75
Clean up code
tunjid Feb 8, 2026
4057d2a
Tighten up code
tunjid Feb 8, 2026
d557ce4
Dropdown labels
tunjid Feb 8, 2026
b557dd5
Merge pull request #960 from CompeyDev/ci/release-workflow
tunjid Feb 8, 2026
fee80a1
Merge pull request #963 from tunjid/tj/graze-5
tunjid Feb 8, 2026
fbcf9d6
Entity filter editing
tunjid Feb 8, 2026
03829b5
Mild aesthtic changes
tunjid Feb 8, 2026
821cc9f
Add padding
tunjid Feb 8, 2026
e6fbb0b
PR feedback
tunjid Feb 8, 2026
0c7f06c
PR feedback
tunjid Feb 8, 2026
3c1fbb3
Clean up code
tunjid Feb 8, 2026
0dee1e3
Merge pull request #964 from tunjid/tj/graze-6
tunjid Feb 8, 2026
afb639e
Move to lazy layouts
tunjid Feb 8, 2026
240c557
Update code style
tunjid Feb 8, 2026
9941ceb
Add empty factory methods to filters
tunjid Feb 9, 2026
159dfb6
Pass modifiers to Filter composables
tunjid Feb 9, 2026
21b4879
Use child id value
tunjid Feb 9, 2026
b1e25c5
Merge pull request #965 from tunjid/tj/graze-7
tunjid Feb 9, 2026
4bc65a9
Update analysis filter
tunjid Feb 9, 2026
06e3c98
Consolidation of AnalysisFilter and MLFilter
tunjid Feb 9, 2026
203a07c
Add enum for Moderation filter
tunjid Feb 9, 2026
1d85745
Add categories for analysis
tunjid Feb 9, 2026
9e6cb42
Use value classes for categories of filters
tunjid Feb 9, 2026
c39b6e0
Pull out ThresholdSlider
tunjid Feb 9, 2026
4b53a6f
Merge pull request #966 from tunjid/tj/graze-8
tunjid Feb 9, 2026
70b8e42
More filter edits
tunjid Feb 9, 2026
ea66b60
More style updates for filters
tunjid Feb 9, 2026
6b270fe
Add shared elements to graze editor
tunjid Feb 9, 2026
1e23dea
PR feedback
tunjid Feb 9, 2026
2d2b55a
Merge pull request #967 from tunjid/tj/graze-9
tunjid Feb 9, 2026
9de358b
PR feedback
tunjid Feb 9, 2026
aad9dbd
Merge pull request #968 from tunjid/tj/graze-10
tunjid Feb 9, 2026
18f2526
Add navigation to graze feed builder
tunjid Feb 9, 2026
1f22277
PR feedback
tunjid Feb 9, 2026
da8dfbb
Merge pull request #969 from tunjid/tj/graze-11
tunjid Feb 9, 2026
24055a1
Make graze editor extend to bottom, and signal unsupported filters
tunjid Feb 9, 2026
944f156
Merge pull request #970 from tunjid/tj/graze-12
tunjid Feb 9, 2026
c5afc11
Implement SocialGraphFilter
tunjid Feb 10, 2026
eabca6e
Merge pull request #971 from tunjid/tj/graze-13
tunjid Feb 10, 2026
23931b8
Implement SocialUserListFilter
tunjid Feb 11, 2026
93f7d73
Fix typo
tunjid Feb 11, 2026
922a088
PR feedback
tunjid Feb 11, 2026
6b8a3b7
Merge pull request #972 from tunjid/tj/graze-14
tunjid Feb 11, 2026
77000c5
Add simple filters
tunjid Feb 11, 2026
a0afab5
PR feedback
tunjid Feb 11, 2026
aaba4bb
PR feedback
tunjid Feb 11, 2026
76f5782
Merge pull request #973 from tunjid/tj/graze-15
tunjid Feb 11, 2026
a8ce8a1
Start integrating graze CRUD into data layer
tunjid Feb 13, 2026
178d1b5
Upate serial name paths
tunjid Feb 13, 2026
e7373f8
Debugging
tunjid Feb 13, 2026
52a84bb
Make filter id transient
tunjid Feb 13, 2026
c82260d
Update serial names
tunjid Feb 13, 2026
9d2c99e
Pull out filter serializers to their own package
tunjid Feb 13, 2026
20e8438
Add LeafFilterSerializer
tunjid Feb 13, 2026
ccb8bea
Iterate on leaf filter serialization
tunjid Feb 14, 2026
95c7fa2
Iterate on FilterSerializers
tunjid Feb 14, 2026
d5027dd
Lint
tunjid Feb 14, 2026
b370253
Update feed record after graze call
tunjid Feb 14, 2026
358d3a9
Observe feed after CRUD
tunjid Feb 14, 2026
b3717b2
Error messages
tunjid Feb 14, 2026
199bfe3
PR feedback
tunjid Feb 14, 2026
af46c4e
Clean up imports
tunjid Feb 14, 2026
c9eb7d4
Use string resources
tunjid Feb 14, 2026
4e63c7e
Merge pull request #975 from tunjid/tj/graze-16
tunjid Feb 14, 2026
ef5df27
Add icons to filters
tunjid Feb 14, 2026
231248b
Add icon for advanced filters
tunjid Feb 14, 2026
15f03d6
Update icons
tunjid Feb 14, 2026
14483ef
Add property for determining is a feed is a graze feed
tunjid Feb 14, 2026
9879ea0
Merge pull request #976 from tunjid/tj/graze-17
tunjid Feb 14, 2026
7300078
Allow navigation to graze editor from feed screen
tunjid Feb 14, 2026
f1f7a69
Hide sheet on click
tunjid Feb 14, 2026
eaf4c78
Merge pull request #977 from tunjid/tj/graze-18
tunjid Feb 14, 2026
daebae4
Fetch graze feed on load
tunjid Feb 14, 2026
31f8e04
Clean up
tunjid Feb 14, 2026
ac413f8
Merge pull request #978 from tunjid/tj/graze-19
tunjid Feb 14, 2026
7fb0e7e
Delete not found records
tunjid Feb 14, 2026
5cc7c14
Merge pull request #979 from tunjid/tj/not-found-records
tunjid Feb 14, 2026
b860b7a
Update UI logic for graze feeds
tunjid Feb 15, 2026
2f348af
Model graze responses better
tunjid Feb 15, 2026
7bfa5d0
Fix stale remember
tunjid Feb 15, 2026
09034e5
Clean up code
tunjid Feb 15, 2026
b314cd4
Merge pull request #980 from tunjid/tj/graze-20
tunjid Feb 15, 2026
07b7bbf
Editable params for graze feeds
tunjid Feb 15, 2026
af819d0
PR feedback
tunjid Feb 15, 2026
ed68cc5
PR feedback
tunjid Feb 15, 2026
48a7d5e
More cleanup
tunjid Feb 15, 2026
e45ee81
Merge pull request #981 from tunjid/tj/graze-21
tunjid Feb 15, 2026
6c6c775
Fix route uri in graze editor navigation
tunjid Feb 15, 2026
48329bd
Merge pull request #982 from tunjid/tj/graze-22
tunjid Feb 15, 2026
100d582
Disable interacting with the feed builder when loading
tunjid Feb 15, 2026
f0686e0
Show a loading dialog for graze editor
tunjid Feb 15, 2026
230cb9e
Merge pull request #983 from tunjid/tj/graze-23
tunjid Feb 15, 2026
ffce897
Delete not found graze feeds
tunjid Feb 15, 2026
a891c5d
Merge pull request #985 from tunjid/tj/graze-24
tunjid Feb 15, 2026
fc90dde
Expand heron media content types
tunjid Feb 15, 2026
1e2201b
build SwitchAccount ui
joelmuraguri Feb 2, 2026
892d3e3
handle button for a switching account action
joelmuraguri Feb 2, 2026
0e6bfe8
wire data logic into UI
joelmuraguri Feb 2, 2026
d15535c
Remove duplicates
joelmuraguri Feb 2, 2026
fe8237f
Fixes : Refactor ext helper name and add icon description
joelmuraguri Feb 2, 2026
a078539
code cleanup
joelmuraguri Feb 2, 2026
f74ff01
handle children composables in `when` conditional
joelmuraguri Feb 2, 2026
ab7996e
handle effects in SettingsScreen when switching session
joelmuraguri Feb 2, 2026
a6164d0
Validate and refresh token
joelmuraguri Feb 5, 2026
ce7e1a2
remove enabled flag
joelmuraguri Feb 5, 2026
567c7f7
lock UI interaction and dim screen during account switching
joelmuraguri Feb 5, 2026
9e24b0b
Update switchSession atomically, to prevent race conditions
joelmuraguri Feb 6, 2026
ad6208d
spotless check
joelmuraguri Feb 6, 2026
ae17149
Handle token refresh with session guard
joelmuraguri Feb 7, 2026
c55c3fc
define SessionContext with advanced coroutines
joelmuraguri Feb 12, 2026
1b1c944
handle auth state for a session
joelmuraguri Feb 12, 2026
d6b1bf3
implement session-safe switching between user accounts
joelmuraguri Feb 12, 2026
e0224f5
include profileData
joelmuraguri Feb 12, 2026
655b687
Fix session switch to use SessionContext as source of truth for refre…
joelmuraguri Feb 12, 2026
4b0639d
Fix typos
tunjid Feb 15, 2026
fe5aefa
PR feedback
tunjid Feb 15, 2026
8ea3628
Clean up
tunjid Feb 15, 2026
4ef69d4
Merge pull request #986 from tunjid/tj/media-content-types
tunjid Feb 15, 2026
9ad35b7
Move methods to RecordRepository
tunjid Feb 16, 2026
332d765
Merge pull request #987 from tunjid/tj/record-repository-update
tunjid Feb 16, 2026
cc3be41
Add recentLists to RecordRepository
tunjid Feb 16, 2026
ef3e23d
Lazy load signed in profile's lists
tunjid Feb 16, 2026
5ee84b2
Merge pull request #988 from tunjid/tj/lazy-timeline-lists
tunjid Feb 16, 2026
fae493d
Edits for saving feeds
tunjid Feb 16, 2026
237847e
Merge pull request #989 from tunjid/tj/graze-feed-edits
tunjid Feb 16, 2026
f005104
Move filter UI into their own package
tunjid Feb 16, 2026
ff4ca3c
PR feedback
tunjid Feb 16, 2026
95cbb15
Merge pull request #990 from tunjid/tj/graze-filter-package
tunjid Feb 16, 2026
2405de1
Update copy
tunjid Feb 16, 2026
1efdfe2
Feed creator UI improvements
tunjid Feb 16, 2026
b14266f
Add sensible default for pending feed display name
tunjid Feb 16, 2026
f3afef9
PR feedback
tunjid Feb 16, 2026
c6b5f7e
Merge pull request #991 from tunjid/tj/graze-25
tunjid Feb 16, 2026
321e4b1
Add validation UI cues to filters
tunjid Feb 16, 2026
eef7ce9
Validation for empty root filters
tunjid Feb 16, 2026
37c6bb5
Place entity filters before attribute filters
tunjid Feb 16, 2026
c6c7098
PR feedback
tunjid Feb 16, 2026
1fcd45c
Clean up
tunjid Feb 16, 2026
628b1f6
Merge pull request #992 from tunjid/tj/graze-26
tunjid Feb 16, 2026
27f6b33
Add support for feed list filters
tunjid Feb 16, 2026
48c0a66
Merge pull request #993 from tunjid/tj/graze-27
tunjid Feb 16, 2026
e4052ce
Remove filter card border, tint is enough
tunjid Feb 16, 2026
d6605ee
Merge pull request #994 from tunjid/tj/graze-28
tunjid Feb 16, 2026
d07d3f1
Pop all related feed routes off the backstack when feed is deleted
tunjid Feb 16, 2026
f09e30e
PR feedback
tunjid Feb 16, 2026
b31ed91
PR feedback
tunjid Feb 16, 2026
1528033
Merge pull request #995 from tunjid/tj/graze-29
tunjid Feb 16, 2026
fe062ab
Changes for switching sessions
tunjid Feb 16, 2026
e3f20ec
Throw SessionSwitchException
tunjid Feb 16, 2026
c117856
Merge pull request #936 from tunjid/joel/account-switching-ui
tunjid Feb 16, 2026
9f374df
enpty timeline items WIP
tunjid Feb 16, 2026
500be32
Empty timeline logic
tunjid Feb 16, 2026
57fde94
Add TimelineItem.Placeholder sealed class
tunjid Feb 16, 2026
aeb761f
Add EmptyPost
tunjid Feb 16, 2026
e845dd1
Update UI for empty timelines
tunjid Feb 16, 2026
77786b9
Use explicit this to refer to latest timeline
tunjid Feb 16, 2026
e404351
Impl animations and shared element transition
joelmuraguri Feb 15, 2026
b143863
Load active profile to show active session
joelmuraguri Feb 15, 2026
b42eb02
Handle app bars visibility when switching session
joelmuraguri Feb 15, 2026
4fe8045
Styling for empty posts
tunjid Feb 16, 2026
d8f5a4b
Extract magic numbers
tunjid Feb 16, 2026
bea4e46
Merge pull request #996 from tunjid/tj/empty-timelines
tunjid Feb 16, 2026
fde4fbd
Merge pull request #984 from tunjid/joel/account-switching-animations
tunjid Feb 16, 2026
2e544c7
Account switching polish
tunjid Feb 16, 2026
1d52446
Misc alignment
tunjid Feb 16, 2026
759d0b3
PR feedback
tunjid Feb 16, 2026
30590a5
Merge pull request #997 from tunjid/tj/account-switching-polish
tunjid Feb 16, 2026
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
20 changes: 20 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,23 @@ jobs:
track: internal
mappingFile: composeApp/build/outputs/mapping/release/mapping.txt
debugSymbols: composeApp/build/intermediates/merged_native_libs/release/mergeReleaseNativeLibs/out/lib


- name: Extract signed APKs
run: |
curl -L -o bundletool.jar https://github.com/google/bundletool/releases/download/1.18.3/bundletool-all-1.18.3.jar
java -jar bundletool.jar build-apks \
--bundle=${{ steps.sign_app.outputs.signedReleaseFile }} \
--output=composeApp/build/outputs/apk/release/heron.apks \
--mode=universal

unzip -o composeApp/build/outputs/apk/release/heron.apks -d composeApp/build/outputs/apk/release/

- name: Upload to GitHub releases
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ github.run_number }}
name: Heron v${{ github.run_number }}
files: composeApp/build/outputs/apk/release/universal.apk
draft: true
allow_updates: true
3 changes: 3 additions & 0 deletions build-logic/convention/src/main/kotlin/ext/UiModuleExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ fun org.gradle.api.Project.configureUiModule(

api(libs.kotlinx.coroutines.core)

implementation(libs.navigation.event)
implementation(libs.navigation.event.compose)

api(libs.savedstate.multiplatform.savedstate)
api(libs.savedstate.multiplatform.compose)

Expand Down
1 change: 1 addition & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ kotlin {
implementation(project(":feature:edit-profile"))
implementation(project(":feature:feed"))
implementation(project(":feature:gallery"))
implementation(project(":feature:graze-editor"))
implementation(project(":feature:home"))
implementation(project(":feature:list"))
implementation(project(":feature:messages"))
Expand Down
7 changes: 7 additions & 0 deletions composeApp/src/commonMain/kotlin/com/tunjid/heron/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import com.tunjid.heron.feed.di.FeedBindings
import com.tunjid.heron.feed.di.FeedNavigationBindings
import com.tunjid.heron.gallery.di.GalleryBindings
import com.tunjid.heron.gallery.di.GalleryNavigationBindings
import com.tunjid.heron.graze.editor.di.GrazeEditorBindings
import com.tunjid.heron.graze.editor.di.GrazeEditorNavigationBindings
import com.tunjid.heron.home.di.HomeBindings
import com.tunjid.heron.home.di.HomeNavigationBindings
import com.tunjid.heron.images.ImageLoader
Expand Down Expand Up @@ -97,6 +99,7 @@ fun createAppState(
feedNavigationBindings = FeedNavigationBindings,
editProfileNavigationBindings = EditProfileNavigationBindings,
galleryNavigationBindings = GalleryNavigationBindings,
grazeEditorNavigationBindings = GrazeEditorNavigationBindings,
homeNavigationBindings = HomeNavigationBindings,
listNavigationBindings = ListNavigationBindings,
messagesNavigationBindings = MessagesNavigationBindings,
Expand Down Expand Up @@ -155,6 +158,10 @@ fun createAppState(
scaffoldBindings = scaffoldBindings,
dataBindings = dataBindings,
),
grazeEditorBindings = GrazeEditorBindings(
scaffoldBindings = scaffoldBindings,
dataBindings = dataBindings,
),
homeBindings = HomeBindings(
scaffoldBindings = scaffoldBindings,
dataBindings = dataBindings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.tunjid.heron.data.utilities.writequeue.WriteQueue
import com.tunjid.heron.editprofile.di.EditProfileBindings
import com.tunjid.heron.feed.di.FeedBindings
import com.tunjid.heron.gallery.di.GalleryBindings
import com.tunjid.heron.graze.editor.di.GrazeEditorBindings
import com.tunjid.heron.home.di.HomeBindings
import com.tunjid.heron.images.ImageLoader
import com.tunjid.heron.list.di.ListBindings
Expand Down Expand Up @@ -73,6 +74,7 @@ interface AppGraph {
@Includes editProfileBindings: EditProfileBindings,
@Includes feedBindings: FeedBindings,
@Includes galleryBindings: GalleryBindings,
@Includes grazeEditorBindings: GrazeEditorBindings,
@Includes homeBindings: HomeBindings,
@Includes listBindings: ListBindings,
@Includes messagesBindings: MessagesBindings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.tunjid.heron.conversation.di.ConversationNavigationBindings
import com.tunjid.heron.editprofile.di.EditProfileNavigationBindings
import com.tunjid.heron.feed.di.FeedNavigationBindings
import com.tunjid.heron.gallery.di.GalleryNavigationBindings
import com.tunjid.heron.graze.editor.di.GrazeEditorNavigationBindings
import com.tunjid.heron.home.di.HomeNavigationBindings
import com.tunjid.heron.list.di.ListNavigationBindings
import com.tunjid.heron.messages.di.MessagesNavigationBindings
Expand Down Expand Up @@ -55,6 +56,7 @@ interface AppNavigationGraph {
@Includes editProfileNavigationBindings: EditProfileNavigationBindings,
@Includes feedNavigationBindings: FeedNavigationBindings,
@Includes galleryNavigationBindings: GalleryNavigationBindings,
@Includes grazeEditorNavigationBindings: GrazeEditorNavigationBindings,
@Includes homeNavigationBindings: HomeNavigationBindings,
@Includes listNavigationBindings: ListNavigationBindings,
@Includes messagesNavigationBindings: MessagesNavigationBindings,
Expand Down
1 change: 1 addition & 0 deletions data/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ kotlin {
commonMain {
dependencies {
implementation(project(":data:files"))
implementation(project(":data:graze"))
implementation(project(":data:models"))
implementation(project(":data:database"))
implementation(project(":data:lexicons"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ internal data class VersionedSavedState(
?.let(profileData::get)
?.auth

override fun profileData(
profileId: ProfileId,
): ProfileData? = profileData[profileId]

companion object {
internal val Initial: VersionedSavedState = VersionedSavedState(
version = CurrentVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import com.tunjid.heron.data.files.FileManager
import com.tunjid.heron.data.files.createFileManager
import com.tunjid.heron.data.network.BlueskyJson
import com.tunjid.heron.data.network.ConnectivityNetworkMonitor
import com.tunjid.heron.data.network.FeedCreationService
import com.tunjid.heron.data.network.GrazeFeedCreationService
import com.tunjid.heron.data.network.KtorNetworkService
import com.tunjid.heron.data.network.NetworkConnectionException
import com.tunjid.heron.data.network.NetworkMonitor
Expand Down Expand Up @@ -242,6 +244,12 @@ class DataBindings(
videoUploadService: SuspendingVideoUploadService,
): VideoUploadService = videoUploadService

@SingleIn(AppScope::class)
@Provides
internal fun provideFeedCreationService(
feedCreationService: GrazeFeedCreationService,
): FeedCreationService = feedCreationService

@SingleIn(AppScope::class)
@Provides
fun providePostDao(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* Copyright 2024 Adetunji Dahunsi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.tunjid.heron.data.network

import com.atproto.server.GetServiceAuthQueryParams
import com.tunjid.heron.data.InternalEndpoints
import com.tunjid.heron.data.core.types.RecordKey
import com.tunjid.heron.data.graze.Filter
import com.tunjid.heron.data.graze.GrazeDid
import com.tunjid.heron.data.graze.GrazeFeed
import com.tunjid.heron.data.logging.LogPriority
import com.tunjid.heron.data.logging.logcat
import com.tunjid.heron.data.logging.loggableText
import com.tunjid.heron.data.repository.SavedStateDataSource
import com.tunjid.heron.data.repository.expiredSessionResult
import com.tunjid.heron.data.repository.inCurrentProfileSession
import com.tunjid.heron.data.utilities.mapCatchingUnlessCancelled
import dev.zacsweers.metro.Inject
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.bearerAuth
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.ktor.http.isSuccess
import io.ktor.http.takeFrom
import kotlin.time.Clock
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import sh.christian.ozone.api.Did
import sh.christian.ozone.api.Nsid

internal interface FeedCreationService {

suspend fun updateGrazeFeed(
update: GrazeFeed.Update,
): Result<GrazeResponse>
}

internal class GrazeFeedCreationService @Inject constructor(
httpClient: HttpClient,
private val networkService: NetworkService,
private val savedStateDataSource: SavedStateDataSource,
) : FeedCreationService {

private val feedCreationClient = httpClient.config {
install(DefaultRequest) {
url.takeFrom(InternalEndpoints.HeronEndpoint)
}
install(Logging) {
level = LogLevel.INFO
logger = object : Logger {
override fun log(message: String) {
logcat(LogPriority.VERBOSE) { message }
}
}
}
install(HttpTimeout) {
requestTimeoutMillis = 15.seconds.inWholeMilliseconds
}
}

override suspend fun updateGrazeFeed(
update: GrazeFeed.Update,
): Result<GrazeResponse> =
when (update) {
is GrazeFeed.Update.Create -> performRequest<GrazeResponse.Created>(
call = GrazeCall.Create,
body = GrazeRequestBody.Feed(update.feed),
)
is GrazeFeed.Update.Delete -> performRequest<GrazeResponse.Deleted>(
call = (GrazeCall.Delete),
body = GrazeRequestBody.Key(
recordKey = update.recordKey,
),
)

is GrazeFeed.Update.Edit -> performRequest<GrazeResponse.Edited>(
call = GrazeCall.Edit,
body = GrazeRequestBody.Feed(update.feed),
)
is GrazeFeed.Update.Get -> performRequest<GrazeResponse.Read>(
call = GrazeCall.Get,
body = GrazeRequestBody.Key(
recordKey = update.recordKey,
),
)
}

private suspend inline fun <reified T : GrazeResponse> performRequest(
call: GrazeCall,
body: GrazeRequestBody,
): Result<GrazeResponse> = savedStateDataSource.inCurrentProfileSession { signedInProfileId ->
if (signedInProfileId == null) return@inCurrentProfileSession expiredSessionResult()

networkService.runCatchingWithMonitoredNetworkRetry {
getServiceAuth(
GetServiceAuthQueryParams(
aud = Did(GrazeDid.id),
exp = Clock.System.now().epochSeconds + 30.minutes.inWholeSeconds,
lxm = call.lexicon,
),
)
}.mapCatchingUnlessCancelled { tokenResponse ->
val response = feedCreationClient.post(call.path) {
setBody(body.body())
contentType(ContentType.Application.Json)
bearerAuth(tokenResponse.token)
}

if (!response.status.isSuccess()) when (response.status) {
HttpStatusCode.NotFound -> return@mapCatchingUnlessCancelled GrazeResponse.Deleted(
rkey = body.recordKey,
)
else -> throw Exception(response.bodyAsText())
}
response.body<T>()
}
.onFailure {
logcat(LogPriority.DEBUG) {
"Failed graze call notification for ${call.path}: ${it.loggableText()}"
}
}
} ?: expiredSessionResult()
}

private enum class GrazeCall(
val path: String,
val lexicon: Nsid,
) {
Create(
path = "/createGrazeFeed",
lexicon = Nsid("com.atproto.repo.createRecord"),
),
Edit(
path = "/editGrazeFeed",
lexicon = Nsid("com.atproto.repo.putRecord"),
),
Delete(
path = "/deleteGrazeFeed",
lexicon = Nsid("com.atproto.repo.deleteRecord"),
),
Get(
path = "/getGrazeFeed",
lexicon = Nsid("com.atproto.repo.getRecord"),
),
}

private sealed interface GrazeRequestBody {
val recordKey: RecordKey

fun body(): Any

@Serializable
data class Key(
@SerialName("rkey")
override val recordKey: RecordKey,
) : GrazeRequestBody {
override fun body(): Any = this
}

data class Feed(
val feed: GrazeFeed,
) : GrazeRequestBody {
override val recordKey: RecordKey
get() = feed.recordKey

override fun body(): Any = feed
}
}

@Serializable
internal sealed interface GrazeResponse {

val rkey: RecordKey

@Serializable
data class Deleted(
override val rkey: RecordKey,
) : GrazeResponse

@Serializable
data class Created(
override val rkey: RecordKey,
val contentMode: String,
) : GrazeResponse

@Serializable
data class Edited(
override val rkey: RecordKey,
val contentMode: String,
) : GrazeResponse

@Serializable
data class Read(
override val rkey: RecordKey,
val contentMode: String,
val algorithm: Detail,
) : GrazeResponse {
@Serializable
data class Detail(
val order: String,
val manifest: Manifest,
)

@Serializable
data class Manifest(
val filter: Filter.Root,
)
}
}
Loading
Loading