├── 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 | --------------------------------------------------------------------------------