├── app
├── common
│ ├── .gitignore
│ └── src
│ │ ├── commonMain
│ │ ├── composeResources
│ │ │ └── font
│ │ │ │ ├── Syne.ttf
│ │ │ │ ├── Rubik-Black.ttf
│ │ │ │ ├── Rubik-Bold.ttf
│ │ │ │ ├── Rubik-Light.ttf
│ │ │ │ ├── Rubik-Medium.ttf
│ │ │ │ ├── Rubik-Normal.ttf
│ │ │ │ ├── Rubik-BoldItalic.ttf
│ │ │ │ ├── Rubik-ExtraBold.ttf
│ │ │ │ ├── Rubik-SemiBold.ttf
│ │ │ │ ├── Rubik-BlackItalic.ttf
│ │ │ │ ├── Rubik-LightItalic.ttf
│ │ │ │ ├── Rubik-MediumItalic.ttf
│ │ │ │ ├── Rubik-NormalItalic.ttf
│ │ │ │ ├── Rubik-ExtraBoldItalic.ttf
│ │ │ │ └── Rubik-SemiBoldItalic.ttf
│ │ └── kotlin
│ │ │ └── sh
│ │ │ └── christian
│ │ │ └── ozone
│ │ │ ├── ui
│ │ │ ├── workflow
│ │ │ │ ├── EmptyScreen.kt
│ │ │ │ └── ViewRendering.kt
│ │ │ ├── DynamicDarkMode.kt
│ │ │ └── compose
│ │ │ │ ├── OnBackPressedModifier.kt
│ │ │ │ ├── ForegroundModifier.kt
│ │ │ │ ├── StablePainter.kt
│ │ │ │ ├── urlImagePainter.kt
│ │ │ │ ├── TimeDelta.kt
│ │ │ │ ├── SystemInsets.kt
│ │ │ │ ├── ZoomableImage.kt
│ │ │ │ └── TextOverlayScreen.kt
│ │ │ ├── error
│ │ │ ├── ErrorOutput.kt
│ │ │ ├── ErrorProps.kt
│ │ │ └── ErrorWorkflow.kt
│ │ │ ├── home
│ │ │ ├── HomeOutput.kt
│ │ │ ├── HomeProps.kt
│ │ │ ├── HomeSubDestination.kt
│ │ │ └── HomeState.kt
│ │ │ ├── api
│ │ │ ├── dispatching.kt
│ │ │ ├── ServerRepository.kt
│ │ │ └── NetworkWorker.kt
│ │ │ ├── timeline
│ │ │ ├── TimelineProps.kt
│ │ │ ├── TimelineOutput.kt
│ │ │ ├── components
│ │ │ │ ├── feature
│ │ │ │ │ ├── UnknownPostPost.kt
│ │ │ │ │ ├── InvisiblePostPost.kt
│ │ │ │ │ └── BlockedPostPost.kt
│ │ │ │ └── PostDate.kt
│ │ │ └── TimelineState.kt
│ │ │ ├── settings
│ │ │ ├── SettingsOutput.kt
│ │ │ └── SettingsState.kt
│ │ │ ├── compose
│ │ │ ├── ComposePostOutput.kt
│ │ │ ├── ComposePostProps.kt
│ │ │ └── ComposePostState.kt
│ │ │ ├── model
│ │ │ ├── Delta.kt
│ │ │ ├── Reference.kt
│ │ │ ├── Label.kt
│ │ │ ├── TimelineReference.kt
│ │ │ ├── LitePost.kt
│ │ │ ├── Timeline.kt
│ │ │ ├── Moment.kt
│ │ │ ├── TimelinePostReply.kt
│ │ │ └── TimelinePostReason.kt
│ │ │ ├── app
│ │ │ ├── AppState.kt
│ │ │ ├── initWorkflow.kt
│ │ │ └── Supervisor.kt
│ │ │ ├── util
│ │ │ ├── time.kt
│ │ │ ├── url.kt
│ │ │ └── json.kt
│ │ │ ├── profile
│ │ │ └── ProfileProps.kt
│ │ │ ├── login
│ │ │ ├── LoginOutput.kt
│ │ │ ├── auth
│ │ │ │ ├── ServerInfo.kt
│ │ │ │ ├── AuthInfo.kt
│ │ │ │ ├── Credentials.kt
│ │ │ │ └── Server.kt
│ │ │ ├── LoginRepository.kt
│ │ │ └── LoginState.kt
│ │ │ ├── notifications
│ │ │ ├── NotificationsOutput.kt
│ │ │ ├── NotificationsState.kt
│ │ │ └── type
│ │ │ │ ├── QuoteRow.kt
│ │ │ │ ├── ReplyRow.kt
│ │ │ │ └── MentionRow.kt
│ │ │ ├── di
│ │ │ └── SingleInApp.kt
│ │ │ ├── user
│ │ │ └── UserReference.kt
│ │ │ └── thread
│ │ │ └── ThreadProps.kt
│ │ ├── jsMain
│ │ └── kotlin
│ │ │ └── sh
│ │ │ └── christian
│ │ │ └── ozone
│ │ │ ├── api
│ │ │ └── dispatching.js.kt
│ │ │ ├── ui
│ │ │ ├── compose
│ │ │ │ ├── OnBackPressedModifier.js.kt
│ │ │ │ └── SystemInsets.js.kt
│ │ │ └── DynamicDarkMode.js.kt
│ │ │ └── util
│ │ │ └── time.js.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── sh
│ │ │ └── christian
│ │ │ └── ozone
│ │ │ ├── api
│ │ │ └── dispatching.jvm.kt
│ │ │ ├── ui
│ │ │ └── compose
│ │ │ │ ├── OnBackPressedModifier.jvm.kt
│ │ │ │ └── SystemInsets.jvm.kt
│ │ │ └── util
│ │ │ └── time.jvm.kt
│ │ ├── androidMain
│ │ └── kotlin
│ │ │ └── sh
│ │ │ └── christian
│ │ │ └── ozone
│ │ │ ├── api
│ │ │ └── dispatching.android.kt
│ │ │ ├── ui
│ │ │ ├── compose
│ │ │ │ └── OnBackPressedModifier.android.kt
│ │ │ └── DynamicDarkMode.android.kt
│ │ │ └── util
│ │ │ └── time.android.kt
│ │ └── iosMain
│ │ └── kotlin
│ │ └── sh
│ │ └── christian
│ │ └── ozone
│ │ ├── ui
│ │ ├── compose
│ │ │ ├── OnBackPressedModifier.ios.kt
│ │ │ └── SystemInsets.ios.kt
│ │ └── DynamicDarkMode.ios.kt
│ │ ├── api
│ │ └── dispatching.ios.kt
│ │ └── util
│ │ └── time.ios.kt
├── ios
│ ├── .gitignore
│ └── build.gradle.kts
├── web
│ ├── .gitignore
│ ├── src
│ │ └── jsMain
│ │ │ └── resources
│ │ │ ├── styles.css
│ │ │ └── index.html
│ ├── webpack.config.d
│ │ └── serverConfig.js
│ └── build.gradle.kts
├── android
│ ├── .gitignore
│ ├── src
│ │ └── androidMain
│ │ │ ├── ic_launcher-playstore.png
│ │ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── ic_launcher_round.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ └── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── kotlin
│ │ │ └── sh
│ │ │ │ └── christian
│ │ │ │ └── ozone
│ │ │ │ └── StatusBarTheme.kt
│ │ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── desktop
│ ├── .gitignore
│ └── build.gradle.kts
├── store
│ ├── .gitignore
│ ├── src
│ │ ├── jsMain
│ │ │ └── kotlin
│ │ │ │ └── sh
│ │ │ │ └── christian
│ │ │ │ └── ozone
│ │ │ │ └── store
│ │ │ │ └── Storage.js.kt
│ │ ├── iosMain
│ │ │ └── kotlin
│ │ │ │ └── sh
│ │ │ │ └── christian
│ │ │ │ └── ozone
│ │ │ │ └── store
│ │ │ │ └── Storage.ios.kt
│ │ ├── jvmMain
│ │ │ └── kotlin
│ │ │ │ └── sh
│ │ │ │ └── christian
│ │ │ │ └── ozone
│ │ │ │ └── store
│ │ │ │ └── Storage.jvm.kt
│ │ ├── androidMain
│ │ │ └── kotlin
│ │ │ │ └── sh
│ │ │ │ └── christian
│ │ │ │ └── ozone
│ │ │ │ └── store
│ │ │ │ └── Storage.android.kt
│ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── sh
│ │ │ └── christian
│ │ │ └── ozone
│ │ │ └── store
│ │ │ ├── Preference.kt
│ │ │ └── PersistentStorage.kt
│ └── build.gradle.kts
└── iosApp
│ ├── ozone
│ ├── Preview Content
│ │ └── .keep
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── launcher.png
│ │ │ └── Contents.json
│ │ └── AccentColor.colorset
│ │ │ └── Contents.json
│ ├── Info.plist
│ ├── ozoneApp.swift
│ └── ContentView.swift
│ ├── ozone.xcodeproj
│ └── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── .gitignore
├── oauth
├── .gitignore
├── gradle.properties
└── src
│ └── commonMain
│ └── kotlin
│ └── sh
│ └── christian
│ └── ozone
│ └── oauth
│ ├── base64url.kt
│ ├── network
│ ├── OAuthParResponse.kt
│ ├── OAuthTokenResponse.kt
│ └── OAuthParRequest.kt
│ ├── OAuthCodeChallengeMethodSelector.kt
│ ├── OAuthClient.kt
│ └── OAuthAuthorizationRequest.kt
├── bluesky
├── .gitignore
├── gradle.properties
└── src
│ └── commonMain
│ └── kotlin
│ └── sh
│ └── christian
│ └── ozone
│ └── BlueskyJson.kt
├── jetstream
├── .gitignore
├── gradle.properties
└── src
│ ├── jsMain
│ ├── resources
│ │ └── files
│ │ │ └── zstd_dictionary.bin
│ └── kotlin
│ │ └── sh
│ │ └── christian
│ │ └── ozone
│ │ └── jetstream
│ │ ├── ZstdCodec.kt
│ │ └── strings.kt
│ ├── jvmMain
│ ├── resources
│ │ └── files
│ │ │ └── zstd_dictionary.bin
│ └── kotlin
│ │ └── sh
│ │ └── christian
│ │ └── ozone
│ │ └── jetstream
│ │ └── zstd.jvm.kt
│ └── commonMain
│ └── kotlin
│ └── sh
│ └── christian
│ └── ozone
│ └── jetstream
│ ├── zstd.kt
│ └── JetstreamHost.kt
├── lexicons
├── .gitignore
├── gradle.properties
├── build.gradle.kts
└── schemas
│ ├── com
│ └── atproto
│ │ ├── sync
│ │ ├── defs.json
│ │ ├── getCheckout.json
│ │ ├── requestCrawl.json
│ │ ├── getHead.json
│ │ └── notifyOfUpdate.json
│ │ ├── server
│ │ ├── deleteSession.json
│ │ ├── requestAccountDelete.json
│ │ ├── requestEmailConfirmation.json
│ │ ├── activateAccount.json
│ │ ├── revokeAppPassword.json
│ │ ├── requestPasswordReset.json
│ │ ├── requestEmailUpdate.json
│ │ ├── resetPassword.json
│ │ ├── deleteAccount.json
│ │ ├── confirmEmail.json
│ │ ├── createInviteCode.json
│ │ ├── deactivateAccount.json
│ │ ├── updateEmail.json
│ │ └── listAppPasswords.json
│ │ ├── identity
│ │ ├── requestPlcOperationSignature.json
│ │ ├── submitPlcOperation.json
│ │ ├── defs.json
│ │ ├── updateHandle.json
│ │ └── getRecommendedDidCredentials.json
│ │ ├── repo
│ │ ├── defs.json
│ │ ├── importRepo.json
│ │ ├── strongRef.json
│ │ └── uploadBlob.json
│ │ ├── admin
│ │ ├── deleteAccount.json
│ │ ├── updateAccountPassword.json
│ │ ├── updateAccountHandle.json
│ │ ├── getAccountInfo.json
│ │ ├── enableAccountInvites.json
│ │ ├── disableInviteCodes.json
│ │ ├── updateAccountEmail.json
│ │ ├── disableAccountInvites.json
│ │ ├── updateAccountSigningKey.json
│ │ └── getAccountInfos.json
│ │ ├── temp
│ │ ├── requestPhoneVerification.json
│ │ ├── checkSignupQueue.json
│ │ ├── revokeAccountCredentials.json
│ │ ├── addReservedHandle.json
│ │ └── fetchLabels.json
│ │ └── lexicon
│ │ └── schema.json
│ ├── chat
│ └── bsky
│ │ ├── actor
│ │ ├── exportAccountData.json
│ │ ├── deleteAccount.json
│ │ └── declaration.json
│ │ ├── moderation
│ │ └── updateActorAccess.json
│ │ └── convo
│ │ ├── getConvo.json
│ │ ├── deleteMessageForSelf.json
│ │ ├── leaveConvo.json
│ │ ├── sendMessage.json
│ │ ├── muteConvo.json
│ │ ├── unmuteConvo.json
│ │ ├── acceptConvo.json
│ │ ├── updateRead.json
│ │ ├── updateAllRead.json
│ │ └── getConvoForMembers.json
│ ├── tools
│ └── ozone
│ │ ├── signature
│ │ ├── defs.json
│ │ └── findCorrelation.json
│ │ ├── communication
│ │ ├── deleteTemplate.json
│ │ └── listTemplates.json
│ │ ├── set
│ │ ├── upsertSet.json
│ │ ├── addValues.json
│ │ └── deleteSet.json
│ │ ├── moderation
│ │ ├── getEvent.json
│ │ ├── getRepo.json
│ │ ├── getRecord.json
│ │ ├── getSubjects.json
│ │ └── getReporterStats.json
│ │ ├── team
│ │ └── deleteMember.json
│ │ └── setting
│ │ └── removeOptions.json
│ └── app
│ └── bsky
│ ├── ageassurance
│ └── getConfig.json
│ ├── embed
│ └── defs.json
│ ├── graph
│ ├── unmuteThread.json
│ ├── unmuteActor.json
│ ├── unmuteActorList.json
│ ├── muteActor.json
│ ├── muteActorList.json
│ ├── muteThread.json
│ ├── follow.json
│ ├── listblock.json
│ ├── block.json
│ ├── listitem.json
│ ├── getStarterPack.json
│ ├── getStarterPacks.json
│ ├── getListBlocks.json
│ └── getListMutes.json
│ ├── notification
│ ├── putPreferences.json
│ ├── updateSeen.json
│ ├── getPreferences.json
│ ├── declaration.json
│ ├── getUnreadCount.json
│ ├── unregisterPush.json
│ └── registerPush.json
│ ├── unspecced
│ ├── getAgeAssuranceState.json
│ ├── getTrends.json
│ ├── getSuggestedFeeds.json
│ ├── getSuggestedStarterPacks.json
│ ├── getConfig.json
│ └── getOnboardingSuggestedStarterPacks.json
│ ├── actor
│ ├── putPreferences.json
│ ├── getProfile.json
│ ├── getPreferences.json
│ └── getProfiles.json
│ ├── feed
│ ├── like.json
│ ├── repost.json
│ ├── sendInteractions.json
│ ├── getFeedGenerators.json
│ └── getPosts.json
│ ├── video
│ ├── uploadVideo.json
│ ├── getUploadLimits.json
│ ├── getJobStatus.json
│ └── defs.json
│ ├── bookmark
│ ├── deleteBookmark.json
│ └── createBookmark.json
│ └── contact
│ └── removeData.json
├── api-gen-runtime
├── .gitignore
├── gradle.properties
├── src
│ ├── jsMain
│ │ └── kotlin
│ │ │ └── sh
│ │ │ └── christian
│ │ │ └── ozone
│ │ │ └── api
│ │ │ └── xrpc
│ │ │ └── defaultHttpEngine.js.kt
│ ├── jvmMain
│ │ └── kotlin
│ │ │ └── sh
│ │ │ └── christian
│ │ │ └── ozone
│ │ │ └── api
│ │ │ └── xrpc
│ │ │ └── defaultHttpEngine.jvm.kt
│ ├── iosMain
│ │ └── kotlin
│ │ │ └── sh
│ │ │ └── christian
│ │ │ └── ozone
│ │ │ └── api
│ │ │ └── xrpc
│ │ │ └── defaultHttpEngine.ios.kt
│ └── commonMain
│ │ └── kotlin
│ │ └── sh
│ │ └── christian
│ │ └── ozone
│ │ └── api
│ │ ├── xrpc
│ │ ├── defaultHttpEngine.kt
│ │ ├── XrpcDefaults.kt
│ │ └── defaultHttpClient.kt
│ │ ├── model
│ │ ├── AtpEnum.kt
│ │ ├── Timestamp.kt
│ │ └── JsonContent.kt
│ │ ├── response
│ │ ├── AtpException.kt
│ │ └── AtpErrorDescription.kt
│ │ └── runtime
│ │ ├── buildXrpcJsonConfiguration.kt
│ │ ├── JsonContentSerializer.kt
│ │ ├── BlobSerializer.kt
│ │ └── AtIdentifierSerializer.kt
└── build.gradle.kts
├── generator
├── .gitignore
├── gradle.properties
├── src
│ └── main
│ │ └── kotlin
│ │ └── sh
│ │ └── christian
│ │ └── ozone
│ │ └── api
│ │ └── generator
│ │ ├── DefaultsConfiguration.kt
│ │ ├── builder
│ │ ├── SealedRelationship.kt
│ │ ├── TypesGenerator.kt
│ │ ├── enums.kt
│ │ └── Requirement.kt
│ │ ├── ApiConfiguration.kt
│ │ └── ApiReturnType.kt
└── settings.gradle.kts
├── api-gen-runtime-internal
├── .gitignore
├── gradle.properties
├── src
│ └── commonMain
│ │ └── kotlin
│ │ └── sh
│ │ └── christian
│ │ └── ozone
│ │ └── api
│ │ └── xrpc
│ │ ├── ChildSerializerLocator.kt
│ │ ├── XrpcSubscriptionFrame.kt
│ │ └── XrpcSubscriptionParseException.kt
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── .gitignore
├── RELEASING.md
├── scripts
├── release.sh
└── bump_version.sh
└── settings.gradle.kts
/app/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
--------------------------------------------------------------------------------
/app/ios/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
--------------------------------------------------------------------------------
/app/web/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
--------------------------------------------------------------------------------
/oauth/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 |
--------------------------------------------------------------------------------
/app/android/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
--------------------------------------------------------------------------------
/app/desktop/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
--------------------------------------------------------------------------------
/app/store/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 |
--------------------------------------------------------------------------------
/bluesky/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 |
--------------------------------------------------------------------------------
/jetstream/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 |
--------------------------------------------------------------------------------
/lexicons/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 |
--------------------------------------------------------------------------------
/api-gen-runtime/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 |
--------------------------------------------------------------------------------
/app/iosApp/ozone/Preview Content/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/generator/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /.kotlin/
--------------------------------------------------------------------------------
/api-gen-runtime-internal/.gitignore:
--------------------------------------------------------------------------------
1 | /build/
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/lexicons/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=AT Protocol for Kotlin - Lexicons
2 | POM_DESCRIPTION=Lexicon schema source files
3 |
--------------------------------------------------------------------------------
/oauth/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=AT Protocol for Kotlin - OAuth
2 | POM_DESCRIPTION=AT Protocol OAuth API bindings for Kotlin.
--------------------------------------------------------------------------------
/app/iosApp/ozone/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/bluesky/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=AT Protocol for Kotlin - Bluesky API
2 | POM_DESCRIPTION=Bluesky Social API bindings for Kotlin.
3 |
--------------------------------------------------------------------------------
/jetstream/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=AT Protocol for Kotlin - Jetstream API
2 | POM_DESCRIPTION=Jetstream API bindings for Kotlin.
3 |
--------------------------------------------------------------------------------
/app/android/src/androidMain/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ozone
4 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Syne.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Syne.ttf
--------------------------------------------------------------------------------
/jetstream/src/jsMain/resources/files/zstd_dictionary.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/jetstream/src/jsMain/resources/files/zstd_dictionary.bin
--------------------------------------------------------------------------------
/jetstream/src/jvmMain/resources/files/zstd_dictionary.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/jetstream/src/jvmMain/resources/files/zstd_dictionary.bin
--------------------------------------------------------------------------------
/api-gen-runtime/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=AT Protocol for Kotlin Generated API Runtime
2 | POM_DESCRIPTION=Utility classes and methods used by generated AT Protocol classes.
3 |
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-Black.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-Bold.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-Light.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/workflow/EmptyScreen.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.workflow
2 |
3 | object EmptyScreen : ViewRendering by screen({})
4 |
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-Medium.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-Normal.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-Normal.ttf
--------------------------------------------------------------------------------
/app/iosApp/ozone/Assets.xcassets/AppIcon.appiconset/launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/iosApp/ozone/Assets.xcassets/AppIcon.appiconset/launcher.png
--------------------------------------------------------------------------------
/generator/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_GROUP_ID=sh.christian.ozone
2 | POM_VERSION=0.3.3
3 | POM_NAME=
4 | POM_DESCRIPTION=
5 |
6 | kotlin.code.style=official
7 | org.gradle.jvmargs=-Xmx8g
8 |
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-BoldItalic.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-ExtraBold.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-SemiBold.ttf
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FAFAFA
4 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-BlackItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-BlackItalic.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-LightItalic.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-MediumItalic.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-NormalItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-NormalItalic.ttf
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/android/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-ExtraBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-ExtraBoldItalic.ttf
--------------------------------------------------------------------------------
/app/common/src/commonMain/composeResources/font/Rubik-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/christiandeange/ozone/HEAD/app/common/src/commonMain/composeResources/font/Rubik-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/api-gen-runtime-internal/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=AT Protocol for Kotlin Generated API Runtime - Internal
2 | POM_DESCRIPTION=Internal utility classes and methods used by generated AT Protocol classes.
3 |
--------------------------------------------------------------------------------
/lexicons/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ozone-publish")
3 | `java-library`
4 | }
5 |
6 | tasks.jar.configure {
7 | from(fileTree("schemas") {
8 | include("**/*.json")
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/app/web/src/jsMain/resources/styles.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | overflow: hidden;
3 | margin: 0 !important;
4 | padding: 0 !important;
5 | }
6 |
7 | #ComposeTarget {
8 | outline: none;
9 | }
10 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/error/ErrorOutput.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.error
2 |
3 | sealed interface ErrorOutput {
4 | object Retry : ErrorOutput
5 | object Dismiss : ErrorOutput
6 | }
7 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/home/HomeOutput.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.home
2 |
3 | sealed interface HomeOutput {
4 | object SignOut : HomeOutput
5 | object CloseApp : HomeOutput
6 | }
7 |
--------------------------------------------------------------------------------
/app/iosApp/ozone.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/api/dispatching.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 |
5 | expect object OzoneDispatchers {
6 | val IO: CoroutineDispatcher
7 | }
8 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/timeline/TimelineProps.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.timeline
2 |
3 | import sh.christian.ozone.login.auth.AuthInfo
4 |
5 | data class TimelineProps(
6 | val authInfo: AuthInfo,
7 | )
8 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/DynamicDarkMode.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | expect fun DynamicDarkMode(content: @Composable () -> Unit)
7 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/compose/OnBackPressedModifier.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.ui.Modifier
4 |
5 | expect fun Modifier.onBackPressed(handler: () -> Unit): Modifier
6 |
--------------------------------------------------------------------------------
/app/iosApp/ozone/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/settings/SettingsOutput.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.settings
2 |
3 | sealed interface SettingsOutput {
4 | object SignOut : SettingsOutput
5 |
6 | object CloseApp : SettingsOutput
7 | }
8 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/compose/ComposePostOutput.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.compose
2 |
3 | sealed interface ComposePostOutput {
4 | object CreatedPost : ComposePostOutput
5 | object CanceledPost : ComposePostOutput
6 | }
7 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/Delta.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlin.time.Duration
5 |
6 | @Serializable
7 | data class Delta(
8 | val duration: Duration,
9 | )
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/settings/SettingsState.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.settings
2 |
3 | sealed interface SettingsState {
4 | object ShowingSettings : SettingsState
5 |
6 | object ConfirmSignOut : SettingsState
7 | }
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/home/HomeProps.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.home
2 |
3 | import sh.christian.ozone.login.auth.AuthInfo
4 |
5 | data class HomeProps(
6 | val authInfo: AuthInfo,
7 | val unreadNotificationCount: Int,
8 | )
9 |
--------------------------------------------------------------------------------
/app/web/webpack.config.d/serverConfig.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | config.plugins = (config.plugins || []).concat([
4 | new webpack.ProvidePlugin({
5 | process: 'process/browser',
6 | Buffer: ['buffer', 'Buffer']
7 | })
8 | ]);
9 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/Reference.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import sh.christian.ozone.api.AtUri
4 | import sh.christian.ozone.api.Cid
5 |
6 | data class Reference(
7 | val uri: AtUri,
8 | val cid: Cid,
9 | )
10 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/sync/defs.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.sync.defs",
4 | "defs": {
5 | "hostStatus": {
6 | "type": "string",
7 | "knownValues": ["active", "idle", "offline", "throttled", "banned"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/oauth/src/commonMain/kotlin/sh/christian/ozone/oauth/base64url.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.oauth
2 |
3 | import io.ktor.util.encodeBase64
4 |
5 | internal fun ByteArray.encodeBase64Url(): String {
6 | return encodeBase64().trimEnd('=').replace('+', '-').replace('/', '_')
7 | }
8 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/jsMain/kotlin/sh/christian/ozone/api/xrpc/defaultHttpEngine.js.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.xrpc
2 |
3 | import io.ktor.client.engine.HttpClientEngineFactory
4 | import io.ktor.client.engine.js.Js
5 |
6 | actual val defaultHttpEngine: HttpClientEngineFactory<*> = Js
7 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/deleteSession.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.deleteSession",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Delete the current session. Requires auth."
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/jvmMain/kotlin/sh/christian/ozone/api/xrpc/defaultHttpEngine.jvm.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.xrpc
2 |
3 | import io.ktor.client.engine.HttpClientEngineFactory
4 | import io.ktor.client.engine.cio.CIO
5 |
6 | actual val defaultHttpEngine: HttpClientEngineFactory<*> = CIO
7 |
--------------------------------------------------------------------------------
/app/iosApp/ozone/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/actor/exportAccountData.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.actor.exportAccountData",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "output": {
8 | "encoding": "application/jsonl"
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/iosMain/kotlin/sh/christian/ozone/api/xrpc/defaultHttpEngine.ios.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.xrpc
2 |
3 | import io.ktor.client.engine.HttpClientEngineFactory
4 | import io.ktor.client.engine.darwin.Darwin
5 |
6 | actual val defaultHttpEngine: HttpClientEngineFactory<*> = Darwin
7 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/requestAccountDelete.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.requestAccountDelete",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Initiate a user account deletion via email."
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/api-gen-runtime-internal/src/commonMain/kotlin/sh/christian/ozone/api/xrpc/ChildSerializerLocator.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.xrpc
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlin.reflect.KClass
5 |
6 | typealias SubscriptionSerializerProvider = (KClass, String) -> KSerializer?
7 |
--------------------------------------------------------------------------------
/api-gen-runtime-internal/src/commonMain/kotlin/sh/christian/ozone/api/xrpc/XrpcSubscriptionFrame.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.xrpc
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | internal data class XrpcSubscriptionFrame(
7 | val op: Int,
8 | val t: String?,
9 | )
10 |
--------------------------------------------------------------------------------
/app/common/src/jsMain/kotlin/sh/christian/ozone/api/dispatching.js.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | actual object OzoneDispatchers {
7 | actual val IO: CoroutineDispatcher get() = Dispatchers.Default
8 | }
9 |
--------------------------------------------------------------------------------
/app/common/src/jvmMain/kotlin/sh/christian/ozone/api/dispatching.jvm.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | actual object OzoneDispatchers {
7 | actual val IO: CoroutineDispatcher get() = Dispatchers.IO
8 | }
9 |
--------------------------------------------------------------------------------
/app/common/src/androidMain/kotlin/sh/christian/ozone/api/dispatching.android.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | actual object OzoneDispatchers {
7 | actual val IO: CoroutineDispatcher get() = Dispatchers.IO
8 | }
9 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/xrpc/defaultHttpEngine.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.xrpc
2 |
3 | import io.ktor.client.engine.HttpClientEngineFactory
4 |
5 | /**
6 | * Platform-default HTTP engine for network requests.
7 | */
8 | expect val defaultHttpEngine: HttpClientEngineFactory<*>
9 |
--------------------------------------------------------------------------------
/generator/src/main/kotlin/sh/christian/ozone/api/generator/DefaultsConfiguration.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.generator
2 |
3 | import java.io.Serializable
4 |
5 | data class DefaultsConfiguration(
6 | val generateUnknownsForSealedTypes: Boolean,
7 | val generateUnknownsForEnums: Boolean,
8 | ) : Serializable
9 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/app/AppState.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.app
2 |
3 | import sh.christian.ozone.home.HomeProps
4 |
5 | sealed interface AppState {
6 | object ShowingLogin : AppState
7 |
8 | data class ShowingLoggedIn(
9 | val props: HomeProps,
10 | ) : AppState
11 | }
12 |
--------------------------------------------------------------------------------
/app/iosApp/ozone/ozoneApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import OzoneIos
3 |
4 | @main
5 | struct ozoneApp: App {
6 | init() {
7 | OzoneIos.MainViewControllerKt.initialize()
8 | }
9 |
10 | var body: some Scene {
11 | WindowGroup {
12 | ContentView()
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/util/time.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.util
2 |
3 | import androidx.compose.runtime.Composable
4 | import sh.christian.ozone.model.Moment
5 |
6 | @Composable
7 | expect fun Moment.formatDate(): String
8 |
9 | @Composable
10 | expect fun Moment.formatTime(): String
11 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/requestEmailConfirmation.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.requestEmailConfirmation",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Request an email with a code to confirm ownership of email."
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/jetstream/src/commonMain/kotlin/sh/christian/ozone/jetstream/zstd.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.jetstream
2 |
3 | internal expect suspend fun initZstd()
4 |
5 | /**
6 | * Decompress a Zstandard-compressed byte array and return the decompressed data.
7 | */
8 | internal expect suspend fun decompressZstd(data: ByteArray): ByteArray?
9 |
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/profile/ProfileProps.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.profile
2 |
3 | import sh.christian.ozone.model.FullProfile
4 | import sh.christian.ozone.user.UserReference
5 |
6 | data class ProfileProps(
7 | val user: UserReference,
8 | val preloadedProfile: FullProfile? = null,
9 | )
10 |
--------------------------------------------------------------------------------
/app/common/src/iosMain/kotlin/sh/christian/ozone/ui/compose/OnBackPressedModifier.ios.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.composed
5 |
6 | actual fun Modifier.onBackPressed(handler: () -> Unit): Modifier = composed {
7 | // No-op
8 | Modifier
9 | }
10 |
--------------------------------------------------------------------------------
/app/common/src/jsMain/kotlin/sh/christian/ozone/ui/compose/OnBackPressedModifier.js.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.composed
5 |
6 | actual fun Modifier.onBackPressed(handler: () -> Unit): Modifier = composed {
7 | // No-op
8 | Modifier
9 | }
10 |
--------------------------------------------------------------------------------
/app/common/src/jvmMain/kotlin/sh/christian/ozone/ui/compose/OnBackPressedModifier.jvm.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.composed
5 |
6 | actual fun Modifier.onBackPressed(handler: () -> Unit): Modifier = composed {
7 | // No-op
8 | Modifier
9 | }
10 |
--------------------------------------------------------------------------------
/app/iosApp/ozone/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "launcher.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/android/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/login/LoginOutput.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.login
2 |
3 | import sh.christian.ozone.login.auth.AuthInfo
4 |
5 | sealed interface LoginOutput {
6 | object CanceledLogin : LoginOutput
7 |
8 | data class LoggedIn(
9 | val authInfo: AuthInfo,
10 | ) : LoginOutput
11 | }
12 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/model/AtpEnum.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.model
2 |
3 | /**
4 | * Parent interface for all enums in the API, which holds the original enum name.
5 | */
6 | abstract class AtpEnum {
7 | abstract val value: String
8 |
9 | override fun toString(): String = value
10 | }
11 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/util/url.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.util
2 |
3 | import io.ktor.http.URLParserException
4 | import io.ktor.http.Url
5 |
6 | fun String.isUrl(): Boolean {
7 | return try {
8 | Url(this).toString()
9 | true
10 | } catch (e: URLParserException) {
11 | false
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/common/src/iosMain/kotlin/sh/christian/ozone/api/dispatching.ios.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.IO
6 |
7 | actual object OzoneDispatchers {
8 | actual val IO: CoroutineDispatcher get() = Dispatchers.IO
9 | }
10 |
--------------------------------------------------------------------------------
/generator/src/main/kotlin/sh/christian/ozone/api/generator/builder/SealedRelationship.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.generator.builder
2 |
3 | import com.squareup.kotlinpoet.ClassName
4 |
5 | data class SealedRelationship(
6 | val sealedInterface: ClassName,
7 | val childClass: ClassName,
8 | val childClassSerialName: String,
9 | )
10 |
--------------------------------------------------------------------------------
/generator/src/main/kotlin/sh/christian/ozone/api/generator/builder/TypesGenerator.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.generator.builder
2 |
3 | import sh.christian.ozone.api.lexicon.LexiconUserType
4 |
5 | interface TypesGenerator {
6 | fun generateTypes(
7 | context: GeneratorContext,
8 | userType: LexiconUserType,
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/api-gen-runtime-internal/src/commonMain/kotlin/sh/christian/ozone/api/xrpc/XrpcSubscriptionParseException.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.xrpc
2 |
3 | import sh.christian.ozone.api.response.AtpErrorDescription
4 |
5 | class XrpcSubscriptionParseException(
6 | val error: AtpErrorDescription?,
7 | ) : RuntimeException("Subscription result could not be parsed")
8 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/timeline/TimelineOutput.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.timeline
2 |
3 | import sh.christian.ozone.home.HomeSubDestination
4 |
5 | sealed interface TimelineOutput {
6 | data class EnterScreen(
7 | val dest: HomeSubDestination,
8 | ) : TimelineOutput
9 |
10 | object CloseApp : TimelineOutput
11 | }
12 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/login/auth/ServerInfo.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.login.auth
2 |
3 | import sh.christian.ozone.util.ReadOnlyList
4 |
5 | data class ServerInfo(
6 | val inviteCodeRequired: Boolean,
7 | val availableUserDomains: ReadOnlyList,
8 | val privacyPolicy: String?,
9 | val termsOfService: String?,
10 | )
11 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/identity/requestPlcOperationSignature.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.identity.requestPlcOperationSignature",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Request an email with a code to in order to request a signed PLC operation. Requires Auth."
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/generator/src/main/kotlin/sh/christian/ozone/api/generator/builder/enums.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.generator.builder
2 |
3 | import com.squareup.kotlinpoet.ClassName
4 |
5 | data class EnumClass(
6 | val className: ClassName,
7 | val description: String?,
8 | )
9 |
10 | data class EnumEntry(
11 | val name: String,
12 | val description: String?,
13 | )
14 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_GROUP_ID=sh.christian.ozone
2 | POM_VERSION=0.3.3
3 | POM_NAME=
4 | POM_DESCRIPTION=
5 |
6 | kotlin.code.style=official
7 | kotlin.mpp.androidSourceSetLayoutVersion=2
8 | android.useAndroidX=true
9 |
10 | org.gradle.jvmargs=-Xmx8g
11 | org.jetbrains.compose.experimental.jscanvas.enabled=true
12 | org.jetbrains.compose.experimental.uikit.enabled=true
13 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/signature/defs.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.signature.defs",
4 | "defs": {
5 | "sigDetail": {
6 | "type": "object",
7 | "required": ["property", "value"],
8 | "properties": {
9 | "property": { "type": "string" },
10 | "value": { "type": "string" }
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/common/src/jsMain/kotlin/sh/christian/ozone/ui/compose/SystemInsets.js.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.unit.dp
6 |
7 | @Composable
8 | actual fun rememberSystemInsets(): PaddingValues {
9 | return PaddingValues(0.dp)
10 | }
11 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/activateAccount.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.activateAccount",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup."
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/Label.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import kotlinx.serialization.Serializable
4 | import com.atproto.label.Label as AtProtoLabel
5 |
6 | @Serializable
7 | data class Label(
8 | val value: String,
9 | )
10 |
11 | fun AtProtoLabel.toLabel(): Label {
12 | return Label(
13 | value = `val`,
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/notifications/NotificationsOutput.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.notifications
2 |
3 | import sh.christian.ozone.home.HomeSubDestination
4 |
5 | sealed interface NotificationsOutput {
6 | data class EnterScreen(
7 | val dest: HomeSubDestination,
8 | ) : NotificationsOutput
9 |
10 | object CloseApp : NotificationsOutput
11 | }
12 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/compose/ForegroundModifier.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.draw.drawWithContent
5 | import androidx.compose.ui.graphics.Color
6 |
7 | fun Modifier.foreground(color: Color): Modifier = drawWithContent {
8 | drawContent()
9 | drawRect(color)
10 | }
11 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/actor/deleteAccount.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.actor.deleteAccount",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "output": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "properties": {}
12 | }
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/repo/defs.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.repo.defs",
4 | "defs": {
5 | "commitMeta": {
6 | "type": "object",
7 | "required": ["cid", "rev"],
8 | "properties": {
9 | "cid": { "type": "string", "format": "cid" },
10 | "rev": { "type": "string", "format": "tid" }
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Gradle ###
2 | .gradle
3 | !gradle/wrapper/gradle-wrapper.jar
4 | /build/
5 |
6 | ### IntelliJ IDEA ###
7 | .idea/
8 | *.iws
9 | *.iml
10 | *.ipr
11 |
12 | ### VS Code ###
13 | .vscode/
14 |
15 | ### Mac OS ###
16 | .DS_Store
17 | /.build/
18 | xcuserdata/
19 | Package.swift
20 |
21 | ### Project ###
22 | /docs
23 | /.kotlin/
24 | local.properties
25 |
26 | ### CI ###
27 | tmp-atproto/
28 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/login/auth/AuthInfo.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.login.auth
2 |
3 | import kotlinx.serialization.Serializable
4 | import sh.christian.ozone.api.Did
5 | import sh.christian.ozone.api.Handle
6 |
7 | @Serializable
8 | data class AuthInfo(
9 | val accessJwt: String,
10 | val refreshJwt: String,
11 | val handle: Handle,
12 | val did: Did,
13 | )
14 |
--------------------------------------------------------------------------------
/app/store/src/jsMain/kotlin/sh/christian/ozone/store/Storage.js.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.store
2 |
3 | import com.russhwolf.settings.StorageSettings
4 | import kotlinx.browser.localStorage
5 | import sh.christian.ozone.store.settings.SettingsStorage
6 |
7 | fun storage(): PersistentStorage {
8 | val settings = StorageSettings(delegate = localStorage)
9 | return SettingsStorage(settings)
10 | }
11 |
--------------------------------------------------------------------------------
/app/web/src/jsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ozone
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/repo/importRepo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.repo.importRepo",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.",
8 | "input": {
9 | "encoding": "application/vnd.ipld.car"
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/common/src/androidMain/kotlin/sh/christian/ozone/ui/compose/OnBackPressedModifier.android.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.activity.compose.BackHandler
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.composed
6 |
7 | actual fun Modifier.onBackPressed(handler: () -> Unit): Modifier = composed {
8 | BackHandler(onBack = handler)
9 | Modifier
10 | }
11 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/workflow/ViewRendering.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.workflow
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | interface ViewRendering {
6 | @Composable
7 | fun Content()
8 | }
9 |
10 | fun screen(content: @Composable () -> Unit): ViewRendering = object : ViewRendering {
11 | @Composable
12 | override fun Content() = content()
13 | }
14 |
--------------------------------------------------------------------------------
/oauth/src/commonMain/kotlin/sh/christian/ozone/oauth/network/OAuthParResponse.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.oauth.network
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class OAuthParResponse(
8 | @SerialName("request_uri")
9 | val requestUri: String,
10 | @SerialName("expires_in")
11 | val expiresIn: Int,
12 | )
13 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Production Releases
2 |
3 | 1. Make sure the `CHANGELOG.md` is updated with all the latest notable changes.
4 | 2. Run the release script. Any unsaved local changes will be ignored.
5 | ```shell
6 | ./release.sh
7 | ```
8 | 3. Push the commits. A new release will automatically be published on Sonatype.
9 | ```shell
10 | git push && git push --tags
11 | ```
12 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/model/Timestamp.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.model
2 |
3 | import kotlinx.datetime.Instant
4 | import kotlinx.serialization.Serializable
5 | import sh.christian.ozone.api.runtime.LenientInstantIso8601Serializer
6 |
7 | /**
8 | * A specific moment in time.
9 | */
10 | typealias Timestamp = @Serializable(LenientInstantIso8601Serializer::class) Instant
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/di/SingleInApp.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.di
2 |
3 | import me.tatarka.inject.annotations.Scope
4 | import kotlin.annotation.AnnotationTarget.CLASS
5 | import kotlin.annotation.AnnotationTarget.FUNCTION
6 | import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
7 |
8 | @Scope
9 | @Target(CLASS, FUNCTION, PROPERTY_GETTER)
10 | annotation class SingleInApp
11 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/compose/StablePainter.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.runtime.Stable
4 | import androidx.compose.ui.graphics.painter.Painter
5 | import kotlin.jvm.JvmInline
6 |
7 | @Stable
8 | @JvmInline
9 | value class StablePainter(
10 | val painter: Painter,
11 | )
12 |
13 | val Painter.stable: StablePainter
14 | get() = StablePainter(this)
15 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/compose/urlImagePainter.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.painter.Painter
5 | import io.kamel.core.Resource
6 | import io.kamel.image.asyncPainterResource
7 |
8 | @Composable
9 | fun urlImagePainter(url: String): Resource {
10 | return asyncPainterResource(url)
11 | }
12 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/error/ErrorProps.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.error
2 |
3 | import sh.christian.ozone.api.response.AtpResponse
4 |
5 | data class ErrorProps(
6 | val title: String?,
7 | val description: String?,
8 | val retryable: Boolean,
9 | )
10 |
11 | fun AtpResponse.Failure<*>.toErrorProps(retryable: Boolean): ErrorProps? =
12 | error?.let { ErrorProps(it.error, it.message, retryable) }
13 |
--------------------------------------------------------------------------------
/bluesky/src/commonMain/kotlin/sh/christian/ozone/BlueskyJson.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone
2 |
3 | import kotlinx.serialization.json.Json
4 | import sh.christian.ozone.api.runtime.buildXrpcJsonConfiguration
5 | import sh.christian.ozone.api.xrpc.XrpcSerializersModule
6 |
7 | /**
8 | * JSON configuration for serializing and deserializing Bluesky API objects.
9 | */
10 | val BlueskyJson: Json = buildXrpcJsonConfiguration(XrpcSerializersModule)
11 |
--------------------------------------------------------------------------------
/oauth/src/commonMain/kotlin/sh/christian/ozone/oauth/OAuthCodeChallengeMethodSelector.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.oauth
2 |
3 | /**
4 | * Interface for selecting an OAuth code challenge method based on the supported methods provided by the OAuth
5 | * authorization server.
6 | */
7 | fun interface OAuthCodeChallengeMethodSelector {
8 | fun selectCodeChallengeMethod(supportedChallengeMethods: List): OAuthCodeChallengeMethod
9 | }
10 |
--------------------------------------------------------------------------------
/app/store/src/iosMain/kotlin/sh/christian/ozone/store/Storage.ios.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.store
2 |
3 | import com.russhwolf.settings.NSUserDefaultsSettings
4 | import platform.Foundation.NSUserDefaults
5 | import sh.christian.ozone.store.settings.SettingsStorage
6 |
7 | fun storage(): PersistentStorage {
8 | val delegate: NSUserDefaults = NSUserDefaults.standardUserDefaults()
9 | return SettingsStorage(NSUserDefaultsSettings(delegate))
10 | }
11 |
--------------------------------------------------------------------------------
/jetstream/src/commonMain/kotlin/sh/christian/ozone/jetstream/JetstreamHost.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.jetstream
2 |
3 | /**
4 | * Known hosted instances of the Jetstream service.
5 | */
6 | enum class JetstreamHost(
7 | val instance: Int,
8 | val region: String,
9 | ) {
10 | JETSTREAM_1_US_EAST(1, "us-east"),
11 | JETSTREAM_2_US_EAST(2, "us-east"),
12 | JETSTREAM_1_US_WEST(1, "us-west"),
13 | JETSTREAM_2_US_WEST(2, "us-west"),
14 | }
15 |
--------------------------------------------------------------------------------
/app/ios/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ozone-multiplatform")
3 | id("ozone-compose")
4 | }
5 |
6 | ozone {
7 | ios("OzoneIos")
8 | }
9 |
10 | kotlin {
11 | sourceSets {
12 | val iosMain by getting {
13 | dependencies {
14 | implementation(project(":app:common"))
15 | implementation(project(":app:store"))
16 | }
17 |
18 | resources.srcDir("../common/src/commonMain/composeResources")
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/repo/strongRef.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.repo.strongRef",
4 | "description": "A URI with a content-hash fingerprint.",
5 | "defs": {
6 | "main": {
7 | "type": "object",
8 | "required": ["uri", "cid"],
9 | "properties": {
10 | "uri": { "type": "string", "format": "at-uri" },
11 | "cid": { "type": "string", "format": "cid" }
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/TimelineReference.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import com.atproto.repo.StrongRef
4 | import sh.christian.ozone.api.AtUri
5 | import sh.christian.ozone.api.Cid
6 |
7 | data class TimelineReference(
8 | val uri: AtUri,
9 | val cid: Cid,
10 | )
11 |
12 | fun StrongRef.toReference(): TimelineReference {
13 | return TimelineReference(
14 | uri = uri,
15 | cid = cid,
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/response/AtpException.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.response
2 |
3 | /**
4 | * An exception thrown for unsuccessful raw API calls and [Result]-wrapped API calls.
5 | */
6 | class AtpException(
7 | val statusCode: StatusCode,
8 | val error: AtpErrorDescription? = null,
9 | ) : Exception(
10 | "XRPC request failed: ${statusCode::class.simpleName}"
11 | .plus(error?.message?.let { " ($it)" }.orEmpty())
12 | )
13 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/timeline/components/feature/UnknownPostPost.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.timeline.components.feature
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | fun UnknownPostPost(onClick: (() -> Unit)?) {
7 | FeatureContainer(onClick = onClick) {
8 | PostFeatureTextContent(
9 | title = "Unknown post",
10 | description = "This post cannot be viewed.",
11 | uri = null,
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/common/src/iosMain/kotlin/sh/christian/ozone/ui/DynamicDarkMode.ios.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.CompositionLocalProvider
6 |
7 | @Composable
8 | actual fun DynamicDarkMode(content: @Composable () -> Unit) {
9 | CompositionLocalProvider(LocalColorTheme provides ColorTheme(isSystemInDarkTheme())) {
10 | content()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/common/src/androidMain/kotlin/sh/christian/ozone/ui/DynamicDarkMode.android.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.CompositionLocalProvider
6 |
7 | @Composable
8 | actual fun DynamicDarkMode(content: @Composable () -> Unit) {
9 | CompositionLocalProvider(LocalColorTheme provides ColorTheme(isSystemInDarkTheme())) {
10 | content()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/generator/src/main/kotlin/sh/christian/ozone/api/generator/ApiConfiguration.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.generator
2 |
3 | import java.io.Serializable
4 |
5 | data class ApiConfiguration(
6 | val namespace: String,
7 | val packageName: String,
8 | val interfaceName: String,
9 | val implementationName: String?,
10 | val suspending: Boolean,
11 | val returnType: ApiReturnType,
12 | val includeMethods: List,
13 | val excludeMethods: List,
14 | ) : Serializable
15 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/xrpc/XrpcDefaults.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("XrpcDefaults")
2 |
3 | package sh.christian.ozone.api.xrpc
4 |
5 | import io.ktor.http.Url
6 | import kotlin.jvm.JvmField
7 | import kotlin.jvm.JvmName
8 |
9 | /**
10 | * Base URL for Bluesky Social.
11 | */
12 | @JvmField
13 | val BSKY_SOCIAL = Url("https://bsky.social")
14 |
15 | /**
16 | * Base URL for Bluesky Network.
17 | */
18 | @JvmField
19 | val BSKY_NETWORK = Url("https://bsky.network")
20 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/timeline/components/feature/InvisiblePostPost.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.timeline.components.feature
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | fun InvisiblePostPost(onClick: (() -> Unit)?) {
7 | FeatureContainer(onClick = onClick) {
8 | PostFeatureTextContent(
9 | title = "Post not found",
10 | description = "The post may have been deleted.",
11 | uri = null,
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/ageassurance/getConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.ageassurance.getConfig",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Returns Age Assurance configuration for use on the client.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "ref",
12 | "ref": "app.bsky.ageassurance.defs#config"
13 | }
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/timeline/components/PostDate.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.timeline.components
2 |
3 | import androidx.compose.material3.Text
4 | import androidx.compose.runtime.Composable
5 | import sh.christian.ozone.model.Moment
6 | import sh.christian.ozone.util.formatDate
7 | import sh.christian.ozone.util.formatTime
8 |
9 | @Composable
10 | fun PostDate(time: Moment) {
11 | Text(
12 | text = "${time.formatDate()} • ${time.formatTime()}",
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/timeline/components/feature/BlockedPostPost.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.timeline.components.feature
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | fun BlockedPostPost(onClick: (() -> Unit)?) {
7 | FeatureContainer(onClick = onClick) {
8 | PostFeatureTextContent(
9 | title = "Post is blocked",
10 | description = "You are blocked from reading this post.",
11 | uri = null,
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/store/src/jvmMain/kotlin/sh/christian/ozone/store/Storage.jvm.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.store
2 |
3 | import com.russhwolf.settings.PreferencesSettings
4 | import sh.christian.ozone.store.settings.SettingsStorage
5 | import java.util.prefs.Preferences
6 |
7 | fun storage(): PersistentStorage {
8 | val preferences = Preferences.userRoot().node("sh.christian.ozone").node("1").apply { sync() }
9 | val settings = PreferencesSettings(preferences)
10 | return SettingsStorage(settings)
11 | }
12 |
--------------------------------------------------------------------------------
/scripts/release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | git checkout main
6 |
7 | if [[ -n "$(git status --porcelain)" ]]; then
8 | echo "Error: Uncommitted changes present. Please re-run with no local changes." >&2
9 | exit 1
10 | fi
11 |
12 | # Strip leading 'v' if present
13 | NEXT_RELEASE="${1-}"
14 | NEXT_RELEASE="${NEXT_RELEASE#v}"
15 |
16 | ./scripts/bump_version.sh "${NEXT_RELEASE}"
17 |
18 | git commit -am "Releasing v${NEXT_RELEASE}"
19 | git tag "v$NEXT_RELEASE" --force
20 |
--------------------------------------------------------------------------------
/generator/src/main/kotlin/sh/christian/ozone/api/generator/builder/Requirement.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.generator.builder
2 |
3 | sealed interface Requirement {
4 | data class MinValue(
5 | val minValue: Comparable<*>,
6 | ) : Requirement
7 |
8 | data class MaxValue(
9 | val maxValue: Comparable<*>,
10 | ) : Requirement
11 |
12 | data class MinLength(
13 | val minLength: Long,
14 | ) : Requirement
15 |
16 | data class MaxLength(
17 | val maxLength: Long,
18 | ) : Requirement
19 | }
20 |
--------------------------------------------------------------------------------
/app/common/src/jvmMain/kotlin/sh/christian/ozone/ui/compose/SystemInsets.jvm.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.unit.dp
6 | import org.apache.commons.lang3.SystemUtils
7 |
8 | @Composable
9 | actual fun rememberSystemInsets(): PaddingValues {
10 | return if (SystemUtils.IS_OS_MAC_OSX) {
11 | PaddingValues(top = 18.dp)
12 | } else {
13 | PaddingValues(0.dp)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/embed/defs.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.embed.defs",
4 | "defs": {
5 | "aspectRatio": {
6 | "type": "object",
7 | "description": "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit.",
8 | "required": ["width", "height"],
9 | "properties": {
10 | "width": { "type": "integer", "minimum": 1 },
11 | "height": { "type": "integer", "minimum": 1 }
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/revokeAppPassword.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.revokeAppPassword",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Revoke an App Password by name.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["name"],
13 | "properties": {
14 | "name": { "type": "string" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/xrpc/defaultHttpClient.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.xrpc
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.plugins.DefaultRequest
5 | import io.ktor.http.takeFrom
6 |
7 | /**
8 | * Platform-default HTTP client for network requests.
9 | *
10 | * This client is configured to use [Bluesky Social][BSKY_SOCIAL] by default.
11 | */
12 | val defaultHttpClient: HttpClient = HttpClient(defaultHttpEngine) {
13 | install(DefaultRequest) {
14 | url.takeFrom(BSKY_SOCIAL)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/login/auth/Credentials.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.login.auth
2 |
3 | import sh.christian.ozone.api.Handle
4 |
5 | data class Credentials(
6 | val email: String?,
7 | val username: Handle,
8 | val password: String,
9 | val inviteCode: String?,
10 | ) {
11 | override fun toString(): String {
12 | return "Credentials(" +
13 | "email='$email', " +
14 | "username='$username', " +
15 | "password='███', " +
16 | "inviteCode='$inviteCode'" +
17 | ")"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/user/UserReference.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.user
2 |
3 | import sh.christian.ozone.api.Did
4 | import sh.christian.ozone.api.Handle
5 | import kotlin.jvm.JvmInline
6 |
7 | sealed interface UserReference
8 |
9 | @JvmInline
10 | value class UserDid(
11 | val did: Did,
12 | ) : UserReference {
13 | override fun toString(): String = did.did
14 | }
15 |
16 | @JvmInline
17 | value class UserHandle(
18 | val handle: Handle,
19 | ) : UserReference {
20 | override fun toString(): String = handle.handle
21 | }
22 |
--------------------------------------------------------------------------------
/app/iosApp/ozone/ContentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import OzoneIos
3 |
4 | struct ContentView: View {
5 | var body: some View {
6 | ComposeViewControllerToSwiftUI().ignoresSafeArea()
7 | }
8 | }
9 |
10 | struct ComposeViewControllerToSwiftUI: UIViewControllerRepresentable {
11 | func makeUIViewController(context: Context) -> UIViewController {
12 | return OzoneIos.MainViewControllerKt.MainViewController()
13 | }
14 |
15 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/communication/deleteTemplate.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.communication.deleteTemplate",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Delete a communication template.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["id"],
13 | "properties": {
14 | "id": { "type": "string" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/runtime/buildXrpcJsonConfiguration.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.runtime
2 |
3 | import kotlinx.serialization.json.Json
4 | import kotlinx.serialization.modules.SerializersModule
5 |
6 | /**
7 | * JSON configuration for serializing and deserializing lexicon objects with the given module.
8 | */
9 | fun buildXrpcJsonConfiguration(module: SerializersModule = Json.serializersModule): Json = Json {
10 | ignoreUnknownKeys = true
11 | classDiscriminator = "${'$'}type"
12 | serializersModule = module
13 | }
14 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/util/json.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.util
2 |
3 | import kotlinx.serialization.KSerializer
4 | import sh.christian.ozone.BlueskyJson
5 | import sh.christian.ozone.api.model.JsonContent
6 |
7 | fun KSerializer.deserialize(jsonContent: JsonContent): T {
8 | return BlueskyJson.decodeFromString(this, BlueskyJson.encodeToString(jsonContent))
9 | }
10 |
11 | fun KSerializer.serialize(value: T): JsonContent {
12 | return BlueskyJson.decodeFromString(BlueskyJson.encodeToString(this, value))
13 | }
14 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/unmuteThread.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.unmuteThread",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Unmutes the specified thread. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["root"],
13 | "properties": {
14 | "root": { "type": "string", "format": "at-uri" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/deleteAccount.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.deleteAccount",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Delete a user account as an administrator.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["did"],
13 | "properties": {
14 | "did": { "type": "string", "format": "did" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/store/src/androidMain/kotlin/sh/christian/ozone/store/Storage.android.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.store
2 |
3 | import android.content.Context
4 | import android.content.Context.MODE_PRIVATE
5 | import com.russhwolf.settings.SharedPreferencesSettings
6 | import sh.christian.ozone.store.settings.SettingsStorage
7 |
8 | val Context.storage: PersistentStorage
9 | get() {
10 | val sharedPreferences = getSharedPreferences("prefs-storage", MODE_PRIVATE)
11 | val settings = SharedPreferencesSettings(sharedPreferences)
12 | return SettingsStorage(settings)
13 | }
14 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/unmuteActor.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.unmuteActor",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Unmutes the specified account. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["actor"],
13 | "properties": {
14 | "actor": { "type": "string", "format": "at-identifier" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/LitePost.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import app.bsky.feed.Post
4 | import sh.christian.ozone.util.ReadOnlyList
5 | import sh.christian.ozone.util.mapNotNullImmutable
6 |
7 | data class LitePost(
8 | val text: String,
9 | val links: ReadOnlyList,
10 | val createdAt: Moment,
11 | )
12 |
13 | fun Post.toLitePost(): LitePost {
14 | return LitePost(
15 | text = text,
16 | links = facets.mapNotNullImmutable { it.toLinkOrNull() },
17 | createdAt = Moment(createdAt),
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/requestPasswordReset.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.requestPasswordReset",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Initiate a user account password reset via email.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["email"],
13 | "properties": {
14 | "email": { "type": "string" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/unmuteActorList.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.unmuteActorList",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Unmutes the specified list of accounts. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["list"],
13 | "properties": {
14 | "list": { "type": "string", "format": "at-uri" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/requestEmailUpdate.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.requestEmailUpdate",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Request a token in order to update email.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["tokenRequired"],
13 | "properties": {
14 | "tokenRequired": { "type": "boolean" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/login/auth/Server.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.login.auth
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | sealed interface Server {
8 | val host: String
9 |
10 | @Serializable
11 | @SerialName("bluesky-social")
12 | object BlueskySocial : Server {
13 | override val host: String = "https://bsky.social"
14 | }
15 |
16 | @Serializable
17 | @SerialName("custom-server")
18 | data class CustomServer(
19 | override val host: String,
20 | ) : Server
21 | }
22 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/actor/declaration.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.actor.declaration",
4 | "defs": {
5 | "main": {
6 | "type": "record",
7 | "description": "A declaration of a Bluesky chat account.",
8 | "key": "literal:self",
9 | "record": {
10 | "type": "object",
11 | "required": ["allowIncoming"],
12 | "properties": {
13 | "allowIncoming": {
14 | "type": "string",
15 | "knownValues": ["all", "none", "following"]
16 | }
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/store/src/commonMain/kotlin/sh/christian/ozone/store/Preference.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.store
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlin.reflect.KProperty
5 |
6 | interface Preference {
7 | val updates: Flow
8 |
9 | fun get(): T
10 | fun set(value: T)
11 | fun delete()
12 |
13 | operator fun setValue(
14 | thisRef: Any?,
15 | property: KProperty<*>,
16 | value: T,
17 | ) {
18 | set(value)
19 | }
20 |
21 | operator fun getValue(
22 | thisRef: Any?,
23 | property: KProperty<*>,
24 | ): T {
25 | return get()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/notification/putPreferences.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.notification.putPreferences",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Set notification-related preferences for an account. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["priority"],
13 | "properties": {
14 | "priority": { "type": "boolean" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/iosApp/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode Project
2 | **/*.xcodeproj/xcuserdata/
3 | **/*.xcworkspace/xcuserdata/
4 | **/.swiftpm/xcode/xcuserdata/
5 | **/*.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
6 | **/*.xcworkspace/xcshareddata/*.xccheckout
7 | **/*.xcworkspace/xcshareddata/*.xcscmblueprint
8 | **/*.playground/**/timeline.xctimeline
9 | .idea/
10 |
11 | # Build
12 | **/.build/
13 | **/Build/
14 | DerivedData/
15 | *.ipa
16 |
17 | # Carthage
18 | Carthage/
19 |
20 | # CocoaPods
21 | Pods/
22 |
23 | # CSV
24 | *.orig
25 | .svn
26 |
27 | # Other
28 | *~
29 | .DS_Store
30 | *.swp
31 | *.save
32 | ._*
33 | *.bak
34 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/home/HomeSubDestination.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.home
2 |
3 | import sh.christian.ozone.compose.ComposePostProps
4 | import sh.christian.ozone.profile.ProfileProps
5 | import sh.christian.ozone.thread.ThreadProps
6 |
7 | sealed interface HomeSubDestination {
8 | data class GoToProfile(
9 | val props: ProfileProps,
10 | ) : HomeSubDestination
11 |
12 | data class GoToThread(
13 | val props: ThreadProps,
14 | ) : HomeSubDestination
15 |
16 | data class GoToComposePost(
17 | val props: ComposePostProps,
18 | ) : HomeSubDestination
19 | }
20 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/temp/requestPhoneVerification.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.temp.requestPhoneVerification",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Request a verification code to be sent to the supplied phone number",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["phoneNumber"],
13 | "properties": {
14 | "phoneNumber": { "type": "string" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/notifications/NotificationsState.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.notifications
2 |
3 | import sh.christian.ozone.error.ErrorProps
4 | import sh.christian.ozone.model.Notifications
5 |
6 | sealed interface NotificationsState {
7 | val notifications: Notifications
8 |
9 | data class ShowingNotifications(
10 | override val notifications: Notifications,
11 | val isLoading: Boolean,
12 | ) : NotificationsState
13 |
14 | data class ShowingError(
15 | override val notifications: Notifications,
16 | val props: ErrorProps,
17 | ) : NotificationsState
18 | }
19 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/notification/updateSeen.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.notification.updateSeen",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Notify server that the requesting account has seen notifications. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["seenAt"],
13 | "properties": {
14 | "seenAt": { "type": "string", "format": "datetime" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/muteActor.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.muteActor",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Creates a mute relationship for the specified account. Mutes are private in Bluesky. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["actor"],
13 | "properties": {
14 | "actor": { "type": "string", "format": "at-identifier" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/unspecced/getAgeAssuranceState.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.unspecced.getAgeAssuranceState",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Returns the current state of the age assurance process for an account. This is used to check if the user has completed age assurance or if further action is required.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "ref",
12 | "ref": "app.bsky.unspecced.defs#ageAssuranceState"
13 | }
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/Timeline.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import app.bsky.feed.FeedViewPost
4 | import sh.christian.ozone.util.ReadOnlyList
5 | import sh.christian.ozone.util.mapImmutable
6 |
7 | data class Timeline(
8 | val posts: ReadOnlyList,
9 | val cursor: String?,
10 | ) {
11 | companion object {
12 | fun from(
13 | posts: List,
14 | cursor: String?,
15 | ): Timeline {
16 | return Timeline(
17 | posts = posts.mapImmutable { it.toPost() },
18 | cursor = cursor,
19 | )
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/moderation/updateActorAccess.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.moderation.updateActorAccess",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "required": ["actor", "allowAccess"],
12 | "properties": {
13 | "actor": { "type": "string", "format": "did" },
14 | "allowAccess": { "type": "boolean" },
15 | "ref": { "type": "string" }
16 | }
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/muteActorList.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.muteActorList",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["list"],
13 | "properties": {
14 | "list": { "type": "string", "format": "at-uri" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/oauth/src/commonMain/kotlin/sh/christian/ozone/oauth/OAuthClient.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.oauth
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents an OAuth client with its client ID and redirect URI.
7 | *
8 | * @param clientId The unique identifier for the OAuth client.
9 | * @param redirectUri The URI to which the authorization server will redirect the user after authorization.
10 | * This URI must match one of the redirect URIs registered for the client.
11 | */
12 | @Serializable
13 | data class OAuthClient(
14 | val clientId: String,
15 | val redirectUri: String,
16 | )
17 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/muteThread.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.muteThread",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Mutes a thread preventing notifications from the thread and any of its children. Mutes are private in Bluesky. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["root"],
13 | "properties": {
14 | "root": { "type": "string", "format": "at-uri" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/set/upsertSet.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.set.upsertSet",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Create or update set metadata",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "ref",
12 | "ref": "tools.ozone.set.defs#set"
13 | }
14 | },
15 | "output": {
16 | "encoding": "application/json",
17 | "schema": {
18 | "type": "ref",
19 | "ref": "tools.ozone.set.defs#setView"
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/jetstream/src/jsMain/kotlin/sh/christian/ozone/jetstream/ZstdCodec.kt:
--------------------------------------------------------------------------------
1 | @file:JsModule("zstd-codec")
2 | @file:JsNonModule
3 |
4 | package sh.christian.ozone.jetstream
5 |
6 | internal external object ZstdCodec {
7 | fun run(callback: (Zstd) -> Unit)
8 | }
9 |
10 | internal external class Zstd {
11 | class Simple {
12 | fun compressUsingDict(data: ByteArray, dict: Dict.Compression): ByteArray
13 | fun decompressUsingDict(data: ByteArray, dict: Dict.Decompression): ByteArray?
14 | }
15 |
16 | class Dict {
17 | class Compression(dictBytes: ByteArray, compressionLevel: Int)
18 | class Decompression(dictBytes: ByteArray)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/updateAccountPassword.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.updateAccountPassword",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Update the password for a user account as an administrator.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["did", "password"],
13 | "properties": {
14 | "did": { "type": "string", "format": "did" },
15 | "password": { "type": "string" }
16 | }
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/actor/putPreferences.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.actor.putPreferences",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Set the private preferences attached to the account.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["preferences"],
13 | "properties": {
14 | "preferences": {
15 | "type": "ref",
16 | "ref": "app.bsky.actor.defs#preferences"
17 | }
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/updateAccountHandle.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.updateAccountHandle",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Administrative action to update an account's handle.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["did", "handle"],
13 | "properties": {
14 | "did": { "type": "string", "format": "did" },
15 | "handle": { "type": "string", "format": "handle" }
16 | }
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/feed/like.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.feed.like",
4 | "defs": {
5 | "main": {
6 | "type": "record",
7 | "description": "Record declaring a 'like' of a piece of subject content.",
8 | "key": "tid",
9 | "record": {
10 | "type": "object",
11 | "required": ["subject", "createdAt"],
12 | "properties": {
13 | "subject": { "type": "ref", "ref": "com.atproto.repo.strongRef" },
14 | "createdAt": { "type": "string", "format": "datetime" },
15 | "via": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/getAccountInfo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.getAccountInfo",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get details about an account.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["did"],
11 | "properties": {
12 | "did": { "type": "string", "format": "did" }
13 | }
14 | },
15 | "output": {
16 | "encoding": "application/json",
17 | "schema": {
18 | "type": "ref",
19 | "ref": "com.atproto.admin.defs#accountView"
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/moderation/getEvent.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.moderation.getEvent",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get details about a moderation event.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["id"],
11 | "properties": {
12 | "id": { "type": "integer" }
13 | }
14 | },
15 | "output": {
16 | "encoding": "application/json",
17 | "schema": {
18 | "type": "ref",
19 | "ref": "tools.ozone.moderation.defs#modEventViewDetail"
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/feed/repost.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.feed.repost",
4 | "defs": {
5 | "main": {
6 | "description": "Record representing a 'repost' of an existing Bluesky post.",
7 | "type": "record",
8 | "key": "tid",
9 | "record": {
10 | "type": "object",
11 | "required": ["subject", "createdAt"],
12 | "properties": {
13 | "subject": { "type": "ref", "ref": "com.atproto.repo.strongRef" },
14 | "createdAt": { "type": "string", "format": "datetime" },
15 | "via": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/sync/getCheckout.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.sync.getCheckout",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "DEPRECATED - please use com.atproto.sync.getRepo instead",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["did"],
11 | "properties": {
12 | "did": {
13 | "type": "string",
14 | "format": "did",
15 | "description": "The DID of the repo."
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/vnd.ipld.car"
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/temp/checkSignupQueue.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.temp.checkSignupQueue",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Check accounts location in signup queue.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["activated"],
13 | "properties": {
14 | "activated": { "type": "boolean" },
15 | "placeInQueue": { "type": "integer" },
16 | "estimatedTimeMs": { "type": "integer" }
17 | }
18 | }
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/store/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ozone-multiplatform")
3 | kotlin("plugin.serialization")
4 | }
5 |
6 | ozone {
7 | androidLibrary {
8 | namespace = "sh.christian.ozone.store"
9 | }
10 | js()
11 | jvm()
12 | ios("OzoneStore")
13 | }
14 |
15 | kotlin {
16 | sourceSets {
17 | val commonMain by getting {
18 | dependencies {
19 | api(libs.kotlinx.coroutines.core)
20 |
21 | implementation(libs.kotlinx.serialization.json)
22 | implementation(libs.multiplatform.settings)
23 | implementation(libs.multiplatform.settings.serialization)
24 | implementation(kotlin("reflect"))
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/web/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("plugin.serialization")
3 | id("ozone-multiplatform")
4 | id("ozone-compose")
5 | }
6 |
7 | ozone {
8 | js()
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | val jsMain by getting {
14 | dependencies {
15 | implementation(compose.html.core)
16 |
17 | implementation(project(":app:common"))
18 | implementation(project(":app:store"))
19 | implementation(project(":bluesky"))
20 |
21 | implementation(npm("process", "0.11.10"))
22 | implementation(npm("url", "0.11.0"))
23 | }
24 |
25 | resources.srcDir("../common/src/commonMain/composeResources")
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/jetstream/src/jvmMain/kotlin/sh/christian/ozone/jetstream/zstd.jvm.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.jetstream
2 |
3 | import com.github.luben.zstd.Zstd
4 | import com.github.luben.zstd.ZstdDictDecompress
5 |
6 | private lateinit var dictionary: ZstdDictDecompress
7 |
8 | internal actual suspend fun initZstd() {
9 | if (!::dictionary.isInitialized) {
10 | val dictionaryFile = JetstreamApi::class.java.getResourceAsStream("/files/zstd_dictionary.bin")!!
11 | dictionary = ZstdDictDecompress(dictionaryFile.readBytes())
12 | }
13 | }
14 |
15 | internal actual suspend fun decompressZstd(data: ByteArray): ByteArray? {
16 | return Zstd.decompress(data, dictionary, 10 * 1024 * 1024)
17 | }
18 |
--------------------------------------------------------------------------------
/oauth/src/commonMain/kotlin/sh/christian/ozone/oauth/network/OAuthTokenResponse.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.oauth.network
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 | import sh.christian.ozone.api.Did
6 |
7 | @Serializable
8 | internal data class OAuthTokenResponse(
9 | @SerialName("access_token")
10 | val accessToken: String,
11 | @SerialName("token_type")
12 | val tokenType: String,
13 | @SerialName("expires_in")
14 | val expiresInSeconds: Int,
15 | @SerialName("refresh_token")
16 | val refreshToken: String,
17 | @SerialName("scope")
18 | val scopes: String,
19 | @SerialName("sub")
20 | val subject: Did,
21 | )
22 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/identity/submitPlcOperation.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.identity.submitPlcOperation",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Validates a PLC operation to ensure that it doesn't violate a service's constraints or get the identity into a bad state, then submits it to the PLC registry",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["operation"],
13 | "properties": {
14 | "operation": { "type": "unknown" }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/generator/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 |
9 | @Suppress("UnstableApiUsage")
10 | dependencyResolutionManagement {
11 | versionCatalogs {
12 | create("libs") {
13 | from(files("../gradle/libs.versions.toml"))
14 | }
15 | }
16 |
17 | repositories {
18 | google()
19 | mavenCentral()
20 | }
21 | }
22 |
23 | plugins {
24 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
25 | }
26 |
27 | rootProject.name = "generator"
28 |
29 | include(":api-gen-runtime")
30 | include(":api-gen-runtime-internal")
31 |
32 | includeBuild("../build-logic")
33 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/resetPassword.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.resetPassword",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Reset a user account password using a token.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["token", "password"],
13 | "properties": {
14 | "token": { "type": "string" },
15 | "password": { "type": "string" }
16 | }
17 | }
18 | },
19 | "errors": [{ "name": "ExpiredToken" }, { "name": "InvalidToken" }]
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/compose/ComposePostProps.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.compose
2 |
3 | import sh.christian.ozone.model.Profile
4 | import sh.christian.ozone.model.Reference
5 | import sh.christian.ozone.model.TimelinePost
6 |
7 | data class ComposePostProps(
8 | val replyTo: PostReplyInfo? = null,
9 | )
10 |
11 | data class PostReplyInfo(
12 | val parent: Reference,
13 | val root: Reference,
14 | val parentAuthor: Profile,
15 | )
16 |
17 | fun TimelinePost.asReplyInfo(): PostReplyInfo {
18 | return PostReplyInfo(
19 | parent = Reference(uri, cid),
20 | root = (reply?.root ?: this).let { Reference(it.uri, it.cid) },
21 | parentAuthor = author,
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/compose/ComposePostState.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.compose
2 |
3 | import sh.christian.ozone.error.ErrorProps
4 | import sh.christian.ozone.model.Profile
5 |
6 | sealed interface ComposePostState {
7 | val myProfile: Profile
8 |
9 | data class ComposingPost(
10 | override val myProfile: Profile,
11 | ) : ComposePostState
12 |
13 | data class CreatingPost(
14 | override val myProfile: Profile,
15 | val postPayload: PostPayload,
16 | ) : ComposePostState
17 |
18 | data class ShowingError(
19 | override val myProfile: Profile,
20 | val errorProps: ErrorProps,
21 | val postPayload: PostPayload,
22 | ) : ComposePostState
23 | }
24 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/runtime/JsonContentSerializer.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
2 |
3 | package sh.christian.ozone.api.runtime
4 |
5 | import kotlinx.serialization.KSerializer
6 | import kotlinx.serialization.descriptors.SerialDescriptor
7 | import kotlinx.serialization.encoding.Decoder
8 | import kotlinx.serialization.encoding.Encoder
9 | import sh.christian.ozone.api.model.JsonContent
10 |
11 | expect object JsonContentSerializer : KSerializer {
12 | override val descriptor: SerialDescriptor
13 | override fun deserialize(decoder: Decoder): JsonContent
14 | override fun serialize(encoder: Encoder, value: JsonContent)
15 | }
16 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/api/ServerRepository.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import me.tatarka.inject.annotations.Inject
5 | import sh.christian.ozone.di.SingleInApp
6 | import sh.christian.ozone.login.auth.Server
7 | import sh.christian.ozone.store.PersistentStorage
8 | import sh.christian.ozone.store.preference
9 |
10 | @Inject
11 | @SingleInApp
12 | class ServerRepository(
13 | storage: PersistentStorage,
14 | ) {
15 | private val serverPreference = storage.preference("servers", Server.BlueskySocial)
16 |
17 | var server: Server by serverPreference
18 |
19 | fun serverFlow(): Flow = serverPreference.updates
20 | }
21 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/login/LoginRepository.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.login
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import me.tatarka.inject.annotations.Inject
5 | import sh.christian.ozone.di.SingleInApp
6 | import sh.christian.ozone.login.auth.AuthInfo
7 | import sh.christian.ozone.store.PersistentStorage
8 | import sh.christian.ozone.store.nullablePreference
9 |
10 | @Inject
11 | @SingleInApp
12 | class LoginRepository(
13 | storage: PersistentStorage,
14 | ) {
15 | private val authPreference = storage.nullablePreference("auth-info", null)
16 |
17 | var auth: AuthInfo? by authPreference
18 |
19 | fun authFlow(): Flow = authPreference.updates
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/follow.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.follow",
4 | "defs": {
5 | "main": {
6 | "type": "record",
7 | "description": "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView.",
8 | "key": "tid",
9 | "record": {
10 | "type": "object",
11 | "required": ["subject", "createdAt"],
12 | "properties": {
13 | "subject": { "type": "string", "format": "did" },
14 | "createdAt": { "type": "string", "format": "datetime" },
15 | "via": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
16 | }
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/temp/revokeAccountCredentials.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.temp.revokeAccountCredentials",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Revoke sessions, password, and app passwords associated with account. May be resolved by a password reset.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["account"],
13 | "properties": {
14 | "account": {
15 | "type": "string",
16 | "format": "at-identifier"
17 | }
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/generator/src/main/kotlin/sh/christian/ozone/api/generator/ApiReturnType.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.generator
2 |
3 | import java.io.Serializable
4 |
5 | sealed interface ApiReturnType : Serializable {
6 | /** Returns the raw data model type, throwing an exception if the XRPC call failed. */
7 | object Raw : ApiReturnType {
8 | private fun readResolve(): Any = Raw
9 | }
10 |
11 | /** Returns a `Result` wrapping the data model type. */
12 | object Result : ApiReturnType {
13 | private fun readResolve(): Any = Result
14 | }
15 |
16 | /** Returns an `AtpResponse` wrapping the data model type. */
17 | object Response : ApiReturnType {
18 | private fun readResolve(): Any = Response
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/video/uploadVideo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.video.uploadVideo",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Upload a video to be processed then stored on the PDS.",
8 | "input": {
9 | "encoding": "video/mp4"
10 | },
11 | "output": {
12 | "encoding": "application/json",
13 | "schema": {
14 | "type": "object",
15 | "required": ["jobStatus"],
16 | "properties": {
17 | "jobStatus": {
18 | "type": "ref",
19 | "ref": "app.bsky.video.defs#jobStatus"
20 | }
21 | }
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/moderation/getRepo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.moderation.getRepo",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get details about a repository.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["did"],
11 | "properties": {
12 | "did": { "type": "string", "format": "did" }
13 | }
14 | },
15 | "output": {
16 | "encoding": "application/json",
17 | "schema": {
18 | "type": "ref",
19 | "ref": "tools.ozone.moderation.defs#repoViewDetail"
20 | }
21 | },
22 | "errors": [{ "name": "RepoNotFound" }]
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/app/initWorkflow.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.app
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.SupervisorJob
5 | import kotlinx.coroutines.launch
6 | import sh.christian.ozone.di.AppComponent
7 | import sh.christian.ozone.di.create
8 | import sh.christian.ozone.store.PersistentStorage
9 |
10 | fun initWorkflow(
11 | coroutineScope: CoroutineScope,
12 | storage: PersistentStorage,
13 | ): AppWorkflow {
14 | val component = AppComponent::class.create(storage)
15 | val workflow = component.appWorkflow
16 |
17 | component.supervisors.forEach {
18 | coroutineScope.launch(SupervisorJob()) { it.start() }
19 | }
20 |
21 | return workflow
22 | }
23 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/Moment.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import androidx.compose.runtime.Immutable
4 | import kotlinx.datetime.Instant
5 | import kotlinx.serialization.Serializable
6 | import kotlin.jvm.JvmInline
7 |
8 | @Serializable
9 | @Immutable
10 | @JvmInline
11 | value class Moment(
12 | val instant: Instant,
13 | ) : Comparable {
14 | operator fun plus(delta: Delta): Moment = Moment(instant + delta.duration)
15 |
16 | operator fun minus(delta: Delta): Moment = Moment(instant - delta.duration)
17 |
18 | operator fun minus(other: Moment): Delta = Delta(instant - other.instant)
19 |
20 | override fun compareTo(other: Moment): Int = instant.compareTo(instant)
21 | }
22 |
--------------------------------------------------------------------------------
/app/common/src/iosMain/kotlin/sh/christian/ozone/util/time.ios.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.util
2 |
3 | import androidx.compose.runtime.Composable
4 | import kotlinx.datetime.toNSDate
5 | import platform.Foundation.NSDateFormatter
6 | import platform.Foundation.NSDateFormatterShortStyle
7 | import sh.christian.ozone.model.Moment
8 |
9 | @Composable
10 | actual fun Moment.formatDate(): String {
11 | return NSDateFormatter()
12 | .apply { dateStyle = NSDateFormatterShortStyle }
13 | .stringFromDate(instant.toNSDate())
14 | }
15 |
16 | @Composable
17 | actual fun Moment.formatTime(): String {
18 | return NSDateFormatter()
19 | .apply { timeStyle = NSDateFormatterShortStyle }
20 | .stringFromDate(instant.toNSDate())
21 | }
22 |
--------------------------------------------------------------------------------
/app/common/src/androidMain/kotlin/sh/christian/ozone/util/time.android.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.util
2 |
3 | import android.text.format.DateFormat
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.platform.LocalContext
6 | import kotlinx.datetime.toJavaInstant
7 | import sh.christian.ozone.model.Moment
8 | import java.util.Date
9 |
10 | @Composable
11 | actual fun Moment.formatDate(): String {
12 | return DateFormat
13 | .getDateFormat(LocalContext.current)
14 | .format(Date.from(instant.toJavaInstant()))
15 | }
16 |
17 | @Composable
18 | actual fun Moment.formatTime(): String {
19 | return DateFormat
20 | .getTimeFormat(LocalContext.current)
21 | .format(Date.from(instant.toJavaInstant()))
22 | }
23 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/runtime/BlobSerializer.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.runtime
2 |
3 | import kotlinx.serialization.DeserializationStrategy
4 | import kotlinx.serialization.json.JsonContentPolymorphicSerializer
5 | import kotlinx.serialization.json.JsonElement
6 | import kotlinx.serialization.json.jsonObject
7 | import sh.christian.ozone.api.model.Blob
8 |
9 | object BlobSerializer : JsonContentPolymorphicSerializer(Blob::class) {
10 | override fun selectDeserializer(element: JsonElement): DeserializationStrategy {
11 | return if (element.jsonObject.containsKey("ref")) {
12 | Blob.StandardBlob.serializer()
13 | } else {
14 | Blob.LegacyBlob.serializer()
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/app/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ozone-multiplatform")
3 | id("ozone-compose")
4 | }
5 |
6 | ozone {
7 | androidApp {
8 | namespace = "sh.christian.ozone"
9 |
10 | defaultConfig {
11 | applicationId = "sh.christian.ozone"
12 | versionCode = 100
13 | versionName = version.toString()
14 | }
15 | }
16 | }
17 |
18 | kotlin {
19 | sourceSets {
20 | val androidMain by getting {
21 | dependencies {
22 | implementation(project(":app:common"))
23 | implementation(libs.androidx.activity.compose)
24 | implementation(libs.androidx.appcompat)
25 | implementation(libs.androidx.core)
26 | implementation(libs.retainedactivity)
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/identity/defs.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.identity.defs",
4 | "defs": {
5 | "identityInfo": {
6 | "type": "object",
7 | "required": ["did", "handle", "didDoc"],
8 | "properties": {
9 | "did": { "type": "string", "format": "did" },
10 | "handle": {
11 | "type": "string",
12 | "format": "handle",
13 | "description": "The validated handle of the account; or 'handle.invalid' if the handle did not bi-directionally match the DID document."
14 | },
15 | "didDoc": {
16 | "type": "unknown",
17 | "description": "The complete DID document for the identity."
18 | }
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/temp/addReservedHandle.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.temp.addReservedHandle",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Add a handle to the set of reserved handles.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["handle"],
13 | "properties": {
14 | "handle": { "type": "string" }
15 | }
16 | }
17 | },
18 | "output": {
19 | "encoding": "application/json",
20 | "schema": {
21 | "type": "object",
22 | "properties": {}
23 | }
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/android/src/androidMain/kotlin/sh/christian/ozone/StatusBarTheme.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone
2 |
3 | import android.app.Activity
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.SideEffect
6 | import androidx.compose.ui.platform.LocalContext
7 | import androidx.core.view.WindowCompat
8 | import sh.christian.ozone.ui.LocalColorTheme
9 |
10 | @Composable
11 | fun StatusBarTheme() {
12 | val window = (LocalContext.current as Activity).window
13 | val view = window.decorView
14 |
15 | if (!view.isInEditMode) {
16 | val lightTheme = LocalColorTheme.current.isLight()
17 |
18 | SideEffect {
19 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = lightTheme
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/listblock.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.listblock",
4 | "defs": {
5 | "main": {
6 | "type": "record",
7 | "description": "Record representing a block relationship against an entire an entire list of accounts (actors).",
8 | "key": "tid",
9 | "record": {
10 | "type": "object",
11 | "required": ["subject", "createdAt"],
12 | "properties": {
13 | "subject": {
14 | "type": "string",
15 | "format": "at-uri",
16 | "description": "Reference (AT-URI) to the mod list record."
17 | },
18 | "createdAt": { "type": "string", "format": "datetime" }
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/getConvo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.getConvo",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "parameters": {
8 | "type": "params",
9 | "required": ["convoId"],
10 | "properties": {
11 | "convoId": { "type": "string" }
12 | }
13 | },
14 | "output": {
15 | "encoding": "application/json",
16 | "schema": {
17 | "type": "object",
18 | "required": ["convo"],
19 | "properties": {
20 | "convo": {
21 | "type": "ref",
22 | "ref": "chat.bsky.convo.defs#convoView"
23 | }
24 | }
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/enableAccountInvites.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.enableAccountInvites",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Re-enable an account's ability to receive invite codes.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["account"],
13 | "properties": {
14 | "account": { "type": "string", "format": "did" },
15 | "note": {
16 | "type": "string",
17 | "description": "Optional reason for enabled invites."
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/block.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.block",
4 | "defs": {
5 | "main": {
6 | "type": "record",
7 | "description": "Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details.",
8 | "key": "tid",
9 | "record": {
10 | "type": "object",
11 | "required": ["subject", "createdAt"],
12 | "properties": {
13 | "subject": {
14 | "type": "string",
15 | "format": "did",
16 | "description": "DID of the account to be blocked."
17 | },
18 | "createdAt": { "type": "string", "format": "datetime" }
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/deleteMessageForSelf.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.deleteMessageForSelf",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "required": ["convoId", "messageId"],
12 | "properties": {
13 | "convoId": { "type": "string" },
14 | "messageId": { "type": "string" }
15 | }
16 | }
17 | },
18 | "output": {
19 | "encoding": "application/json",
20 | "schema": {
21 | "type": "ref",
22 | "ref": "chat.bsky.convo.defs#deletedMessageView"
23 | }
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/disableInviteCodes.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.disableInviteCodes",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Disable some set of codes and/or all codes associated with a set of users.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "properties": {
13 | "codes": {
14 | "type": "array",
15 | "items": { "type": "string" }
16 | },
17 | "accounts": {
18 | "type": "array",
19 | "items": { "type": "string" }
20 | }
21 | }
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/updateAccountEmail.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.updateAccountEmail",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Administrative action to update an account's email.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["account", "email"],
13 | "properties": {
14 | "account": {
15 | "type": "string",
16 | "format": "at-identifier",
17 | "description": "The handle or DID of the repo."
18 | },
19 | "email": { "type": "string" }
20 | }
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/common/src/iosMain/kotlin/sh/christian/ozone/ui/compose/SystemInsets.ios.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.unit.dp
6 | import kotlinx.cinterop.ExperimentalForeignApi
7 | import kotlinx.cinterop.useContents
8 | import platform.UIKit.UIApplication
9 |
10 | @OptIn(ExperimentalForeignApi::class)
11 | @Composable
12 | actual fun rememberSystemInsets(): PaddingValues {
13 | val window = UIApplication.sharedApplication.keyWindow!!
14 |
15 | return window.safeAreaInsets.useContents {
16 | PaddingValues(
17 | start = left.dp,
18 | top = top.dp,
19 | end = right.dp,
20 | bottom = bottom.dp,
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/common/src/jvmMain/kotlin/sh/christian/ozone/util/time.jvm.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.util
2 |
3 | import androidx.compose.runtime.Composable
4 | import kotlinx.datetime.toJavaInstant
5 | import sh.christian.ozone.model.Moment
6 | import java.text.DateFormat
7 | import java.text.SimpleDateFormat
8 | import java.util.Date
9 | import java.util.Locale
10 |
11 | @Composable
12 | actual fun Moment.formatDate(): String {
13 | return SimpleDateFormat
14 | .getDateInstance(DateFormat.LONG, Locale.getDefault())
15 | .format(Date.from(instant.toJavaInstant()))
16 | }
17 |
18 | @Composable
19 | actual fun Moment.formatTime(): String {
20 | return SimpleDateFormat
21 | .getTimeInstance(DateFormat.SHORT, Locale.getDefault())
22 | .format(Date.from(instant.toJavaInstant()))
23 | }
24 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/video/getUploadLimits.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.video.getUploadLimits",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get video upload limits for the authenticated user.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["canUpload"],
13 | "properties": {
14 | "canUpload": { "type": "boolean" },
15 | "remainingDailyVideos": { "type": "integer" },
16 | "remainingDailyBytes": { "type": "integer" },
17 | "message": { "type": "string" },
18 | "error": { "type": "string" }
19 | }
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/identity/updateHandle.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.identity.updateHandle",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Updates the current account's handle. Verifies handle validity, and updates did:plc document if necessary. Implemented by PDS, and requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["handle"],
13 | "properties": {
14 | "handle": {
15 | "type": "string",
16 | "format": "handle",
17 | "description": "The new handle."
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/disableAccountInvites.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.disableAccountInvites",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Disable an account from receiving new invite codes, but does not invalidate existing codes.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["account"],
13 | "properties": {
14 | "account": { "type": "string", "format": "did" },
15 | "note": {
16 | "type": "string",
17 | "description": "Optional reason for disabled invites."
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/communication/listTemplates.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.communication.listTemplates",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get list of all communication templates.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["communicationTemplates"],
13 | "properties": {
14 | "communicationTemplates": {
15 | "type": "array",
16 | "items": {
17 | "type": "ref",
18 | "ref": "tools.ozone.communication.defs#templateView"
19 | }
20 | }
21 | }
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/moderation/getRecord.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.moderation.getRecord",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get details about a record.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["uri"],
11 | "properties": {
12 | "uri": { "type": "string", "format": "at-uri" },
13 | "cid": { "type": "string", "format": "cid" }
14 | }
15 | },
16 | "output": {
17 | "encoding": "application/json",
18 | "schema": {
19 | "type": "ref",
20 | "ref": "tools.ozone.moderation.defs#recordViewDetail"
21 | }
22 | },
23 | "errors": [{ "name": "RecordNotFound" }]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/notification/getPreferences.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.notification.getPreferences",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get notification-related preferences for an account. Requires auth.",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {}
11 | },
12 | "output": {
13 | "encoding": "application/json",
14 | "schema": {
15 | "type": "object",
16 | "required": ["preferences"],
17 | "properties": {
18 | "preferences": {
19 | "type": "ref",
20 | "ref": "app.bsky.notification.defs#preferences"
21 | }
22 | }
23 | }
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/oauth/src/commonMain/kotlin/sh/christian/ozone/oauth/network/OAuthParRequest.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.oauth.network
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class OAuthParRequest(
8 | @SerialName("response_type")
9 | val responseType: String,
10 | @SerialName("code_challenge_method")
11 | val codeChallengeMethod: String,
12 | @SerialName("scope")
13 | val scope: String,
14 | @SerialName("client_id")
15 | val clientId: String,
16 | @SerialName("redirect_uri")
17 | val redirectUri: String,
18 | @SerialName("code_challenge")
19 | val codeChallenge: String,
20 | @SerialName("state")
21 | val state: String,
22 | @SerialName("login_hint")
23 | val loginHint: String? = null,
24 | )
25 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/leaveConvo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.leaveConvo",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "required": ["convoId"],
12 | "properties": {
13 | "convoId": { "type": "string" }
14 | }
15 | }
16 | },
17 | "output": {
18 | "encoding": "application/json",
19 | "schema": {
20 | "type": "object",
21 | "required": ["convoId", "rev"],
22 | "properties": {
23 | "convoId": { "type": "string" },
24 | "rev": { "type": "string" }
25 | }
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/app/Supervisor.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.app
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.coroutineScope
5 | import kotlinx.coroutines.suspendCancellableCoroutine
6 |
7 | abstract class Supervisor {
8 | protected var scope: CoroutineScope? = null
9 |
10 | suspend fun start() {
11 | coroutineScope {
12 | scope = this
13 | onStart()
14 |
15 | try {
16 | // Hang forever, or until this coroutine is cancelled.
17 | suspendCancellableCoroutine { }
18 | } finally {
19 | scope = null
20 | }
21 | }
22 | }
23 |
24 | protected open suspend fun CoroutineScope.onStart() = Unit
25 |
26 | protected fun requireCoroutineScope(): CoroutineScope = requireNotNull(scope)
27 | }
28 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/updateAccountSigningKey.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.updateAccountSigningKey",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Administrative action to update an account's signing key in their Did document.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["did", "signingKey"],
13 | "properties": {
14 | "did": { "type": "string", "format": "did" },
15 | "signingKey": {
16 | "type": "string",
17 | "format": "did",
18 | "description": "Did-key formatted public key"
19 | }
20 | }
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/error/ErrorWorkflow.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.error
2 |
3 | import com.squareup.workflow1.StatelessWorkflow
4 | import me.tatarka.inject.annotations.Inject
5 | import sh.christian.ozone.ui.workflow.OverlayRendering
6 |
7 | @Inject
8 | class ErrorWorkflow : StatelessWorkflow() {
9 | override fun render(
10 | renderProps: ErrorProps,
11 | context: RenderContext,
12 | ): OverlayRendering {
13 | return ErrorScreen(
14 | title = renderProps.title,
15 | description = renderProps.description,
16 | retryable = renderProps.retryable,
17 | onRetry = context.eventHandler { setOutput(ErrorOutput.Retry) },
18 | onDismiss = context.eventHandler { setOutput(ErrorOutput.Dismiss) },
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/repo/uploadBlob.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.repo.uploadBlob",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.",
8 | "input": {
9 | "encoding": "*/*"
10 | },
11 | "output": {
12 | "encoding": "application/json",
13 | "schema": {
14 | "type": "object",
15 | "required": ["blob"],
16 | "properties": {
17 | "blob": { "type": "blob" }
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/notifications/type/QuoteRow.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.notifications.type
2 |
3 | import androidx.compose.runtime.Composable
4 | import sh.christian.ozone.compose.asReplyInfo
5 | import sh.christian.ozone.model.Notification
6 | import sh.christian.ozone.notifications.NotificationRowContext
7 | import sh.christian.ozone.timeline.components.TimelinePostItem
8 |
9 | @Composable
10 | fun QuoteRow(
11 | context: NotificationRowContext,
12 | content: Notification.Content.Quoted,
13 | ) {
14 | TimelinePostItem(
15 | now = context.now,
16 | post = content.post,
17 | onOpenPost = context.onOpenPost,
18 | onOpenUser = context.onOpenUser,
19 | onOpenImage = context.onOpenImage,
20 | onReplyToPost = { context.onReplyToPost(content.post.asReplyInfo()) },
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/notification/declaration.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.notification.declaration",
4 | "defs": {
5 | "main": {
6 | "type": "record",
7 | "description": "A declaration of the user's choices related to notifications that can be produced by them.",
8 | "key": "literal:self",
9 | "record": {
10 | "type": "object",
11 | "required": ["allowSubscriptions"],
12 | "properties": {
13 | "allowSubscriptions": {
14 | "type": "string",
15 | "description": "A declaration of the user's preference for allowing activity subscriptions from other users. Absence of a record implies 'followers'.",
16 | "knownValues": ["followers", "mutuals", "none"]
17 | }
18 | }
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/notification/getUnreadCount.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.notification.getUnreadCount",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Count the number of unread notifications for the requesting account. Requires auth.",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {
11 | "priority": { "type": "boolean" },
12 | "seenAt": { "type": "string", "format": "datetime" }
13 | }
14 | },
15 | "output": {
16 | "encoding": "application/json",
17 | "schema": {
18 | "type": "object",
19 | "required": ["count"],
20 | "properties": {
21 | "count": { "type": "integer" }
22 | }
23 | }
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/api/NetworkWorker.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api
2 |
3 | import com.squareup.workflow1.Worker
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.flow
6 | import kotlinx.coroutines.flow.flowOn
7 | import sh.christian.ozone.api.response.AtpResponse
8 |
9 | abstract class NetworkWorker : Worker> {
10 | override fun run(): Flow> = flow {
11 | emit(execute())
12 | }.flowOn(OzoneDispatchers.IO)
13 |
14 | abstract suspend fun execute(): AtpResponse
15 |
16 | companion object {
17 | operator fun invoke(
18 | block: suspend () -> AtpResponse,
19 | ): NetworkWorker = object : NetworkWorker() {
20 | override suspend fun execute(): AtpResponse = block()
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/notifications/type/ReplyRow.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.notifications.type
2 |
3 | import androidx.compose.runtime.Composable
4 | import sh.christian.ozone.compose.asReplyInfo
5 | import sh.christian.ozone.model.Notification
6 | import sh.christian.ozone.notifications.NotificationRowContext
7 | import sh.christian.ozone.timeline.components.TimelinePostItem
8 |
9 | @Composable
10 | fun ReplyRow(
11 | context: NotificationRowContext,
12 | content: Notification.Content.RepliedTo,
13 | ) {
14 | TimelinePostItem(
15 | now = context.now,
16 | post = content.post,
17 | onOpenPost = context.onOpenPost,
18 | onOpenUser = context.onOpenUser,
19 | onOpenImage = context.onOpenImage,
20 | onReplyToPost = { context.onReplyToPost(content.post.asReplyInfo()) },
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/app/desktop/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2 |
3 | plugins {
4 | kotlin("plugin.serialization")
5 | id("ozone-multiplatform")
6 | id("ozone-compose")
7 | }
8 |
9 | ozone {
10 | jvm()
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | val jvmMain by getting {
16 | dependencies {
17 | implementation(project(":app:common"))
18 | implementation(compose.desktop.currentOs)
19 | implementation(kotlin("reflect"))
20 | }
21 | }
22 | }
23 | }
24 |
25 | compose.desktop {
26 | application {
27 | mainClass = "sh.christian.ozone.MainKt"
28 |
29 | nativeDistributions {
30 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
31 | packageName = "ozone"
32 | packageVersion = "1.0.0"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/sendMessage.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.sendMessage",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "required": ["convoId", "message"],
12 | "properties": {
13 | "convoId": { "type": "string" },
14 | "message": {
15 | "type": "ref",
16 | "ref": "chat.bsky.convo.defs#messageInput"
17 | }
18 | }
19 | }
20 | },
21 | "output": {
22 | "encoding": "application/json",
23 | "schema": {
24 | "type": "ref",
25 | "ref": "chat.bsky.convo.defs#messageView"
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/notifications/type/MentionRow.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.notifications.type
2 |
3 | import androidx.compose.runtime.Composable
4 | import sh.christian.ozone.compose.asReplyInfo
5 | import sh.christian.ozone.model.Notification
6 | import sh.christian.ozone.notifications.NotificationRowContext
7 | import sh.christian.ozone.timeline.components.TimelinePostItem
8 |
9 | @Composable
10 | fun MentionRow(
11 | context: NotificationRowContext,
12 | content: Notification.Content.Mentioned,
13 | ) {
14 | TimelinePostItem(
15 | now = context.now,
16 | post = content.post,
17 | onOpenPost = context.onOpenPost,
18 | onOpenUser = context.onOpenUser,
19 | onOpenImage = context.onOpenImage,
20 | onReplyToPost = { context.onReplyToPost(content.post.asReplyInfo()) },
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/deleteAccount.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.deleteAccount",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["did", "password", "token"],
13 | "properties": {
14 | "did": { "type": "string", "format": "did" },
15 | "password": { "type": "string" },
16 | "token": { "type": "string" }
17 | }
18 | }
19 | },
20 | "errors": [{ "name": "ExpiredToken" }, { "name": "InvalidToken" }]
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/confirmEmail.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.confirmEmail",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Confirm an email using a token from com.atproto.server.requestEmailConfirmation.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["email", "token"],
13 | "properties": {
14 | "email": { "type": "string" },
15 | "token": { "type": "string" }
16 | }
17 | }
18 | },
19 | "errors": [
20 | { "name": "AccountNotFound" },
21 | { "name": "ExpiredToken" },
22 | { "name": "InvalidToken" },
23 | { "name": "InvalidEmail" }
24 | ]
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/muteConvo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.muteConvo",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "required": ["convoId"],
12 | "properties": {
13 | "convoId": { "type": "string" }
14 | }
15 | }
16 | },
17 | "output": {
18 | "encoding": "application/json",
19 | "schema": {
20 | "type": "object",
21 | "required": ["convo"],
22 | "properties": {
23 | "convo": {
24 | "type": "ref",
25 | "ref": "chat.bsky.convo.defs#convoView"
26 | }
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/unmuteConvo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.unmuteConvo",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "required": ["convoId"],
12 | "properties": {
13 | "convoId": { "type": "string" }
14 | }
15 | }
16 | },
17 | "output": {
18 | "encoding": "application/json",
19 | "schema": {
20 | "type": "object",
21 | "required": ["convo"],
22 | "properties": {
23 | "convo": {
24 | "type": "ref",
25 | "ref": "chat.bsky.convo.defs#convoView"
26 | }
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/sync/requestCrawl.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.sync.requestCrawl",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Request a service to persistently crawl hosted repos. Expected use is new PDS instances declaring their existence to Relays. Does not require auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["hostname"],
13 | "properties": {
14 | "hostname": {
15 | "type": "string",
16 | "description": "Hostname of the current service (eg, PDS) that is requesting to be crawled."
17 | }
18 | }
19 | }
20 | },
21 | "errors": [{ "name": "HostBanned" }]
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/runtime/AtIdentifierSerializer.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.runtime
2 |
3 | import kotlinx.serialization.DeserializationStrategy
4 | import kotlinx.serialization.json.JsonContentPolymorphicSerializer
5 | import kotlinx.serialization.json.JsonElement
6 | import kotlinx.serialization.json.jsonPrimitive
7 | import sh.christian.ozone.api.AtIdentifier
8 | import sh.christian.ozone.api.Did
9 | import sh.christian.ozone.api.Handle
10 |
11 | object AtIdentifierSerializer : JsonContentPolymorphicSerializer(AtIdentifier::class) {
12 | override fun selectDeserializer(element: JsonElement): DeserializationStrategy {
13 | return if (element.jsonPrimitive.content.startsWith("did:")) {
14 | Did.serializer()
15 | } else {
16 | Handle.serializer()
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/store/src/commonMain/kotlin/sh/christian/ozone/store/PersistentStorage.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.store
2 |
3 | import kotlin.reflect.KClass
4 |
5 | interface PersistentStorage {
6 | fun preference(
7 | key: String,
8 | defaultValue: T,
9 | clazz: KClass,
10 | ): Preference
11 |
12 | fun nullablePreference(
13 | key: String,
14 | defaultValue: T?,
15 | clazz: KClass,
16 | ): Preference
17 | }
18 |
19 | inline fun PersistentStorage.preference(
20 | key: String,
21 | defaultValue: T,
22 | ): Preference {
23 | return preference(key, defaultValue, T::class)
24 | }
25 |
26 | inline fun PersistentStorage.nullablePreference(
27 | key: String,
28 | defaultValue: T?,
29 | ): Preference {
30 | return nullablePreference(key, defaultValue, T::class)
31 | }
32 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/bookmark/deleteBookmark.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.bookmark.deleteBookmark",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Deletes a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["uri"],
13 | "properties": {
14 | "uri": { "type": "string", "format": "at-uri" }
15 | }
16 | }
17 | },
18 | "errors": [
19 | {
20 | "name": "UnsupportedCollection",
21 | "description": "The URI to be bookmarked is for an unsupported collection."
22 | }
23 | ]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/thread/ThreadProps.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.thread
2 |
3 | import sh.christian.ozone.api.AtUri
4 | import sh.christian.ozone.api.Cid
5 | import sh.christian.ozone.model.Reference
6 | import sh.christian.ozone.model.TimelinePost
7 |
8 | sealed interface ThreadProps {
9 | val uri: AtUri
10 | val cid: Cid
11 | val originalPost: TimelinePost?
12 |
13 | data class FromPost(
14 | override val originalPost: TimelinePost,
15 | ) : ThreadProps {
16 | override val uri: AtUri = originalPost.uri
17 | override val cid: Cid = originalPost.cid
18 | }
19 |
20 | data class FromReference(
21 | val reference: Reference,
22 | ) : ThreadProps {
23 | override val originalPost: TimelinePost? = null
24 | override val uri: AtUri = reference.uri
25 | override val cid: Cid = reference.cid
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/common/src/jsMain/kotlin/sh/christian/ozone/util/time.js.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.util
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.text.intl.Locale
5 | import kotlinx.datetime.toJSDate
6 | import sh.christian.ozone.model.Moment
7 |
8 | @Composable
9 | actual fun Moment.formatDate(): String {
10 | return instant.toJSDate().toLocaleDateString(
11 | locales = Locale.current.toLanguageTag(),
12 | options = dateLocaleOptions {
13 | year = "numeric"
14 | month = "long"
15 | day = "numeric"
16 | },
17 | )
18 | }
19 |
20 | @Composable
21 | actual fun Moment.formatTime(): String {
22 | return instant.toJSDate().toLocaleTimeString(
23 | locales = Locale.current.toLanguageTag(),
24 | options = dateLocaleOptions {
25 | hour = "numeric"
26 | minute = "numeric"
27 | },
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/video/getJobStatus.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.video.getJobStatus",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get status details for a video processing job.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["jobId"],
11 | "properties": {
12 | "jobId": {
13 | "type": "string"
14 | }
15 | }
16 | },
17 | "output": {
18 | "encoding": "application/json",
19 | "schema": {
20 | "type": "object",
21 | "required": ["jobStatus"],
22 | "properties": {
23 | "jobStatus": {
24 | "type": "ref",
25 | "ref": "app.bsky.video.defs#jobStatus"
26 | }
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/actor/getProfile.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.actor.getProfile",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["actor"],
11 | "properties": {
12 | "actor": {
13 | "type": "string",
14 | "format": "at-identifier",
15 | "description": "Handle or DID of account to fetch profile of."
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "ref",
23 | "ref": "app.bsky.actor.defs#profileViewDetailed"
24 | }
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/acceptConvo.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.acceptConvo",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "required": ["convoId"],
12 | "properties": {
13 | "convoId": { "type": "string" }
14 | }
15 | }
16 | },
17 | "output": {
18 | "encoding": "application/json",
19 | "schema": {
20 | "type": "object",
21 | "properties": {
22 | "rev": {
23 | "description": "Rev when the convo was accepted. If not present, the convo was already accepted.",
24 | "type": "string"
25 | }
26 | }
27 | }
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/actor/getPreferences.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.actor.getPreferences",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth.",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {}
11 | },
12 | "output": {
13 | "encoding": "application/json",
14 | "schema": {
15 | "type": "object",
16 | "required": ["preferences"],
17 | "properties": {
18 | "preferences": {
19 | "type": "ref",
20 | "ref": "app.bsky.actor.defs#preferences"
21 | }
22 | }
23 | }
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/api-gen-runtime-internal/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ozone-multiplatform")
3 | id("ozone-publish")
4 | kotlin("plugin.serialization")
5 | }
6 |
7 | ozone {
8 | js()
9 | jvm()
10 | ios("BlueskyAPIRuntimeInternal")
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | val commonMain by getting {
16 | dependencies {
17 | api(libs.kotlinx.coroutines.core)
18 | api(libs.kotlinx.serialization.core)
19 | api(libs.ktor.core)
20 |
21 | api(project(":api-gen-runtime"))
22 |
23 | implementation(libs.kotlinx.serialization.cbor)
24 | implementation(libs.kotlinx.serialization.json)
25 | implementation(libs.ktor.contentnegotiation)
26 | implementation(libs.ktor.serialization.json)
27 | implementation(libs.ktor.websockets)
28 |
29 | implementation(kotlin("reflect"))
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/team/deleteMember.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.team.deleteMember",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Delete a member from ozone team. Requires admin role.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["did"],
13 | "properties": {
14 | "did": { "type": "string", "format": "did" }
15 | }
16 | }
17 | },
18 | "errors": [
19 | {
20 | "name": "MemberNotFound",
21 | "description": "The member being deleted does not exist"
22 | },
23 | {
24 | "name": "CannotDeleteSelf",
25 | "description": "You can not delete yourself from the team"
26 | }
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/updateRead.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.updateRead",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "required": ["convoId"],
12 | "properties": {
13 | "convoId": { "type": "string" },
14 | "messageId": { "type": "string" }
15 | }
16 | }
17 | },
18 | "output": {
19 | "encoding": "application/json",
20 | "schema": {
21 | "type": "object",
22 | "required": ["convo"],
23 | "properties": {
24 | "convo": {
25 | "type": "ref",
26 | "ref": "chat.bsky.convo.defs#convoView"
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/createInviteCode.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.createInviteCode",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Create an invite code.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["useCount"],
13 | "properties": {
14 | "useCount": { "type": "integer" },
15 | "forAccount": { "type": "string", "format": "did" }
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["code"],
24 | "properties": {
25 | "code": { "type": "string" }
26 | }
27 | }
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/deactivateAccount.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.deactivateAccount",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "properties": {
13 | "deleteAfter": {
14 | "type": "string",
15 | "format": "datetime",
16 | "description": "A recommendation to server as to how long they should hold onto the deactivated account before deleting."
17 | }
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/oauth/src/commonMain/kotlin/sh/christian/ozone/oauth/OAuthAuthorizationRequest.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.oauth
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlin.time.Duration
5 |
6 | /**
7 | * Represents an OAuth authorization request.
8 | *
9 | * @param authorizeRequestUrl The URL to which the user should be redirected to authorize the request.
10 | * @param expiresIn The duration for which the authorization request is valid.
11 | * @param codeVerifier A unique string that will be used to verify the token request.
12 | * @param state A unique string to maintain state between the request and callback.
13 | * @param nonce A unique string to prevent replay attacks.
14 | */
15 | @Serializable
16 | data class OAuthAuthorizationRequest(
17 | val authorizeRequestUrl: String,
18 | val expiresIn: Duration,
19 | val codeVerifier: String,
20 | val state: String,
21 | val nonce: String,
22 | )
23 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/login/LoginState.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.login
2 |
3 | import sh.christian.ozone.error.ErrorProps
4 | import sh.christian.ozone.login.auth.Credentials
5 | import sh.christian.ozone.login.auth.ServerInfo
6 |
7 | sealed interface LoginState {
8 | val mode: LoginScreenMode
9 | val serverInfo: ServerInfo?
10 |
11 | data class ShowingLogin(
12 | override val mode: LoginScreenMode,
13 | override val serverInfo: ServerInfo?,
14 | ) : LoginState
15 |
16 | data class SigningIn(
17 | override val mode: LoginScreenMode,
18 | override val serverInfo: ServerInfo?,
19 | val credentials: Credentials,
20 | ) : LoginState
21 |
22 | data class ShowingError(
23 | override val mode: LoginScreenMode,
24 | override val serverInfo: ServerInfo?,
25 | val errorProps: ErrorProps,
26 | val credentials: Credentials,
27 | ) : LoginState
28 | }
29 |
--------------------------------------------------------------------------------
/api-gen-runtime/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("ozone-dokka")
3 | id("ozone-multiplatform")
4 | id("ozone-publish")
5 | kotlin("plugin.serialization")
6 | }
7 |
8 | ozone {
9 | js()
10 | jvm()
11 | ios("BlueskyAPIRuntime")
12 | }
13 |
14 | kotlin {
15 | sourceSets {
16 | val commonMain by getting {
17 | dependencies {
18 | api(libs.kotlinx.datetime)
19 | api(libs.kotlinx.serialization.json)
20 | api(libs.ktor.core)
21 |
22 | implementation(kotlin("reflect"))
23 | }
24 | }
25 | val iosMain by getting {
26 | dependencies {
27 | implementation(libs.ktor.darwin)
28 | }
29 | }
30 | val jvmMain by getting {
31 | dependencies {
32 | implementation(libs.ktor.cio)
33 | }
34 | }
35 | val jsMain by getting {
36 | dependencies {
37 | implementation(libs.ktor.js)
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/contact/removeData.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.contact.removeData",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "WARNING: This is unstable and under active development, don't use it while this warning is here. Removes all stored hashes used for contact matching, existing matches, and sync status. Requires authentication.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "properties": {}
13 | }
14 | },
15 | "output": {
16 | "encoding": "application/json",
17 | "schema": {
18 | "type": "object",
19 | "properties": {}
20 | }
21 | },
22 | "errors": [
23 | {
24 | "name": "TODO",
25 | "description": "TODO"
26 | }
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/lexicon/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.lexicon.schema",
4 | "defs": {
5 | "main": {
6 | "type": "record",
7 | "description": "Representation of Lexicon schemas themselves, when published as atproto records. Note that the schema language is not defined in Lexicon; this meta schema currently only includes a single version field ('lexicon'). See the atproto specifications for description of the other expected top-level fields ('id', 'defs', etc).",
8 | "key": "nsid",
9 | "record": {
10 | "type": "object",
11 | "required": ["lexicon"],
12 | "properties": {
13 | "lexicon": {
14 | "type": "integer",
15 | "description": "Indicates the 'version' of the Lexicon language. Must be '1' for the current atproto/Lexicon schema system."
16 | }
17 | }
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/bookmark/createBookmark.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.bookmark.createBookmark",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Creates a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["uri", "cid"],
13 | "properties": {
14 | "uri": { "type": "string", "format": "at-uri" },
15 | "cid": { "type": "string", "format": "cid" }
16 | }
17 | }
18 | },
19 | "errors": [
20 | {
21 | "name": "UnsupportedCollection",
22 | "description": "The URI to be bookmarked is for an unsupported collection."
23 | }
24 | ]
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/updateAllRead.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.updateAllRead",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "input": {
8 | "encoding": "application/json",
9 | "schema": {
10 | "type": "object",
11 | "properties": {
12 | "status": {
13 | "type": "string",
14 | "knownValues": ["request", "accepted"]
15 | }
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["updatedCount"],
24 | "properties": {
25 | "updatedCount": {
26 | "description": "The count of updated convos.",
27 | "type": "integer"
28 | }
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/sync/getHead.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.sync.getHead",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "DEPRECATED - please use com.atproto.sync.getLatestCommit instead",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["did"],
11 | "properties": {
12 | "did": {
13 | "type": "string",
14 | "format": "did",
15 | "description": "The DID of the repo."
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["root"],
24 | "properties": {
25 | "root": { "type": "string", "format": "cid" }
26 | }
27 | }
28 | },
29 | "errors": [{ "name": "HeadNotFound" }]
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/sync/notifyOfUpdate.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.sync.notifyOfUpdate",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Notify a crawling service of a recent update, and that crawling should resume. Intended use is after a gap between repo stream events caused the crawling service to disconnect. Does not require auth; implemented by Relay. DEPRECATED: just use com.atproto.sync.requestCrawl",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["hostname"],
13 | "properties": {
14 | "hostname": {
15 | "type": "string",
16 | "description": "Hostname of the current service (usually a PDS) that is notifying of update."
17 | }
18 | }
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/compose/TimeDelta.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import sh.christian.ozone.model.Delta
8 |
9 | @Composable
10 | fun TimeDelta(
11 | delta: Delta,
12 | modifier: Modifier = Modifier,
13 | ) {
14 | Text(
15 | modifier = modifier,
16 | text = delta.duration.toComponents { days, hours, minutes, seconds, _ ->
17 | when {
18 | days > 0 -> "${days}d"
19 | hours > 0 -> "${hours}h"
20 | minutes > 0 -> "${minutes}m"
21 | seconds > 0 -> "${seconds}s"
22 | seconds < 0 || minutes < 0 || hours < 0 || days < 0 -> "The Future"
23 | else -> "Now"
24 | }
25 | },
26 | maxLines = 1,
27 | style = MaterialTheme.typography.bodySmall,
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/updateEmail.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.updateEmail",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Update an account's email.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["email"],
13 | "properties": {
14 | "email": { "type": "string" },
15 | "emailAuthFactor": { "type": "boolean" },
16 | "token": {
17 | "type": "string",
18 | "description": "Requires a token from com.atproto.sever.requestEmailUpdate if the account's email has been confirmed."
19 | }
20 | }
21 | }
22 | },
23 | "errors": [
24 | { "name": "ExpiredToken" },
25 | { "name": "InvalidToken" },
26 | { "name": "TokenRequired" }
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/model/JsonContent.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.api.model
2 |
3 | import kotlinx.serialization.Serializable
4 | import kotlinx.serialization.json.Json
5 | import sh.christian.ozone.api.runtime.JsonContentSerializer
6 |
7 | /**
8 | * Represents arbitrary JSON content that corresponds to a model object.
9 | * This is often used to represent content that does not have known type defined at runtime.
10 | */
11 | @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
12 | @Serializable(with = JsonContentSerializer::class)
13 | expect class JsonContent {
14 |
15 | /**
16 | * Decode the content as a known [@Serializable][Serializable] class.
17 | */
18 | inline fun decodeAs(): T
19 |
20 | companion object {
21 | /**
22 | * Encode the value as a [JsonContent] object.
23 | */
24 | inline fun Json.encodeAsJsonContent(value: T): JsonContent
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/listitem.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.listitem",
4 | "defs": {
5 | "main": {
6 | "type": "record",
7 | "description": "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records.",
8 | "key": "tid",
9 | "record": {
10 | "type": "object",
11 | "required": ["subject", "list", "createdAt"],
12 | "properties": {
13 | "subject": {
14 | "type": "string",
15 | "format": "did",
16 | "description": "The account which is included on the list."
17 | },
18 | "list": {
19 | "type": "string",
20 | "format": "at-uri",
21 | "description": "Reference (AT-URI) to the list record (app.bsky.graph.list)."
22 | },
23 | "createdAt": { "type": "string", "format": "datetime" }
24 | }
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/TimelinePostReply.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import app.bsky.feed.ReplyRef
4 | import app.bsky.feed.ReplyRefParentUnion
5 | import app.bsky.feed.ReplyRefRootUnion
6 |
7 | data class TimelinePostReply(
8 | val root: TimelinePost?,
9 | val parent: TimelinePost?,
10 | )
11 |
12 | fun ReplyRef.toReply(): TimelinePostReply {
13 | return TimelinePostReply(
14 | root = when (val root = root) {
15 | is ReplyRefRootUnion.BlockedPost -> null
16 | is ReplyRefRootUnion.NotFoundPost -> null
17 | is ReplyRefRootUnion.Unknown -> null
18 | is ReplyRefRootUnion.PostView -> root.value.toPost()
19 | },
20 | parent = when (val parent = parent) {
21 | is ReplyRefParentUnion.BlockedPost -> null
22 | is ReplyRefParentUnion.NotFoundPost -> null
23 | is ReplyRefParentUnion.Unknown -> null
24 | is ReplyRefParentUnion.PostView -> parent.value.toPost()
25 | }
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/feed/sendInteractions.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.feed.sendInteractions",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Send information about interactions with feed items back to the feed generator that served them.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["interactions"],
13 | "properties": {
14 | "interactions": {
15 | "type": "array",
16 | "items": {
17 | "type": "ref",
18 | "ref": "app.bsky.feed.defs#interaction"
19 | }
20 | }
21 | }
22 | }
23 | },
24 | "output": {
25 | "encoding": "application/json",
26 | "schema": {
27 | "type": "object",
28 | "properties": {}
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/notification/unregisterPush.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.notification.unregisterPush",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "The inverse of registerPush - inform a specified service that push notifications should no longer be sent to the given token for the requesting account. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["serviceDid", "token", "platform", "appId"],
13 | "properties": {
14 | "serviceDid": { "type": "string", "format": "did" },
15 | "token": { "type": "string" },
16 | "platform": {
17 | "type": "string",
18 | "knownValues": ["ios", "android", "web"]
19 | },
20 | "appId": { "type": "string" }
21 | }
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/android/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/lexicons/schemas/chat/bsky/convo/getConvoForMembers.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "chat.bsky.convo.getConvoForMembers",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "parameters": {
8 | "type": "params",
9 | "required": ["members"],
10 | "properties": {
11 | "members": {
12 | "type": "array",
13 | "minLength": 1,
14 | "maxLength": 10,
15 | "items": {
16 | "type": "string",
17 | "format": "did"
18 | }
19 | }
20 | }
21 | },
22 | "output": {
23 | "encoding": "application/json",
24 | "schema": {
25 | "type": "object",
26 | "required": ["convo"],
27 | "properties": {
28 | "convo": {
29 | "type": "ref",
30 | "ref": "chat.bsky.convo.defs#convoView"
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/getStarterPack.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.getStarterPack",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Gets a view of a starter pack.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["starterPack"],
11 | "properties": {
12 | "starterPack": {
13 | "type": "string",
14 | "format": "at-uri",
15 | "description": "Reference (AT-URI) of the starter pack record."
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["starterPack"],
24 | "properties": {
25 | "starterPack": {
26 | "type": "ref",
27 | "ref": "app.bsky.graph.defs#starterPackView"
28 | }
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/video/defs.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.video.defs",
4 | "defs": {
5 | "jobStatus": {
6 | "type": "object",
7 | "required": ["jobId", "did", "state"],
8 | "properties": {
9 | "jobId": { "type": "string" },
10 | "did": { "type": "string", "format": "did" },
11 | "state": {
12 | "type": "string",
13 | "description": "The state of the video processing job. All values not listed as a known value indicate that the job is in process.",
14 | "knownValues": ["JOB_STATE_COMPLETED", "JOB_STATE_FAILED"]
15 | },
16 | "progress": {
17 | "type": "integer",
18 | "minimum": 0,
19 | "maximum": 100,
20 | "description": "Progress within the current processing state."
21 | },
22 | "blob": { "type": "blob" },
23 | "error": { "type": "string" },
24 | "message": { "type": "string" }
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/compose/SystemInsets.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.BoxScope
5 | import androidx.compose.foundation.layout.PaddingValues
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 |
11 | @Composable
12 | expect fun rememberSystemInsets(): PaddingValues
13 |
14 | @Composable
15 | fun SystemInsets(
16 | modifier: Modifier = Modifier,
17 | contentAlignment: Alignment = Alignment.TopStart,
18 | propagateMinConstraints: Boolean = false,
19 | content: @Composable BoxScope.() -> Unit
20 | ) {
21 | Box(
22 | modifier = Modifier.padding(rememberSystemInsets()).then(modifier),
23 | contentAlignment = contentAlignment,
24 | propagateMinConstraints = propagateMinConstraints,
25 | content = content,
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/compose/ZoomableImage.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.Immutable
5 | import androidx.compose.runtime.Stable
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.ColorFilter
9 | import androidx.compose.ui.graphics.DefaultAlpha
10 | import androidx.compose.ui.graphics.painter.Painter
11 | import androidx.compose.ui.layout.ContentScale
12 |
13 | @Composable
14 | expect fun ZoomableImage(
15 | painter: Painter,
16 | contentDescription: String?,
17 | modifier: Modifier = Modifier,
18 | alignment: Alignment = Alignment.Center,
19 | contentScale: ContentScale = ContentScale.Fit,
20 | alpha: Float = DefaultAlpha,
21 | colorFilter: ColorFilter? = null,
22 | ): ZoomableImageHandle
23 |
24 | @Stable
25 | @Immutable
26 | data class ZoomableImageHandle(
27 | val resetZoom: suspend () -> Unit,
28 | )
29 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/unspecced/getTrends.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.unspecced.getTrends",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get the current trends on the network",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {
11 | "limit": {
12 | "type": "integer",
13 | "minimum": 1,
14 | "maximum": 25,
15 | "default": 10
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["trends"],
24 | "properties": {
25 | "trends": {
26 | "type": "array",
27 | "items": {
28 | "type": "ref",
29 | "ref": "app.bsky.unspecced.defs#trendView"
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/unspecced/getSuggestedFeeds.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.unspecced.getSuggestedFeeds",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get a list of suggested feeds",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {
11 | "limit": {
12 | "type": "integer",
13 | "minimum": 1,
14 | "maximum": 25,
15 | "default": 10
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["feeds"],
24 | "properties": {
25 | "feeds": {
26 | "type": "array",
27 | "items": {
28 | "type": "ref",
29 | "ref": "app.bsky.feed.defs#generatorView"
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/admin/getAccountInfos.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.admin.getAccountInfos",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get details about some accounts.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["dids"],
11 | "properties": {
12 | "dids": {
13 | "type": "array",
14 | "items": { "type": "string", "format": "did" }
15 | }
16 | }
17 | },
18 | "output": {
19 | "encoding": "application/json",
20 | "schema": {
21 | "type": "object",
22 | "required": ["infos"],
23 | "properties": {
24 | "infos": {
25 | "type": "array",
26 | "items": {
27 | "type": "ref",
28 | "ref": "com.atproto.admin.defs#accountView"
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/server/listAppPasswords.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.server.listAppPasswords",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "List all App Passwords.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["passwords"],
13 | "properties": {
14 | "passwords": {
15 | "type": "array",
16 | "items": { "type": "ref", "ref": "#appPassword" }
17 | }
18 | }
19 | }
20 | },
21 | "errors": [{ "name": "AccountTakedown" }]
22 | },
23 | "appPassword": {
24 | "type": "object",
25 | "required": ["name", "createdAt"],
26 | "properties": {
27 | "name": { "type": "string" },
28 | "createdAt": { "type": "string", "format": "datetime" },
29 | "privileged": { "type": "boolean" }
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/ui/compose/TextOverlayScreen.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui.compose
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.Surface
7 | import androidx.compose.material3.Text
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.unit.dp
10 | import sh.christian.ozone.ui.workflow.Dismissable
11 | import sh.christian.ozone.ui.workflow.OverlayRendering
12 | import sh.christian.ozone.ui.workflow.overlay
13 |
14 | class TextOverlayScreen(
15 | onDismiss: Dismissable,
16 | private val text: String,
17 | ) : OverlayRendering by overlay(onDismiss, { _ ->
18 | SystemInsets {
19 | Surface(shadowElevation = 16.dp) {
20 | Column(
21 | modifier = Modifier
22 | .padding(32.dp)
23 | .fillMaxWidth(),
24 | ) {
25 | Text(text)
26 | }
27 | }
28 | }
29 | })
30 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/feed/getFeedGenerators.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.feed.getFeedGenerators",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get information about a list of feed generators.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["feeds"],
11 | "properties": {
12 | "feeds": {
13 | "type": "array",
14 | "items": { "type": "string", "format": "at-uri" }
15 | }
16 | }
17 | },
18 | "output": {
19 | "encoding": "application/json",
20 | "schema": {
21 | "type": "object",
22 | "required": ["feeds"],
23 | "properties": {
24 | "feeds": {
25 | "type": "array",
26 | "items": {
27 | "type": "ref",
28 | "ref": "app.bsky.feed.defs#generatorView"
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/model/TimelinePostReason.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.model
2 |
3 | import app.bsky.feed.FeedViewPostReasonUnion
4 | import sh.christian.ozone.model.TimelinePostReason.TimelinePostPin
5 | import sh.christian.ozone.model.TimelinePostReason.TimelinePostRepost
6 |
7 | sealed interface TimelinePostReason {
8 | data class TimelinePostRepost(
9 | val repostAuthor: Profile,
10 | val indexedAt: Moment,
11 | ) : TimelinePostReason
12 |
13 | data object TimelinePostPin : TimelinePostReason
14 | }
15 |
16 | fun FeedViewPostReasonUnion.toReasonOrNull(): TimelinePostReason? {
17 | return when (this) {
18 | is FeedViewPostReasonUnion.ReasonRepost -> {
19 | TimelinePostRepost(
20 | repostAuthor = value.by.toProfile(),
21 | indexedAt = Moment(value.indexedAt),
22 | )
23 | }
24 | is FeedViewPostReasonUnion.ReasonPin -> {
25 | TimelinePostPin
26 | }
27 | is FeedViewPostReasonUnion.Unknown -> {
28 | null
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/jetstream/src/jsMain/kotlin/sh/christian/ozone/jetstream/strings.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.jetstream
2 |
3 | /**
4 | * Encodes a URI by replacing each instance of certain characters by one, two, three, or four escape sequences
5 | * representing the UTF-8 encoding of the character (will only be four escape sequences for characters composed of two
6 | * surrogate characters).
7 | */
8 | internal external fun encodeURIComponent(encodedURI: String): String
9 |
10 | /**
11 | * Decodes a Uniform Resource Identifier (URI) component previously created by [encodeURIComponent].
12 | */
13 | internal external fun decodeURIComponent(encodedURI: String): String
14 |
15 | /**
16 | * Computes a new string in which certain characters have been replaced by hexadecimal escape sequences.
17 | */
18 | internal external fun escape(str: String): String
19 |
20 | /**
21 | * Computes a new string in which hexadecimal escape sequences are replaced with the characters that they represent.
22 | */
23 | internal external fun unescape(str: String): String
24 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/set/addValues.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.set.addValues",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Add values to a specific set. Attempting to add values to a set that does not exist will result in an error.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["name", "values"],
13 | "properties": {
14 | "name": {
15 | "type": "string",
16 | "description": "Name of the set to add values to"
17 | },
18 | "values": {
19 | "type": "array",
20 | "minLength": 1,
21 | "maxLength": 1000,
22 | "items": {
23 | "type": "string"
24 | },
25 | "description": "Array of string values to add to the set"
26 | }
27 | }
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/set/deleteSet.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.set.deleteSet",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Delete an entire set. Attempting to delete a set that does not exist will result in an error.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["name"],
13 | "properties": {
14 | "name": {
15 | "type": "string",
16 | "description": "Name of the set to delete"
17 | }
18 | }
19 | }
20 | },
21 | "output": {
22 | "encoding": "application/json",
23 | "schema": {
24 | "type": "object",
25 | "properties": {}
26 | }
27 | },
28 | "errors": [
29 | {
30 | "name": "SetNotFound",
31 | "description": "set with the given name does not exist"
32 | }
33 | ]
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
7 | }
8 | }
9 |
10 | @Suppress("UnstableApiUsage")
11 | dependencyResolutionManagement {
12 | repositories {
13 | google()
14 | mavenCentral()
15 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
16 | }
17 | }
18 |
19 | plugins {
20 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
21 | }
22 |
23 | rootProject.name = "ozone"
24 |
25 | // Sample app modules
26 | include(":app:android")
27 | include(":app:common")
28 | include(":app:desktop")
29 | include(":app:ios")
30 | include(":app:store")
31 | include(":app:web")
32 |
33 | // Published artifact modules
34 | include(":api-gen-runtime")
35 | include(":api-gen-runtime-internal")
36 | include(":bluesky")
37 | include(":jetstream")
38 | include(":lexicons")
39 | include(":oauth")
40 |
41 | includeBuild("build-logic")
42 | includeBuild("generator")
43 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/unspecced/getSuggestedStarterPacks.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.unspecced.getSuggestedStarterPacks",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get a list of suggested starterpacks",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {
11 | "limit": {
12 | "type": "integer",
13 | "minimum": 1,
14 | "maximum": 25,
15 | "default": 10
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["starterPacks"],
24 | "properties": {
25 | "starterPacks": {
26 | "type": "array",
27 | "items": {
28 | "type": "ref",
29 | "ref": "app.bsky.graph.defs#starterPackView"
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/api-gen-runtime/src/commonMain/kotlin/sh/christian/ozone/api/response/AtpErrorDescription.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalSerializationApi::class)
2 |
3 | package sh.christian.ozone.api.response
4 |
5 | import kotlinx.serialization.ExperimentalSerializationApi
6 | import kotlinx.serialization.Serializable
7 | import kotlinx.serialization.json.JsonNames
8 |
9 | /**
10 | * Description of an unsuccessful response.
11 | *
12 | * The error type should map to an error name defined in the endpoint's Lexicon schema. This enables more specific
13 | * error-handling by client software. This is particularly encouraged on `400`, `500`, and `502` status codes, where
14 | * further information will be useful.
15 | */
16 | @Serializable
17 | data class AtpErrorDescription(
18 | /**
19 | * Type name of the error (generic ASCII constant, no whitespace).
20 | */
21 | val error: String,
22 | /**
23 | * Description of the error, appropriate for display to humans.
24 | */
25 | @JsonNames("message", "error_description")
26 | val message: String? = null,
27 | )
28 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/unspecced/getConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.unspecced.getConfig",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get miscellaneous runtime configuration.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": [],
13 | "properties": {
14 | "checkEmailConfirmed": { "type": "boolean" },
15 | "liveNow": {
16 | "type": "array",
17 | "items": { "type": "ref", "ref": "#liveNowConfig" }
18 | }
19 | }
20 | }
21 | }
22 | },
23 | "liveNowConfig": {
24 | "type": "object",
25 | "required": ["did", "domains"],
26 | "properties": {
27 | "did": { "type": "string", "format": "did" },
28 | "domains": {
29 | "type": "array",
30 | "items": {
31 | "type": "string"
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/actor/getProfiles.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.actor.getProfiles",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get detailed profile views of multiple actors.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["actors"],
11 | "properties": {
12 | "actors": {
13 | "type": "array",
14 | "items": { "type": "string", "format": "at-identifier" },
15 | "maxLength": 25
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["profiles"],
24 | "properties": {
25 | "profiles": {
26 | "type": "array",
27 | "items": {
28 | "type": "ref",
29 | "ref": "app.bsky.actor.defs#profileViewDetailed"
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/getStarterPacks.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.getStarterPacks",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get views for a list of starter packs.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["uris"],
11 | "properties": {
12 | "uris": {
13 | "type": "array",
14 | "items": { "type": "string", "format": "at-uri" },
15 | "maxLength": 25
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["starterPacks"],
24 | "properties": {
25 | "starterPacks": {
26 | "type": "array",
27 | "items": {
28 | "type": "ref",
29 | "ref": "app.bsky.graph.defs#starterPackViewBasic"
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/temp/fetchLabels.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.temp.fetchLabels",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "DEPRECATED: use queryLabels or subscribeLabels instead -- Fetch all labels from a labeler created after a certain date.",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {
11 | "since": { "type": "integer" },
12 | "limit": {
13 | "type": "integer",
14 | "minimum": 1,
15 | "maximum": 250,
16 | "default": 50
17 | }
18 | }
19 | },
20 | "output": {
21 | "encoding": "application/json",
22 | "schema": {
23 | "type": "object",
24 | "required": ["labels"],
25 | "properties": {
26 | "labels": {
27 | "type": "array",
28 | "items": { "type": "ref", "ref": "com.atproto.label.defs#label" }
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/getListBlocks.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.getListBlocks",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get mod lists that the requesting account (actor) is blocking. Requires auth.",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {
11 | "limit": {
12 | "type": "integer",
13 | "minimum": 1,
14 | "maximum": 100,
15 | "default": 50
16 | },
17 | "cursor": { "type": "string" }
18 | }
19 | },
20 | "output": {
21 | "encoding": "application/json",
22 | "schema": {
23 | "type": "object",
24 | "required": ["lists"],
25 | "properties": {
26 | "cursor": { "type": "string" },
27 | "lists": {
28 | "type": "array",
29 | "items": { "type": "ref", "ref": "app.bsky.graph.defs#listView" }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lexicons/schemas/com/atproto/identity/getRecommendedDidCredentials.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "com.atproto.identity.getRecommendedDidCredentials",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Describe the credentials that should be included in the DID doc of an account that is migrating to this service.",
8 | "output": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "properties": {
13 | "rotationKeys": {
14 | "description": "Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs.",
15 | "type": "array",
16 | "items": { "type": "string" }
17 | },
18 | "alsoKnownAs": {
19 | "type": "array",
20 | "items": { "type": "string" }
21 | },
22 | "verificationMethods": { "type": "unknown" },
23 | "services": { "type": "unknown" }
24 | }
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.unspecced.getOnboardingSuggestedStarterPacks",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get a list of suggested starterpacks for onboarding",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {
11 | "limit": {
12 | "type": "integer",
13 | "minimum": 1,
14 | "maximum": 25,
15 | "default": 10
16 | }
17 | }
18 | },
19 | "output": {
20 | "encoding": "application/json",
21 | "schema": {
22 | "type": "object",
23 | "required": ["starterPacks"],
24 | "properties": {
25 | "starterPacks": {
26 | "type": "array",
27 | "items": {
28 | "type": "ref",
29 | "ref": "app.bsky.graph.defs#starterPackView"
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/home/HomeState.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.home
2 |
3 | import sh.christian.ozone.compose.ComposePostProps
4 | import sh.christian.ozone.profile.ProfileProps
5 | import sh.christian.ozone.thread.ThreadProps
6 | import sh.christian.ozone.timeline.TimelineProps
7 |
8 | sealed interface HomeState {
9 |
10 | sealed interface InTab : HomeState {
11 | data class InTimeline(
12 | val props: TimelineProps,
13 | ) : InTab
14 |
15 | object InNotifications : InTab
16 |
17 | object InSettings : InTab
18 | }
19 |
20 | sealed interface InSubScreen : HomeState {
21 | val inTabState: InTab
22 |
23 | data class InProfile(
24 | val props: ProfileProps,
25 | override val inTabState: InTab,
26 | ) : InSubScreen
27 |
28 | data class InThread(
29 | val props: ThreadProps,
30 | override val inTabState: InTab,
31 | ) : InSubScreen
32 |
33 | data class InComposePost(
34 | val props: ComposePostProps,
35 | override val inTabState: InTab,
36 | ) : InSubScreen
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/graph/getListMutes.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.graph.getListMutes",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth.",
8 | "parameters": {
9 | "type": "params",
10 | "properties": {
11 | "limit": {
12 | "type": "integer",
13 | "minimum": 1,
14 | "maximum": 100,
15 | "default": 50
16 | },
17 | "cursor": { "type": "string" }
18 | }
19 | },
20 | "output": {
21 | "encoding": "application/json",
22 | "schema": {
23 | "type": "object",
24 | "required": ["lists"],
25 | "properties": {
26 | "cursor": { "type": "string" },
27 | "lists": {
28 | "type": "array",
29 | "items": { "type": "ref", "ref": "app.bsky.graph.defs#listView" }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/notification/registerPush.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.notification.registerPush",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Register to receive push notifications, via a specified service, for the requesting account. Requires auth.",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["serviceDid", "token", "platform", "appId"],
13 | "properties": {
14 | "serviceDid": { "type": "string", "format": "did" },
15 | "token": { "type": "string" },
16 | "platform": {
17 | "type": "string",
18 | "knownValues": ["ios", "android", "web"]
19 | },
20 | "appId": { "type": "string" },
21 | "ageRestricted": {
22 | "type": "boolean",
23 | "description": "Set to true when the actor is age restricted"
24 | }
25 | }
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/scripts/bump_version.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | function replace_in_file {
6 | local replacement="${1}"
7 | local file="${2}"
8 | sed -i.bak "${replacement}" "${file}"
9 | rm "${file}.bak"
10 | }
11 |
12 | # Strip leading 'v' if present
13 | NEXT_RELEASE="${1-}"
14 | NEXT_RELEASE="${NEXT_RELEASE#v}"
15 |
16 | if [[ -z "${NEXT_RELEASE}" ]]; then
17 | echo "Usage: $0 " >&2
18 | exit 1
19 | elif ! [[ "${NEXT_RELEASE}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
20 | echo "Error: '${NEXT_RELEASE}' is not a valid semantic version." >&2
21 | echo "Usage: $0 " >&2
22 | exit 1
23 | fi
24 |
25 | LAST_RELEASE="$(awk -F\" '/sh.christian.ozone:bluesky/ { print $4 }' < gradle/libs.versions.toml)"
26 |
27 | properties_files="$(find . -name gradle.properties)"
28 | for file in $properties_files; do
29 | replace_in_file "s/POM_VERSION=.*/POM_VERSION=$NEXT_RELEASE/g" "${file}"
30 | done
31 |
32 | replace_in_file "s/$LAST_RELEASE/$NEXT_RELEASE/g" gradle/libs.versions.toml
33 | replace_in_file "s/$LAST_RELEASE/$NEXT_RELEASE/g" README.md
34 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/setting/removeOptions.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.setting.removeOptions",
4 | "defs": {
5 | "main": {
6 | "type": "procedure",
7 | "description": "Delete settings by key",
8 | "input": {
9 | "encoding": "application/json",
10 | "schema": {
11 | "type": "object",
12 | "required": ["keys", "scope"],
13 | "properties": {
14 | "keys": {
15 | "type": "array",
16 | "minLength": 1,
17 | "maxLength": 200,
18 | "items": {
19 | "type": "string",
20 | "format": "nsid"
21 | }
22 | },
23 | "scope": {
24 | "type": "string",
25 | "knownValues": ["instance", "personal"]
26 | }
27 | }
28 | }
29 | },
30 | "output": {
31 | "encoding": "application/json",
32 | "schema": {
33 | "type": "object",
34 | "properties": {}
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/common/src/commonMain/kotlin/sh/christian/ozone/timeline/TimelineState.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.timeline
2 |
3 | import sh.christian.ozone.error.ErrorProps
4 | import sh.christian.ozone.model.FullProfile
5 | import sh.christian.ozone.model.Timeline
6 | import sh.christian.ozone.ui.compose.OpenImageAction
7 |
8 | sealed interface TimelineState {
9 | val profile: FullProfile?
10 | val timeline: Timeline?
11 |
12 | data class FetchingTimeline(
13 | override val profile: FullProfile?,
14 | override val timeline: Timeline?,
15 | val fullRefresh: Boolean,
16 | ) : TimelineState
17 |
18 | data class ShowingTimeline(
19 | override val profile: FullProfile,
20 | override val timeline: Timeline,
21 | val showRefreshPrompt: Boolean,
22 | ) : TimelineState
23 |
24 | data class ShowingFullSizeImage(
25 | val previousState: TimelineState,
26 | val openImageAction: OpenImageAction,
27 | ) : TimelineState by previousState
28 |
29 | data class ShowingError(
30 | val previousState: TimelineState,
31 | val props: ErrorProps,
32 | ) : TimelineState by previousState
33 | }
34 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/moderation/getSubjects.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.moderation.getSubjects",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get details about subjects.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["subjects"],
11 | "properties": {
12 | "subjects": {
13 | "type": "array",
14 | "maxLength": 100,
15 | "minLength": 1,
16 | "items": {
17 | "type": "string"
18 | }
19 | }
20 | }
21 | },
22 | "output": {
23 | "encoding": "application/json",
24 | "schema": {
25 | "type": "object",
26 | "required": ["subjects"],
27 | "properties": {
28 | "subjects": {
29 | "type": "array",
30 | "items": {
31 | "type": "ref",
32 | "ref": "tools.ozone.moderation.defs#subjectView"
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/signature/findCorrelation.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.signature.findCorrelation",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Find all correlated threat signatures between 2 or more accounts.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["dids"],
11 | "properties": {
12 | "dids": {
13 | "type": "array",
14 | "items": {
15 | "type": "string",
16 | "format": "did"
17 | }
18 | }
19 | }
20 | },
21 | "output": {
22 | "encoding": "application/json",
23 | "schema": {
24 | "type": "object",
25 | "required": ["details"],
26 | "properties": {
27 | "details": {
28 | "type": "array",
29 | "items": {
30 | "type": "ref",
31 | "ref": "tools.ozone.signature.defs#sigDetail"
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lexicons/schemas/tools/ozone/moderation/getReporterStats.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "tools.ozone.moderation.getReporterStats",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Get reporter stats for a list of users.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["dids"],
11 | "properties": {
12 | "dids": {
13 | "type": "array",
14 | "maxLength": 100,
15 | "items": {
16 | "type": "string",
17 | "format": "did"
18 | }
19 | }
20 | }
21 | },
22 | "output": {
23 | "encoding": "application/json",
24 | "schema": {
25 | "type": "object",
26 | "required": ["stats"],
27 | "properties": {
28 | "stats": {
29 | "type": "array",
30 | "items": {
31 | "type": "ref",
32 | "ref": "tools.ozone.moderation.defs#reporterStats"
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/common/src/jsMain/kotlin/sh/christian/ozone/ui/DynamicDarkMode.js.kt:
--------------------------------------------------------------------------------
1 | package sh.christian.ozone.ui
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import androidx.compose.runtime.LaunchedEffect
6 | import androidx.compose.runtime.getValue
7 | import androidx.compose.runtime.mutableStateOf
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.runtime.setValue
10 | import kotlinx.coroutines.delay
11 | import kotlinx.coroutines.isActive
12 | import org.jetbrains.skiko.SystemTheme
13 | import org.jetbrains.skiko.currentSystemTheme
14 | import kotlin.time.Duration.Companion.seconds
15 |
16 | @Composable
17 | actual fun DynamicDarkMode(content: @Composable () -> Unit) {
18 | var isInDarkMode by remember { mutableStateOf(currentSystemTheme == SystemTheme.DARK) }
19 |
20 | LaunchedEffect(Unit) {
21 | while (isActive) {
22 | delay(1.seconds)
23 | isInDarkMode = currentSystemTheme == SystemTheme.DARK
24 | }
25 | }
26 |
27 | CompositionLocalProvider(LocalColorTheme provides ColorTheme(isInDarkMode)) {
28 | content()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lexicons/schemas/app/bsky/feed/getPosts.json:
--------------------------------------------------------------------------------
1 | {
2 | "lexicon": 1,
3 | "id": "app.bsky.feed.getPosts",
4 | "defs": {
5 | "main": {
6 | "type": "query",
7 | "description": "Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'.",
8 | "parameters": {
9 | "type": "params",
10 | "required": ["uris"],
11 | "properties": {
12 | "uris": {
13 | "type": "array",
14 | "description": "List of post AT-URIs to return hydrated views for.",
15 | "items": { "type": "string", "format": "at-uri" },
16 | "maxLength": 25
17 | }
18 | }
19 | },
20 | "output": {
21 | "encoding": "application/json",
22 | "schema": {
23 | "type": "object",
24 | "required": ["posts"],
25 | "properties": {
26 | "posts": {
27 | "type": "array",
28 | "items": { "type": "ref", "ref": "app.bsky.feed.defs#postView" }
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------