├── .editorconfig
├── .github
├── assets
│ ├── chat.png
│ ├── drawer.png
│ ├── themed_chat.png
│ └── themed_drawer.png
└── workflows
│ ├── android.yml
│ └── crowdin.yml
├── .gitignore
├── LICENSE
├── README.md
├── STATUS.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── xinto
│ │ └── opencord
│ │ ├── OpenCord.kt
│ │ ├── ast
│ │ ├── node
│ │ │ ├── ChannelMentionNode.kt
│ │ │ ├── EmoteNode.kt
│ │ │ ├── SpoilerNode.kt
│ │ │ ├── StyledNode.kt
│ │ │ ├── TextNode.kt
│ │ │ └── UserMentionNode.kt
│ │ ├── rule
│ │ │ ├── DiscordRules.kt
│ │ │ ├── MarkdownRules.kt
│ │ │ └── OtherRules.kt
│ │ └── util
│ │ │ ├── Patterns.kt
│ │ │ └── Rules.kt
│ │ ├── db
│ │ ├── Converters.kt
│ │ ├── dao
│ │ │ ├── AccountsDao.kt
│ │ │ ├── AttachmentsDao.kt
│ │ │ ├── ChannelsDao.kt
│ │ │ ├── EmbedsDao.kt
│ │ │ ├── GuildsDao.kt
│ │ │ ├── MessagesDao.kt
│ │ │ ├── ReactionsDao.kt
│ │ │ ├── UnreadStatesDao.kt
│ │ │ └── UsersDao.kt
│ │ ├── database
│ │ │ ├── AccountDatabase.kt
│ │ │ └── CacheDatabase.kt
│ │ └── entity
│ │ │ ├── EntityAccount.kt
│ │ │ ├── channel
│ │ │ ├── EntityChannel.kt
│ │ │ └── EntityUnreadState.kt
│ │ │ ├── guild
│ │ │ └── EntityGuild.kt
│ │ │ ├── message
│ │ │ ├── EntityAttachment.kt
│ │ │ ├── EntityEmbed.kt
│ │ │ └── EntityMessage.kt
│ │ │ ├── reactions
│ │ │ └── EntityReaction.kt
│ │ │ └── user
│ │ │ └── EntityUser.kt
│ │ ├── di
│ │ ├── DatabaseModule.kt
│ │ ├── GatewayModule.kt
│ │ ├── HCaptcha.kt
│ │ ├── HttpModule.kt
│ │ ├── LoggerModule.kt
│ │ ├── ManagerModule.kt
│ │ ├── ProviderModule.kt
│ │ ├── ServiceModule.kt
│ │ ├── SimpleASTModule.kt
│ │ ├── StoreModule.kt
│ │ └── ViewModelModule.kt
│ │ ├── domain
│ │ ├── Mentionable.kt
│ │ ├── activity
│ │ │ ├── ActivityType.kt
│ │ │ ├── DomainActivity.kt
│ │ │ ├── DomainActivityAssets.kt
│ │ │ ├── DomainActivityButton.kt
│ │ │ ├── DomainActivityMetadata.kt
│ │ │ ├── DomainActivityParty.kt
│ │ │ ├── DomainActivitySecrets.kt
│ │ │ ├── DomainActivityTimestamp.kt
│ │ │ └── types
│ │ │ │ ├── DomainActivityCustom.kt
│ │ │ │ ├── DomainActivityGame.kt
│ │ │ │ ├── DomainActivityListening.kt
│ │ │ │ ├── DomainActivityStreaming.kt
│ │ │ │ └── DomainActivityUnknown.kt
│ │ ├── attachment
│ │ │ ├── DomainAttachment.kt
│ │ │ ├── DomainFileAttachment.kt
│ │ │ ├── DomainPictureAttachment.kt
│ │ │ └── DomainVideoAttachment.kt
│ │ ├── channel
│ │ │ ├── DomainAnnouncementChannel.kt
│ │ │ ├── DomainCategoryChannel.kt
│ │ │ ├── DomainChannel.kt
│ │ │ ├── DomainTextChannel.kt
│ │ │ ├── DomainUnreadState.kt
│ │ │ └── DomainVoiceChannel.kt
│ │ ├── embed
│ │ │ ├── DomainEmbed.kt
│ │ │ └── DomainEmbedFooter.kt
│ │ ├── emoji
│ │ │ ├── DomainEmoji.kt
│ │ │ ├── DomainGuildEmoji.kt
│ │ │ ├── DomainUnicodeEmoji.kt
│ │ │ └── DomainUnknownEmoji.kt
│ │ ├── guild
│ │ │ └── DomainGuild.kt
│ │ ├── login
│ │ │ └── DomainLogin.kt
│ │ ├── member
│ │ │ └── DomainGuildMember.kt
│ │ ├── message
│ │ │ ├── DomainMessage.kt
│ │ │ ├── DomainMessageMemberJoin.kt
│ │ │ ├── DomainMessageRegular.kt
│ │ │ ├── DomainMessageUnknown.kt
│ │ │ └── reaction
│ │ │ │ └── DomainReaction.kt
│ │ ├── permissions
│ │ │ └── DomainPermission.kt
│ │ ├── user
│ │ │ ├── DomainUser.kt
│ │ │ ├── DomainUserPrivate.kt
│ │ │ ├── DomainUserPrivateReady.kt
│ │ │ └── DomainUserPublic.kt
│ │ └── usersettings
│ │ │ ├── DomainCustomStatus.kt
│ │ │ ├── DomainFriendSources.kt
│ │ │ ├── DomainGuildFolder.kt
│ │ │ ├── DomainThemeSetting.kt
│ │ │ ├── DomainUserSettings.kt
│ │ │ └── DomainUserStatus.kt
│ │ ├── gateway
│ │ ├── CloseCode.kt
│ │ ├── DiscordGateway.kt
│ │ ├── dto
│ │ │ ├── GuildDeleteData.kt
│ │ │ ├── Heartbeat.kt
│ │ │ ├── Identification.kt
│ │ │ ├── MessageAckData.kt
│ │ │ ├── MessageDeleteData.kt
│ │ │ ├── Presence.kt
│ │ │ ├── Reaction.kt
│ │ │ ├── Ready.kt
│ │ │ ├── RequestGuildMembers.kt
│ │ │ ├── Resume.kt
│ │ │ └── SessionsReplaceData.kt
│ │ ├── event
│ │ │ ├── Channel.kt
│ │ │ ├── Event.kt
│ │ │ ├── Guild.kt
│ │ │ ├── GuildMemberChunk.kt
│ │ │ ├── Message.kt
│ │ │ ├── Reaction.kt
│ │ │ ├── Ready.kt
│ │ │ ├── Sessions.kt
│ │ │ ├── UserSettings.kt
│ │ │ └── UserUpdateEvent.kt
│ │ └── io
│ │ │ ├── Capabilities.kt
│ │ │ ├── EventName.kt
│ │ │ ├── OpCode.kt
│ │ │ └── Payload.kt
│ │ ├── manager
│ │ ├── AccountCookieManager.kt
│ │ ├── AccountManager.kt
│ │ ├── ActivityManager.kt
│ │ ├── ClipboardManager.kt
│ │ ├── DownloadManager.kt
│ │ ├── PersistentDataManager.kt
│ │ ├── ToastManager.kt
│ │ └── base
│ │ │ └── BasePreferenceManager.kt
│ │ ├── provider
│ │ ├── PropertyProvider.kt
│ │ └── TelemetryProvider.kt
│ │ ├── rest
│ │ ├── body
│ │ │ ├── Login.kt
│ │ │ ├── Message.kt
│ │ │ ├── TwoFactor.kt
│ │ │ └── XSuperProperties.kt
│ │ ├── models
│ │ │ ├── ApiAttachment.kt
│ │ │ ├── ApiChannel.kt
│ │ │ ├── ApiColor.kt
│ │ │ ├── ApiExperiments.kt
│ │ │ ├── ApiGuild.kt
│ │ │ ├── ApiGuildMemberChunk.kt
│ │ │ ├── ApiPermissions.kt
│ │ │ ├── GuildMember.kt
│ │ │ ├── Snowflake.kt
│ │ │ ├── activity
│ │ │ │ ├── ApiActivity.kt
│ │ │ │ ├── ApiActivityAssets.kt
│ │ │ │ ├── ApiActivityButton.kt
│ │ │ │ ├── ApiActivityMetadata.kt
│ │ │ │ ├── ApiActivityParty.kt
│ │ │ │ ├── ApiActivitySecrets.kt
│ │ │ │ └── ApiActivityTimestamp.kt
│ │ │ ├── embed
│ │ │ │ ├── ApiEmbed.kt
│ │ │ │ ├── ApiEmbedAuthor.kt
│ │ │ │ ├── ApiEmbedField.kt
│ │ │ │ ├── ApiEmbedFooter.kt
│ │ │ │ ├── ApiEmbedMedia.kt
│ │ │ │ └── ApiEmbedProvider.kt
│ │ │ ├── emoji
│ │ │ │ └── ApiEmoji.kt
│ │ │ ├── login
│ │ │ │ ├── ApiLogin.kt
│ │ │ │ └── ApiLoginUserSettings.kt
│ │ │ ├── message
│ │ │ │ ├── ApiMessage.kt
│ │ │ │ └── ApiMessageType.kt
│ │ │ ├── reaction
│ │ │ │ └── ApiReaction.kt
│ │ │ └── user
│ │ │ │ ├── ApiUser.kt
│ │ │ │ └── settings
│ │ │ │ ├── ApiCustomStatus.kt
│ │ │ │ ├── ApiFriendSources.kt
│ │ │ │ ├── ApiGuildFolder.kt
│ │ │ │ └── ApiUserSettings.kt
│ │ └── service
│ │ │ ├── DiscordApiService.kt
│ │ │ ├── DiscordAuthService.kt
│ │ │ └── DiscordCdnService.kt
│ │ ├── store
│ │ ├── ChannelStore.kt
│ │ ├── CurrentUserStore.kt
│ │ ├── Event.kt
│ │ ├── GuildStore.kt
│ │ ├── LastMessageStore.kt
│ │ ├── MessageStore.kt
│ │ ├── ReactionStore.kt
│ │ ├── SessionStore.kt
│ │ ├── UnreadStore.kt
│ │ └── UserSettingsStore.kt
│ │ ├── ui
│ │ ├── AppActivity.kt
│ │ ├── LoginActivity.kt
│ │ ├── SplashScreenActivity.kt
│ │ ├── components
│ │ │ ├── OCBadgeBox.kt
│ │ │ ├── OCImage.kt
│ │ │ ├── OCTextField.kt
│ │ │ ├── attachment
│ │ │ │ ├── AttachmentPicture.kt
│ │ │ │ └── AttachmentVideo.kt
│ │ │ ├── captcha
│ │ │ │ └── HCaptcha.kt
│ │ │ ├── channel
│ │ │ │ └── list
│ │ │ │ │ ├── ChannelListCategoryItem.kt
│ │ │ │ │ └── ChannelListRegularItem.kt
│ │ │ ├── embed
│ │ │ │ ├── Embed.kt
│ │ │ │ ├── EmbedAuthor.kt
│ │ │ │ ├── EmbedField.kt
│ │ │ │ ├── EmbedFooter.kt
│ │ │ │ ├── EmbedVideo.kt
│ │ │ │ └── SpotifyEmbed.kt
│ │ │ ├── guild
│ │ │ │ └── list
│ │ │ │ │ ├── GuildsListHeaderItem.kt
│ │ │ │ │ ├── GuildsListItemImage.kt
│ │ │ │ │ ├── GuildsListRegularItem.kt
│ │ │ │ │ └── GuildsListTextItem.kt
│ │ │ ├── indicator
│ │ │ │ ├── MentionCountBadge.kt
│ │ │ │ ├── UnreadIndicator.kt
│ │ │ │ └── UserStatusIcon.kt
│ │ │ └── message
│ │ │ │ ├── MessageAuthor.kt
│ │ │ │ ├── MessageAvatar.kt
│ │ │ │ ├── MessageContent.kt
│ │ │ │ ├── MessageReaction.kt
│ │ │ │ ├── MessageRegular.kt
│ │ │ │ └── reply
│ │ │ │ ├── MessageReferenced.kt
│ │ │ │ ├── MessageReferencedAuthor.kt
│ │ │ │ ├── MessageReferencedContent.kt
│ │ │ │ └── MessageReplyBranch.kt
│ │ ├── navigation
│ │ │ ├── AppDestination.kt
│ │ │ └── LoginDestination.kt
│ │ ├── screens
│ │ │ ├── SettingsScreen.kt
│ │ │ ├── home
│ │ │ │ ├── HomeScreen.kt
│ │ │ │ └── panels
│ │ │ │ │ ├── HomeNavButtons.kt
│ │ │ │ │ ├── channel
│ │ │ │ │ ├── ChannelsList.kt
│ │ │ │ │ ├── ChannelsListLoaded.kt
│ │ │ │ │ ├── ChannelsListLoading.kt
│ │ │ │ │ └── ChannelsListUnselected.kt
│ │ │ │ │ ├── chat
│ │ │ │ │ ├── Chat.kt
│ │ │ │ │ ├── ChatError.kt
│ │ │ │ │ ├── ChatLoaded.kt
│ │ │ │ │ ├── ChatLoading.kt
│ │ │ │ │ └── ChatUnselected.kt
│ │ │ │ │ ├── chatinput
│ │ │ │ │ └── ChatInput.kt
│ │ │ │ │ ├── currentuser
│ │ │ │ │ ├── CurrentUser.kt
│ │ │ │ │ ├── CurrentUserContent.kt
│ │ │ │ │ ├── CurrentUserLoaded.kt
│ │ │ │ │ ├── CurrentUserLoading.kt
│ │ │ │ │ └── sheet
│ │ │ │ │ │ └── CurrentUserSheet.kt
│ │ │ │ │ ├── guild
│ │ │ │ │ ├── GuildsList.kt
│ │ │ │ │ ├── GuildsListLoaded.kt
│ │ │ │ │ └── GuildsListLoading.kt
│ │ │ │ │ ├── member
│ │ │ │ │ └── MembersList.kt
│ │ │ │ │ └── messagemenu
│ │ │ │ │ ├── MessageMenu.kt
│ │ │ │ │ ├── MessageMenuLoaded.kt
│ │ │ │ │ ├── MessageMenuLoading.kt
│ │ │ │ │ └── MessageMenuPreviewMessage.kt
│ │ │ ├── login
│ │ │ │ ├── LoginLandingScreen.kt
│ │ │ │ └── LoginScreen.kt
│ │ │ ├── mentions
│ │ │ │ └── MentionsScreen.kt
│ │ │ └── pins
│ │ │ │ ├── PinsScreen.kt
│ │ │ │ ├── PinsScreenError.kt
│ │ │ │ ├── PinsScreenLoaded.kt
│ │ │ │ └── PinsScreenLoading.kt
│ │ ├── theme
│ │ │ ├── Color.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ ├── util
│ │ │ ├── Animation.kt
│ │ │ ├── Composable.kt
│ │ │ ├── CompositePaddingValues.kt
│ │ │ ├── ContentAlpha.kt
│ │ │ ├── CornerBasedShape.kt
│ │ │ ├── GestureNavigation.kt
│ │ │ ├── LocalViewModel.kt
│ │ │ ├── MessageInlineContent.kt
│ │ │ ├── NavUtil.kt
│ │ │ ├── ScrollableWebView.kt
│ │ │ ├── UnsafeImmutableList.kt
│ │ │ └── VoidablePaddingValues.kt
│ │ └── viewmodel
│ │ │ ├── ChannelPinsViewModel.kt
│ │ │ ├── ChannelsViewModel.kt
│ │ │ ├── ChatInputViewModel.kt
│ │ │ ├── ChatViewModel.kt
│ │ │ ├── CurrentUserViewModel.kt
│ │ │ ├── GuildsViewModel.kt
│ │ │ ├── LoginViewModel.kt
│ │ │ ├── MentionsViewModel.kt
│ │ │ ├── MessageMenuViewModel.kt
│ │ │ └── base
│ │ │ └── BasePersistenceViewModel.kt
│ │ └── util
│ │ ├── DiscordLocale.kt
│ │ ├── FlowUtil.kt
│ │ ├── KtorUtil.kt
│ │ ├── Logger.kt
│ │ ├── NonceGenerator.kt
│ │ ├── Quad.kt
│ │ ├── StdUtil.kt
│ │ ├── TextParser.kt
│ │ ├── Throttling.kt
│ │ └── Timestamp.kt
│ └── res
│ ├── drawable
│ ├── ic_account_switch.xml
│ ├── ic_add.xml
│ ├── ic_add_reaction.xml
│ ├── ic_arrow_back.xml
│ ├── ic_at_sign.xml
│ ├── ic_cancel.xml
│ ├── ic_channel_announcements.xml
│ ├── ic_delete.xml
│ ├── ic_discord_logo.xml
│ ├── ic_discord_logo_text.xml
│ ├── ic_edit.xml
│ ├── ic_error.xml
│ ├── ic_file_copy.xml
│ ├── ic_filter.xml
│ ├── ic_group.xml
│ ├── ic_guild_badge_premium_tier_1.xml
│ ├── ic_guild_badge_premium_tier_1_banner.xml
│ ├── ic_guild_badge_premium_tier_2.xml
│ ├── ic_guild_badge_premium_tier_2_banner.xml
│ ├── ic_guild_badge_premium_tier_3.xml
│ ├── ic_guild_badge_premium_tier_3_banner.xml
│ ├── ic_keyboard_arrow_down.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_launcher_foreground_no_shadow.xml
│ ├── ic_link.xml
│ ├── ic_mark_unread.xml
│ ├── ic_menu.xml
│ ├── ic_people.xml
│ ├── ic_pin.xml
│ ├── ic_reply.xml
│ ├── ic_search.xml
│ ├── ic_set_custom_status.xml
│ ├── ic_settings.xml
│ ├── ic_status_dnd.xml
│ ├── ic_status_idle.xml
│ ├── ic_status_invisible.xml
│ ├── ic_status_online.xml
│ ├── ic_status_streaming.xml
│ ├── ic_tag.xml
│ └── ic_volume_up.xml
│ ├── font
│ ├── inter_black.ttf
│ ├── inter_bold.ttf
│ ├── inter_extrabold.ttf
│ ├── inter_extralight.ttf
│ ├── inter_light.ttf
│ ├── inter_medium.ttf
│ ├── inter_regular.ttf
│ ├── inter_semibold.ttf
│ └── inter_thin.ttf
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── raw
│ └── keep.xml
│ ├── values-af-rZA
│ └── strings.xml
│ ├── values-ar-rSA
│ └── strings.xml
│ ├── values-ca-rES
│ └── strings.xml
│ ├── values-cs-rCZ
│ └── strings.xml
│ ├── values-da-rDK
│ └── strings.xml
│ ├── values-de-rDE
│ └── strings.xml
│ ├── values-el-rGR
│ └── strings.xml
│ ├── values-es-rES
│ └── strings.xml
│ ├── values-fi-rFI
│ └── strings.xml
│ ├── values-fr-rFR
│ └── strings.xml
│ ├── values-hi-rIN
│ └── strings.xml
│ ├── values-hu-rHU
│ └── strings.xml
│ ├── values-in-rID
│ └── strings.xml
│ ├── values-it-rIT
│ └── strings.xml
│ ├── values-iw-rIL
│ └── strings.xml
│ ├── values-ja-rJP
│ └── strings.xml
│ ├── values-ko-rKR
│ └── strings.xml
│ ├── values-nl-rNL
│ └── strings.xml
│ ├── values-no-rNO
│ └── strings.xml
│ ├── values-pl-rPL
│ └── strings.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-pt-rPT
│ └── strings.xml
│ ├── values-ro-rRO
│ └── strings.xml
│ ├── values-ru-rRU
│ └── strings.xml
│ ├── values-sr-rSP
│ └── strings.xml
│ ├── values-sv-rSE
│ └── strings.xml
│ ├── values-tr-rTR
│ └── strings.xml
│ ├── values-uk-rUA
│ └── strings.xml
│ ├── values-vi-rVN
│ └── strings.xml
│ ├── values-zh-rCN
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
├── build.gradle.kts
├── crowdin.yml
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── simpleast-compose
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
└── java
└── com
└── xinto
└── simpleast
├── DSL.kt
├── Node.kt
├── ParseException.kt
├── ParseSpec.kt
├── Parser.kt
├── Renderer.kt
└── Rule.kt
/.github/assets/chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/.github/assets/chat.png
--------------------------------------------------------------------------------
/.github/assets/drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/.github/assets/drawer.png
--------------------------------------------------------------------------------
/.github/assets/themed_chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/.github/assets/themed_chat.png
--------------------------------------------------------------------------------
/.github/assets/themed_drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/.github/assets/themed_drawer.png
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Build APK
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 | paths-ignore:
8 | - '**.md'
9 | pull_request:
10 | branches:
11 | - '*'
12 | paths-ignore:
13 | - '**.md'
14 | workflow_dispatch:
15 |
16 | jobs:
17 | build:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v2
21 |
22 | - name: Set up JDK 11
23 | uses: actions/setup-java@v2
24 | with:
25 | java-version: 11
26 | distribution: 'zulu'
27 | cache: 'gradle'
28 |
29 | - name: Build OpenCord
30 | run: ./gradlew app:assembleDebug
31 |
32 | - name: Upload Discord APK
33 | uses: actions/upload-artifact@v2
34 | with:
35 | name: opencord
36 | path: app/build/outputs/apk/debug/app-debug.apk
37 |
--------------------------------------------------------------------------------
/.github/workflows/crowdin.yml:
--------------------------------------------------------------------------------
1 | name: Sync Crowdin
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | paths:
8 | - 'app/src/main/res/values/strings.xml'
9 | - '.github/workflows/crowdin.yml'
10 | schedule:
11 | - cron: "0 17 * * 6" # "At 17:00 on Saturday."
12 | workflow_dispatch:
13 |
14 | jobs:
15 | sync-crowdin:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v2
20 |
21 | - name: Crowdin
22 | uses: crowdin/github-action@1.4.11
23 | with:
24 | config: crowdin.yml
25 | upload_translations: true
26 | download_translations: true
27 | push_translations: true
28 | create_pull_request: false
29 | localization_branch_name: l10n
30 | commit_message: 'chore(i18n): sync translations'
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 | CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_ID }}
34 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
35 |
36 | - name: Merge
37 | run: |
38 | sudo chmod -R ugo+rwX .
39 | git checkout master
40 | git add *
41 | git merge l10n
42 | git push
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .DS_Store
3 | .cxx
4 | .externalNativeBuild
5 | .gradle
6 | .idea
7 | /build
8 | /captures
9 | /local.properties
10 | *.log
11 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
3 | /discord
4 | /fosscord
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/OpenCord.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord
2 |
3 | import android.app.Application
4 | import com.xinto.opencord.di.*
5 | import org.koin.android.ext.koin.androidContext
6 | import org.koin.core.context.startKoin
7 |
8 | class OpenCord : Application() {
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 |
13 | startKoin {
14 | androidContext(this@OpenCord)
15 | modules(
16 | gatewayModule,
17 | httpModule,
18 | managerModule,
19 | serviceModule,
20 | simpleAstModule,
21 | viewModelModule,
22 | loggerModule,
23 | providerModule,
24 | databaseModule,
25 | storeModule,
26 | hcaptchaModule,
27 | )
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/node/ChannelMentionNode.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.node
2 |
3 | import androidx.compose.ui.text.AnnotatedString
4 | import androidx.compose.ui.text.SpanStyle
5 | import androidx.compose.ui.text.font.FontWeight
6 | import androidx.compose.ui.text.withStyle
7 | import com.xinto.opencord.ui.theme.Blurple
8 | import com.xinto.simpleast.Node
9 |
10 | class ChannelMentionNode(
11 | val channel: String?
12 | ) : Node() {
13 | //@formatter:off
14 | context(AnnotatedString.Builder)
15 | override fun render(renderContext: RC) { //@formatter:on
16 | withStyle(
17 | SpanStyle(
18 | color = Blurple,
19 | background = Blurple.copy(alpha = 0.2f),
20 | fontWeight = FontWeight.SemiBold,
21 | ),
22 | ) {
23 | append("#$channel")
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/node/EmoteNode.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.node
2 |
3 | import androidx.compose.foundation.text.appendInlineContent
4 | import androidx.compose.ui.text.AnnotatedString
5 | import com.xinto.simpleast.Node
6 |
7 | class EmoteNode(
8 | val emoteId: String
9 | ) : Node() {
10 | //@formatter:off
11 | context(AnnotatedString.Builder)
12 | override fun render(renderContext: RC) { //@formatter:on
13 | appendInlineContent("emote", emoteId)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/node/SpoilerNode.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.node
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.text.AnnotatedString
5 | import androidx.compose.ui.text.SpanStyle
6 | import androidx.compose.ui.text.withStyle
7 | import com.xinto.simpleast.Node
8 |
9 | class SpoilerNode : Node.Parent() {
10 | //@formatter:off
11 | context(AnnotatedString.Builder)
12 | override fun render(renderContext: RC) { //@formatter:on
13 | withStyle(
14 | SpanStyle(
15 | background = Color.Black,
16 | ),
17 | ) {
18 | super.render(renderContext)
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/node/StyledNode.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.node
2 |
3 | import androidx.compose.ui.text.AnnotatedString
4 | import androidx.compose.ui.text.SpanStyle
5 | import androidx.compose.ui.text.withStyle
6 | import com.xinto.simpleast.Node
7 |
8 | class StyledNode(
9 | val styles: Collection,
10 | ) : Node.Parent() {
11 | //@formatter:off
12 | context(AnnotatedString.Builder)
13 | override fun render(renderContext: RC) { //@formatter:on
14 | var style = SpanStyle()
15 |
16 | styles.forEach {
17 | style += it
18 | }
19 |
20 | withStyle(style) {
21 | super.render(renderContext)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/node/TextNode.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.node
2 |
3 | import androidx.compose.ui.text.AnnotatedString
4 | import com.xinto.simpleast.Node
5 |
6 | open class TextNode(
7 | val content: String,
8 | ) : Node() {
9 | //@formatter:off
10 | context(AnnotatedString.Builder)
11 | override fun render(renderContext: RC) { //@formatter:on
12 | append(content)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/node/UserMentionNode.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.node
2 |
3 | import androidx.compose.ui.text.AnnotatedString
4 | import androidx.compose.ui.text.SpanStyle
5 | import androidx.compose.ui.text.font.FontWeight
6 | import androidx.compose.ui.text.withStyle
7 | import com.xinto.opencord.ui.theme.Blurple
8 | import com.xinto.simpleast.Node
9 |
10 | class UserMentionNode(
11 | private val userId: String?
12 | ) : Node() {
13 | //@formatter:off
14 | context(AnnotatedString.Builder)
15 | override fun render(renderContext: RC) { //@formatter:on
16 | withStyle(
17 | SpanStyle(
18 | color = Blurple,
19 | background = Blurple.copy(alpha = 0.2f),
20 | fontWeight = FontWeight.SemiBold,
21 | ),
22 | ) {
23 | append("@$userId")
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/rule/MarkdownRules.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.rule
2 |
3 | import androidx.compose.ui.text.SpanStyle
4 | import androidx.compose.ui.text.font.FontStyle
5 | import androidx.compose.ui.text.font.FontWeight
6 | import androidx.compose.ui.text.style.TextDecoration
7 | import com.xinto.opencord.ast.util.*
8 | import com.xinto.simpleast.Node
9 | import com.xinto.simpleast.Rule
10 |
11 | fun createBoldTextRule() =
12 | createSimpleTextRule(
13 | pattern = PATTERN_BOLD_TEXT,
14 | styles = listOf(
15 | SpanStyle(
16 | fontWeight = FontWeight.Bold,
17 | ),
18 | ),
19 | )
20 |
21 | fun createItalicAsteriskTextRule(): Rule, S> =
22 | createSimpleTextRule(
23 | pattern = PATTERN_ITALIC_ASTERISK_TEXT,
24 | styles = listOf(SpanStyle(fontStyle = FontStyle.Italic)),
25 | )
26 |
27 | fun createItalicUnderscoreTextRule(): Rule, S> =
28 | createSimpleTextRule(
29 | pattern = PATTERN_ITALIC_UNDERSCORE_TEXT,
30 | styles = listOf(SpanStyle(fontStyle = FontStyle.Italic)),
31 | )
32 |
33 | fun createUnderlineTextRule() =
34 | createSimpleTextRule(
35 | pattern = PATTERN_UNDERLINE,
36 | styles = listOf(
37 | SpanStyle(textDecoration = TextDecoration.Underline),
38 | ),
39 | )
40 |
41 | fun createStrikeThroughRule() =
42 | createSimpleTextRule(
43 | pattern = PATTERN_STRIKETHROUGH,
44 | styles = listOf(
45 | SpanStyle(textDecoration = TextDecoration.LineThrough),
46 | ),
47 | )
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/rule/OtherRules.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.rule
2 |
3 | import com.xinto.opencord.ast.node.TextNode
4 | import com.xinto.opencord.ast.util.PATTERN_OTHER
5 | import com.xinto.simpleast.Node
6 | import com.xinto.simpleast.ParseSpec
7 | import com.xinto.simpleast.Rule
8 | import com.xinto.simpleast.createRule
9 |
10 | fun createOtherRule(): Rule, S> =
11 | createRule(PATTERN_OTHER) { matcher, _, state ->
12 | ParseSpec.createTerminal(
13 | node = TextNode(matcher.group()),
14 | state = state,
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/util/Patterns.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.util
2 |
3 | import java.util.regex.Pattern
4 |
5 | val PATTERN_OTHER: Pattern =
6 | Pattern.compile("""^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n| {2,}\n|\w+:\S|${'$'})""")
7 | val PATTERN_BOLD_TEXT: Pattern = Pattern.compile("""^\*\*([\s\S]+?)\*\*(?!\*)""")
8 | val PATTERN_ITALIC_ASTERISK_TEXT: Pattern = Pattern.compile("""^\*(?=\S)((?:\*\*|\s+(?:[^*\s]|\*\*)|[^\s*])+?)\*(?!\*)""")
9 | val PATTERN_ITALIC_UNDERSCORE_TEXT: Pattern = Pattern.compile("""^\b_((?:__|\\[\s\S]|[^\\_])+?)_\b""")
10 | val PATTERN_SPOILER: Pattern = Pattern.compile("""^\|\|([\s\S]+?)\|\|""")
11 | val PATTERN_STRIKETHROUGH: Pattern = Pattern.compile("""^~~(?=\S)([\s\S]+?\S)~~""")
12 | val PATTERN_ESCAPE: Pattern = Pattern.compile("""^\\([^0-9A-Za-z\s])""")
13 | val PATTERN_UNDERLINE: Pattern = Pattern.compile("""^__([\s\S]+?)__(?!_)""")
14 | val PATTERN_EMOTE: Pattern = Pattern.compile("""^<(a)?:([a-zA-Z_0-9]+):(\d+)>""")
15 | val PATTERN_USER_MENTION: Pattern = Pattern.compile("""^<@!?(\d+)>""")
16 | val PATTERN_EVERYONE_MENTION: Pattern = Pattern.compile("""^@(everyone|here)""")
17 | val PATTERN_CHANNEL_MENTION: Pattern = Pattern.compile("""^<#(\d+)>""")
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ast/util/Rules.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ast.util
2 |
3 | import androidx.compose.ui.text.SpanStyle
4 | import com.xinto.opencord.ast.node.StyledNode
5 | import com.xinto.simpleast.Node
6 | import com.xinto.simpleast.ParseSpec
7 | import com.xinto.simpleast.Rule
8 | import com.xinto.simpleast.createRule
9 | import java.util.regex.Pattern
10 |
11 | fun createSimpleTextRule(
12 | pattern: Pattern,
13 | styles: Collection
14 | ): Rule, S> =
15 | createRule(pattern) { matcher, _, state ->
16 | val node = StyledNode(styles)
17 | ParseSpec.createNonTerminal(
18 | node = node,
19 | state = state,
20 | startIndex = matcher.start(1),
21 | endIndex = matcher.end(1),
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/Converters.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db
2 |
3 | import androidx.room.TypeConverter
4 | import com.xinto.opencord.rest.models.embed.ApiEmbedField
5 | import kotlinx.serialization.decodeFromString
6 | import kotlinx.serialization.encodeToString
7 | import kotlinx.serialization.json.Json
8 |
9 | @Suppress("unused")
10 | class Converters {
11 | @TypeConverter
12 | fun fromEmbedFields(fields: List?): String? {
13 | return Json.encodeToString(fields ?: return null)
14 | }
15 |
16 | @TypeConverter
17 | fun toEmbedFields(fields: String?): List? {
18 | return Json.decodeFromString(fields ?: return null)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/dao/AccountsDao.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.dao
2 |
3 | import androidx.room.*
4 | import com.xinto.opencord.db.entity.EntityAccount
5 |
6 | @Dao
7 | interface AccountsDao {
8 | // --------------- Inserts ---------------
9 | @Insert(
10 | onConflict = OnConflictStrategy.REPLACE,
11 | entity = EntityAccount::class,
12 | )
13 | fun insertAccount(account: EntityAccount)
14 |
15 | // --------------- Updates ---------------
16 | @Query("UPDATE accounts SET cookies = :cookies WHERE token = :token")
17 | fun setCookies(token: String, cookies: String)
18 |
19 | @Query("UPDATE accounts SET locale = :locale WHERE token = :token")
20 | fun setLocale(token: String, locale: String?)
21 |
22 | // --------------- Queries ---------------
23 | @Query("SELECT * FROM accounts WHERE token = :token LIMIT 1")
24 | fun getAccount(token: String): EntityAccount?
25 |
26 | @Query("SELECT cookies FROM accounts WHERE token = :token LIMIT 1")
27 | fun getCookies(token: String): String?
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/dao/AttachmentsDao.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.xinto.opencord.db.entity.message.EntityAttachment
8 |
9 | @Dao
10 | interface AttachmentsDao {
11 | // --------------- Inserts ---------------
12 | @Insert(
13 | onConflict = OnConflictStrategy.REPLACE,
14 | entity = EntityAttachment::class,
15 | )
16 | fun insertAttachments(attachments: List)
17 |
18 | // --------------- Deletes ---------------
19 | @Query("DELETE FROM attachments WHERE message_id = :messageId")
20 | fun deleteAttachments(messageId: Long)
21 |
22 | @Query("DELETE FROM attachments")
23 | fun clear()
24 |
25 | // --------------- Queries ---------------
26 | @Query("SELECT * FROM attachments WHERE id = :messageId")
27 | fun getAttachments(messageId: Long): List
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/dao/EmbedsDao.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.xinto.opencord.db.entity.message.EntityEmbed
8 |
9 | @Dao
10 | interface EmbedsDao {
11 | // --------------- Inserts ---------------
12 | @Insert(
13 | onConflict = OnConflictStrategy.REPLACE,
14 | entity = EntityEmbed::class,
15 | )
16 | fun insertEmbeds(embeds: List)
17 |
18 | // --------------- Deletes ---------------
19 | @Query("DELETE FROM embeds WHERE message_id = :messageId")
20 | fun deleteEmbeds(messageId: Long)
21 |
22 | @Query("DELETE FROM embeds WHERE message_id = :messageId AND embed_index >= :embedCount")
23 | fun deleteTrailingEmbeds(messageId: Long, embedCount: Int)
24 |
25 | @Query("DELETE FROM embeds")
26 | fun clear()
27 |
28 | // --------------- Queries ---------------
29 | @Query("SELECT * FROM embeds WHERE message_id = :messageId ORDER BY embed_index ASC")
30 | fun getEmbeds(messageId: Long): List
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/dao/GuildsDao.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.dao
2 |
3 | import androidx.room.*
4 | import com.xinto.opencord.db.entity.guild.EntityGuild
5 |
6 | @Dao
7 | interface GuildsDao {
8 | // --------------- Inserts ---------------
9 | @Insert(
10 | onConflict = OnConflictStrategy.REPLACE,
11 | entity = EntityGuild::class,
12 | )
13 | fun insertGuild(guild: EntityGuild)
14 |
15 | @Insert(
16 | onConflict = OnConflictStrategy.REPLACE,
17 | entity = EntityGuild::class,
18 | )
19 | fun insertGuilds(guilds: List)
20 |
21 | @Transaction
22 | fun replaceAllGuilds(guilds: List) {
23 | clear()
24 | insertGuilds(guilds)
25 | }
26 |
27 | // --------------- Deletes ---------------
28 | @Query("DELETE FROM guilds WHERE id = :guildId")
29 | fun deleteGuild(guildId: Long)
30 |
31 | @Query("DELETE FROM guilds")
32 | fun clear()
33 |
34 | // --------------- Queries ---------------
35 | @Query("SELECT * FROM guilds WHERE id = :guildId LIMIT 1")
36 | fun getGuild(guildId: Long): EntityGuild?
37 |
38 | @Query("SELECT * FROM guilds")
39 | fun getGuilds(): List
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/dao/UnreadStatesDao.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.dao
2 |
3 | import androidx.room.*
4 | import com.xinto.opencord.db.entity.channel.EntityUnreadState
5 |
6 | @Dao
7 | interface UnreadStatesDao {
8 | // --------------- Inserts ---------------
9 | @Insert(
10 | onConflict = OnConflictStrategy.REPLACE,
11 | entity = EntityUnreadState::class,
12 | )
13 | fun insertState(state: EntityUnreadState)
14 |
15 | @Insert(
16 | onConflict = OnConflictStrategy.REPLACE,
17 | entity = EntityUnreadState::class,
18 | )
19 | fun insertStates(states: List)
20 |
21 | @Transaction
22 | fun replaceAllStates(states: List) {
23 | clear()
24 | insertStates(states)
25 | }
26 |
27 | // --------------- Updates ---------------
28 | @Query("UPDATE unread_states SET mention_count = coalesce(mention_count, 0) + 1 WHERE channel_id = :channelId")
29 | fun incrementMentionCount(channelId: Long)
30 |
31 | // --------------- Deletes ---------------
32 | @Query("DELETE FROM unread_states WHERE channel_id = :channelId")
33 | fun deleteUnreadState(channelId: Long)
34 |
35 | @Query("DELETE FROM unread_states")
36 | fun clear()
37 |
38 | // --------------- Queries ---------------
39 | @Query("SELECT * from unread_states WHERE channel_id = :channelId LIMIT 1")
40 | fun getUnreadState(channelId: Long): EntityUnreadState?
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/dao/UsersDao.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.xinto.opencord.db.entity.user.EntityUser
8 |
9 | @Dao
10 | interface UsersDao {
11 | // --------------- Inserts ---------------
12 | @Insert(
13 | onConflict = OnConflictStrategy.REPLACE,
14 | entity = EntityUser::class,
15 | )
16 | fun insertUsers(users: List)
17 |
18 | // --------------- Deletes ---------------
19 | @Query("DELETE FROM USERS WHERE id NOT IN(SELECT author_id FROM MESSAGES)")
20 | fun deleteUnusedUsers()
21 |
22 | // --------------- Queries ---------------
23 | @Query("SELECT * FROM users WHERE id = :userId LIMIT 1")
24 | fun getUser(userId: Long): EntityUser?
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/database/AccountDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.xinto.opencord.db.dao.AccountsDao
6 | import com.xinto.opencord.db.entity.EntityAccount
7 |
8 | @Database(
9 | version = 1,
10 | entities = [
11 | EntityAccount::class,
12 | ],
13 | exportSchema = false,
14 | )
15 | abstract class AccountDatabase : RoomDatabase() {
16 | abstract fun accounts(): AccountsDao
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/database/CacheDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import androidx.room.TypeConverters
6 | import com.xinto.opencord.db.Converters
7 | import com.xinto.opencord.db.dao.*
8 | import com.xinto.opencord.db.entity.channel.EntityChannel
9 | import com.xinto.opencord.db.entity.channel.EntityUnreadState
10 | import com.xinto.opencord.db.entity.guild.EntityGuild
11 | import com.xinto.opencord.db.entity.message.EntityAttachment
12 | import com.xinto.opencord.db.entity.message.EntityEmbed
13 | import com.xinto.opencord.db.entity.message.EntityMessage
14 | import com.xinto.opencord.db.entity.reactions.EntityReaction
15 | import com.xinto.opencord.db.entity.user.EntityUser
16 |
17 | @Database(
18 | version = 1,
19 | entities = [
20 | EntityChannel::class,
21 | EntityGuild::class,
22 | EntityAttachment::class,
23 | EntityEmbed::class,
24 | EntityMessage::class,
25 | EntityUser::class,
26 | EntityUnreadState::class,
27 | EntityReaction::class,
28 | ],
29 | exportSchema = false,
30 | )
31 | @TypeConverters(Converters::class)
32 | abstract class CacheDatabase : RoomDatabase() {
33 | abstract fun users(): UsersDao
34 | abstract fun guilds(): GuildsDao
35 | abstract fun channels(): ChannelsDao
36 | abstract fun unreadStates(): UnreadStatesDao
37 |
38 | abstract fun messages(): MessagesDao
39 | abstract fun reactions(): ReactionsDao
40 | abstract fun attachments(): AttachmentsDao
41 | abstract fun embeds(): EmbedsDao
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/entity/EntityAccount.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.entity
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(
8 | tableName = "accounts",
9 | )
10 | data class EntityAccount(
11 | @PrimaryKey
12 | @ColumnInfo(name = "token")
13 | val token: String,
14 |
15 | @ColumnInfo(name = "user_id")
16 | val userId: Long? = null,
17 |
18 | @ColumnInfo(name = "username")
19 | val username: String? = null,
20 |
21 | @ColumnInfo(name = "discriminator")
22 | val discriminator: String? = null,
23 |
24 | @ColumnInfo(name = "avatar_url")
25 | val avatarUrl: String? = null,
26 |
27 | @ColumnInfo(name = "cookies")
28 | val cookies: String? = null,
29 |
30 | @ColumnInfo(name = "fingerprint")
31 | val fingerprint: String? = null,
32 |
33 | @ColumnInfo(name = "locale")
34 | val locale: String? = null,
35 | )
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/entity/channel/EntityChannel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.entity.channel
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.Index
6 | import androidx.room.PrimaryKey
7 | import com.xinto.opencord.rest.models.ApiChannel
8 |
9 | @Entity(
10 | tableName = "channels",
11 | indices = [
12 | Index(value = ["guild_id"]),
13 | ],
14 | )
15 | data class EntityChannel(
16 | // -------- Discord data -------- //
17 | @PrimaryKey
18 | val id: Long,
19 |
20 | @ColumnInfo(name = "guild_id")
21 | val guildId: Long,
22 |
23 | @ColumnInfo(name = "name")
24 | val name: String,
25 |
26 | @ColumnInfo(name = "type")
27 | val type: Int,
28 |
29 | @ColumnInfo(name = "position")
30 | val position: Int,
31 |
32 | @ColumnInfo(name = "parent_id")
33 | val parentId: Long?,
34 |
35 | @ColumnInfo(name = "nsfw")
36 | val nsfw: Boolean,
37 |
38 | @ColumnInfo(name = "last_message_id")
39 | val lastMessageId: Long? = null,
40 |
41 | // -------- DB relational data -------- //
42 | @ColumnInfo(name = "is_pins_stored")
43 | val pinsStored: Boolean,
44 | )
45 |
46 | fun ApiChannel.toEntity(guildId: Long): EntityChannel {
47 | return EntityChannel(
48 | id = id.value,
49 | guildId = this.guildId?.value ?: guildId,
50 | name = name,
51 | type = type,
52 | position = position,
53 | parentId = parentId?.value,
54 | nsfw = nsfw,
55 | lastMessageId = lastMessageId?.value,
56 | pinsStored = false,
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/entity/channel/EntityUnreadState.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.entity.channel
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(
8 | tableName = "unread_states",
9 | )
10 | data class EntityUnreadState(
11 | @PrimaryKey
12 | @ColumnInfo(name = "channel_id")
13 | val channelId: Long,
14 |
15 | @ColumnInfo(name = "mention_count")
16 | val mentionCount: Int,
17 |
18 | @ColumnInfo(name = "last_message_id")
19 | val lastMessageId: Long,
20 | )
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/entity/guild/EntityGuild.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.entity.guild
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import com.xinto.opencord.rest.models.ApiGuild
7 |
8 | @Entity(
9 | tableName = "guilds",
10 | )
11 | data class EntityGuild(
12 | @PrimaryKey
13 | val id: Long,
14 |
15 | @ColumnInfo(name = "name")
16 | val name: String,
17 |
18 | @ColumnInfo(name = "icon")
19 | val icon: String?,
20 |
21 | @ColumnInfo(name = "banner")
22 | val banner: String? = null,
23 |
24 | @ColumnInfo(name = "premium_tier")
25 | val premiumTier: Int,
26 |
27 | @ColumnInfo(name = "premium_subscription_count")
28 | val premiumSubscriptionCount: Int?
29 | )
30 |
31 | fun ApiGuild.toEntity(): EntityGuild {
32 | return EntityGuild(
33 | id = id.value,
34 | name = name,
35 | icon = icon,
36 | banner = banner,
37 | premiumTier = premiumTier,
38 | premiumSubscriptionCount = premiumSubscriptionCount,
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/entity/message/EntityAttachment.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.entity.message
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.Index
6 | import androidx.room.PrimaryKey
7 | import com.xinto.opencord.rest.models.ApiAttachment
8 |
9 | @Entity(
10 | tableName = "attachments",
11 | indices = [
12 | Index(value = ["message_id"]),
13 | ],
14 | )
15 | data class EntityAttachment(
16 | @PrimaryKey
17 | val id: Long,
18 |
19 | @ColumnInfo(name = "message_id")
20 | val messageId: Long,
21 |
22 | @ColumnInfo(name = "file_name")
23 | val fileName: String,
24 |
25 | @ColumnInfo(name = "size")
26 | val size: Int,
27 |
28 | @ColumnInfo(name = "url")
29 | val url: String,
30 |
31 | @ColumnInfo(name = "proxy_url")
32 | val proxyUrl: String,
33 |
34 | @ColumnInfo(name = "width")
35 | val width: Int?,
36 |
37 | @ColumnInfo(name = "height")
38 | val height: Int?,
39 |
40 | @ColumnInfo(name = "content_type")
41 | val contentType: String?,
42 | )
43 |
44 | fun ApiAttachment.toEntity(messageId: Long): EntityAttachment {
45 | return EntityAttachment(
46 | id = id.value,
47 | messageId = messageId,
48 | fileName = filename,
49 | size = size,
50 | url = url,
51 | proxyUrl = proxyUrl,
52 | width = width,
53 | height = height,
54 | contentType = contentType,
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/db/entity/user/EntityUser.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.db.entity.user
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import com.xinto.opencord.rest.models.user.ApiUser
7 |
8 | @Entity(
9 | tableName = "users",
10 | )
11 | data class EntityUser(
12 | @PrimaryKey
13 | val id: Long,
14 |
15 | @ColumnInfo(name = "username")
16 | val username: String,
17 |
18 | @ColumnInfo(name = "discriminator")
19 | val discriminator: String,
20 |
21 | @ColumnInfo(name = "avatar_hash")
22 | val avatarHash: String?,
23 |
24 | @ColumnInfo(name = "bot")
25 | val bot: Boolean,
26 |
27 | @ColumnInfo(name = "pronouns")
28 | val pronouns: String?,
29 |
30 | @ColumnInfo(name = "bio")
31 | val bio: String?,
32 |
33 | @ColumnInfo(name = "banner_url")
34 | val bannerUrl: String?,
35 |
36 | @ColumnInfo(name = "public_flags")
37 | val publicFlags: Int,
38 | )
39 |
40 | fun ApiUser.toEntity(): EntityUser {
41 | return EntityUser(
42 | id = id.value,
43 | username = username,
44 | discriminator = discriminator,
45 | avatarHash = avatar,
46 | bot = bot,
47 | pronouns = pronouns,
48 | bio = bio,
49 | bannerUrl = banner,
50 | publicFlags = publicFlags ?: 0,
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/GatewayModule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import com.xinto.opencord.gateway.DiscordGateway
4 | import com.xinto.opencord.gateway.DiscordGatewayImpl
5 | import org.koin.core.qualifier.named
6 | import org.koin.dsl.module
7 |
8 | val gatewayModule = module {
9 | single {
10 | DiscordGatewayImpl(
11 | client = get(named("gatewayHttp")),
12 | json = get(),
13 | accountManager = get(),
14 | propertyProvider = get(),
15 | logger = get(),
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/HCaptcha.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import android.content.Context
4 | import com.hcaptcha.sdk.HCaptcha
5 | import com.hcaptcha.sdk.HCaptchaConfig
6 | import com.xinto.opencord.BuildConfig
7 | import org.koin.dsl.module
8 |
9 | val hcaptchaModule = module {
10 | fun provideHCaptcha(activity: Context): HCaptcha {
11 | val config = HCaptchaConfig.builder()
12 | .siteKey(BuildConfig.CAPTCHA_KEY) // doubt this will ever change
13 | .resetOnTimeout(true)
14 | .tokenExpiration(1000000L)
15 | .build()
16 | return HCaptcha.getClient(activity).setup(config)
17 | }
18 |
19 | single { params -> provideHCaptcha(params.get()) }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/LoggerModule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import com.xinto.opencord.util.Logger
4 | import com.xinto.opencord.util.LoggerImpl
5 | import org.koin.core.module.dsl.singleOf
6 | import org.koin.dsl.bind
7 | import org.koin.dsl.module
8 |
9 | val loggerModule = module {
10 | singleOf(::LoggerImpl) bind Logger::class
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/ManagerModule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import android.content.Context
4 | import com.xinto.opencord.db.database.AccountDatabase
5 | import com.xinto.opencord.manager.*
6 | import org.koin.android.ext.koin.androidContext
7 | import org.koin.core.module.dsl.singleOf
8 | import org.koin.dsl.bind
9 | import org.koin.dsl.module
10 |
11 | val managerModule = module {
12 | fun provideAccountManager(
13 | context: Context,
14 | accountDatabase: AccountDatabase,
15 | ): AccountManager {
16 | return AccountManagerImpl(
17 | authPrefs = context.getSharedPreferences("auth", Context.MODE_PRIVATE),
18 | accounts = accountDatabase,
19 | )
20 | }
21 |
22 | fun providePersistentDataManager(
23 | context: Context
24 | ): PersistentDataManager {
25 | return PersistentDataManagerImpl(
26 | persistentPrefs = context.getSharedPreferences("persistent_data", Context.MODE_PRIVATE),
27 | )
28 | }
29 |
30 | single { provideAccountManager(androidContext(), get()) }
31 | single { providePersistentDataManager(androidContext()) }
32 | single { ActivityManagerImpl(androidContext()) }
33 | singleOf(::AccountCookieManagerImpl) bind AccountCookieManager::class
34 | singleOf(::ClipboardManagerImpl) bind ClipboardManager::class
35 | singleOf(::ToastManagerImpl) bind ToastManager::class
36 | singleOf(::DownloadManagerImpl) bind DownloadManager::class
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/ProviderModule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import com.xinto.opencord.provider.AnonymousTelemetryProvider
4 | import com.xinto.opencord.provider.PropertyProvider
5 | import com.xinto.opencord.provider.PropertyProviderImpl
6 | import com.xinto.opencord.provider.TelemetryProvider
7 | import org.koin.core.module.dsl.singleOf
8 | import org.koin.dsl.bind
9 | import org.koin.dsl.module
10 |
11 | val providerModule = module {
12 | singleOf(::AnonymousTelemetryProvider) bind TelemetryProvider::class
13 | singleOf(::PropertyProviderImpl) bind PropertyProvider::class
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/ServiceModule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import com.xinto.opencord.rest.service.DiscordApiService
4 | import com.xinto.opencord.rest.service.DiscordApiServiceImpl
5 | import com.xinto.opencord.rest.service.DiscordAuthService
6 | import com.xinto.opencord.rest.service.DiscordAuthServiceImpl
7 | import org.koin.core.qualifier.named
8 | import org.koin.dsl.module
9 |
10 | val serviceModule = module {
11 | single {
12 | DiscordAuthServiceImpl(
13 | client = get(named("authHttp")),
14 | )
15 | }
16 | single {
17 | DiscordApiServiceImpl(
18 | client = get(named("apiHttp")),
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/SimpleASTModule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import com.xinto.opencord.ast.rule.*
4 | import com.xinto.simpleast.Node
5 | import com.xinto.simpleast.Parser
6 | import com.xinto.simpleast.createParser
7 | import org.koin.dsl.module
8 |
9 | val simpleAstModule = module {
10 | fun provideSimpleAst(): Parser, Any?> {
11 | return createParser {
12 | rule(createEscapeRule())
13 | rule(createSpoilerRule())
14 | rule(createBoldTextRule())
15 | rule(createUnderlineTextRule())
16 | rule(createItalicAsteriskTextRule())
17 | rule(createItalicUnderscoreTextRule())
18 | rule(createStrikeThroughRule())
19 | rule(createEmoteRule())
20 | rule(createUserMentionRule())
21 | rule(createEveryoneMentionRule())
22 | rule(createChannelMentionRule())
23 | rule(createOtherRule())
24 | }
25 | }
26 |
27 | single { provideSimpleAst() }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/StoreModule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import com.xinto.opencord.store.*
4 | import org.koin.core.module.dsl.singleOf
5 | import org.koin.dsl.bind
6 | import org.koin.dsl.module
7 |
8 | val storeModule = module {
9 | singleOf(::MessageStoreImpl) bind MessageStore::class
10 | singleOf(::LastMessageStoreImpl) bind LastMessageStore::class
11 | singleOf(::ChannelStoreImpl) bind ChannelStore::class
12 | singleOf(::GuildStoreImpl) bind GuildStore::class
13 | singleOf(::UserSettingsStoreImpl) bind UserSettingsStore::class
14 | singleOf(::CurrentUserStoreImpl) bind CurrentUserStore::class
15 | singleOf(::SessionStoreImpl) bind SessionStore::class
16 | singleOf(::UnreadStoreImpl) bind UnreadStore::class
17 | singleOf(::ReactionStoreImpl) bind ReactionStore::class
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/di/ViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.di
2 |
3 | import com.xinto.opencord.ui.viewmodel.*
4 | import org.koin.androidx.viewmodel.dsl.viewModelOf
5 | import org.koin.dsl.module
6 |
7 | val viewModelModule = module {
8 | viewModelOf(::LoginViewModel)
9 | viewModelOf(::ChatViewModel)
10 | viewModelOf(::GuildsViewModel)
11 | viewModelOf(::ChannelsViewModel)
12 | viewModelOf(::ChannelPinsViewModel)
13 | viewModelOf(::CurrentUserViewModel)
14 | viewModelOf(::MessageMenuViewModel)
15 | viewModelOf(::MentionsViewModel)
16 | viewModelOf(::ChatInputViewModel)
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/Mentionable.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain
2 |
3 | interface Mentionable {
4 | val formattedMention: String
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/ActivityType.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.github.materiiapps.enumutil.FromValue
5 |
6 | @Immutable
7 | @FromValue
8 | enum class ActivityType(val value: Int) {
9 | Game(0),
10 | Streaming(1),
11 | Listening(2),
12 | Watching(3),
13 | Custom(4),
14 | Competing(5),
15 | Unknown(-1);
16 |
17 | companion object
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/DomainActivityAssets.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.activity.ApiActivityAssets
5 |
6 | @Immutable
7 | data class DomainActivityAssets(
8 | val largeImage: String?,
9 | val largeText: String?,
10 | val smallImage: String?,
11 | val smallText: String?,
12 | )
13 |
14 | fun ApiActivityAssets.toDomain(): DomainActivityAssets {
15 | return DomainActivityAssets(
16 | largeImage = largeImage,
17 | largeText = largeText,
18 | smallImage = smallImage,
19 | smallText = smallText,
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/DomainActivityButton.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity
2 |
3 | //@Immutable
4 | //data class DomainActivityButton(
5 | // val label: String,
6 | // val url: String,
7 | //)
8 |
9 | //fun ApiActivityButton.toDomain(): DomainActivityButton {
10 | // return DomainActivityButton(
11 | // label = label,
12 | // url = url,
13 | // )
14 | //}
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/DomainActivityMetadata.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.activity.ApiActivityMetadata
5 |
6 | @Immutable
7 | data class DomainActivityMetadata(
8 | val albumId: String?,
9 | val artistIds: List?,
10 | val contextUri: String?,
11 | )
12 |
13 | fun ApiActivityMetadata.toDomain(): DomainActivityMetadata {
14 | return DomainActivityMetadata(
15 | albumId = albumId,
16 | artistIds = artistIds,
17 | contextUri = contextUri,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/DomainActivityParty.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.activity.ApiActivityParty
5 |
6 | @Immutable
7 | data class DomainActivityParty(
8 | val id: String?,
9 | val currentSize: Int?,
10 | val maxSize: Int?,
11 | )
12 |
13 | fun ApiActivityParty.toDomain(): DomainActivityParty {
14 | return DomainActivityParty(
15 | id = id,
16 | currentSize = size?.get(0),
17 | maxSize = size?.get(1),
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/DomainActivitySecrets.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.activity.ApiActivitySecrets
5 |
6 | @Immutable
7 | data class DomainActivitySecrets(
8 | val join: String?,
9 | val spectate: String?,
10 | val match: String?,
11 | )
12 |
13 | fun ApiActivitySecrets.toDomain(): DomainActivitySecrets {
14 | return DomainActivitySecrets(
15 | join = join,
16 | spectate = spectate,
17 | match = match,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/DomainActivityTimestamp.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.activity.ApiActivityTimestamp
5 | import kotlinx.datetime.Instant
6 |
7 | @Immutable
8 | data class DomainActivityTimestamp(
9 | val start: Instant?,
10 | val end: Instant?,
11 | )
12 |
13 | fun ApiActivityTimestamp.toDomain(): DomainActivityTimestamp {
14 | return DomainActivityTimestamp(
15 | start = start?.let { Instant.fromEpochMilliseconds(it.toLong()) },
16 | end = end?.let { Instant.fromEpochMilliseconds(it.toLong()) },
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/types/DomainActivityCustom.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity.types
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.domain.activity.ActivityType
5 | import com.xinto.opencord.domain.activity.DomainActivity
6 | import com.xinto.opencord.domain.emoji.DomainEmoji
7 |
8 | @Immutable
9 | data class DomainActivityCustom(
10 | override val name: String,
11 | override val createdAt: Long,
12 | val status: String?,
13 | val emoji: DomainEmoji?,
14 | ) : DomainActivity {
15 | override val type = ActivityType.Custom
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/types/DomainActivityGame.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity.types
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.domain.activity.*
5 |
6 | @Immutable
7 | data class DomainActivityGame(
8 | override val name: String,
9 | override val createdAt: Long,
10 | val id: String?,
11 | val state: String,
12 | val details: String,
13 | val applicationId: Long,
14 | val party: DomainActivityParty?,
15 | val assets: DomainActivityAssets?,
16 | val secrets: DomainActivitySecrets?,
17 | val timestamps: DomainActivityTimestamp?,
18 | ) : DomainActivity {
19 | override val type = ActivityType.Game
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/types/DomainActivityListening.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity.types
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.domain.activity.*
5 |
6 | @Immutable
7 | data class DomainActivityListening(
8 | override val name: String,
9 | override val createdAt: Long,
10 | val id: String,
11 | val flags: Int,
12 | val state: String,
13 | val details: String,
14 | val syncId: String,
15 | val party: DomainActivityParty,
16 | val assets: DomainActivityAssets,
17 | val metadata: DomainActivityMetadata?,
18 | val timestamps: DomainActivityTimestamp,
19 | ) : DomainActivity {
20 | override val type = ActivityType.Listening
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/types/DomainActivityStreaming.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity.types
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.domain.activity.ActivityType
5 | import com.xinto.opencord.domain.activity.DomainActivity
6 | import com.xinto.opencord.domain.activity.DomainActivityAssets
7 |
8 | @Immutable
9 | data class DomainActivityStreaming(
10 | override val name: String,
11 | override val createdAt: Long,
12 | val id: String,
13 | val url: String,
14 | val state: String,
15 | val details: String,
16 | val assets: DomainActivityAssets,
17 | ) : DomainActivity {
18 | override val type = ActivityType.Streaming
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/activity/types/DomainActivityUnknown.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.activity.types
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.domain.activity.ActivityType
5 | import com.xinto.opencord.domain.activity.DomainActivity
6 |
7 | // TODO: remove this once all activity types are implemented
8 | @Immutable
9 | data class DomainActivityUnknown(
10 | override val name: String,
11 | override val createdAt: Long,
12 | ) : DomainActivity {
13 | override val type = ActivityType.Unknown
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/attachment/DomainFileAttachment.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.attachment
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainFileAttachment(
7 | override val id: Long,
8 | override val filename: String,
9 | override val size: Int,
10 | override val url: String,
11 | override val proxyUrl: String,
12 | override val type: String?,
13 | ) : DomainAttachment
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/attachment/DomainPictureAttachment.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.attachment
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainPictureAttachment(
7 | override val id: Long,
8 | override val filename: String,
9 | override val size: Int,
10 | override val url: String,
11 | override val proxyUrl: String,
12 | override val type: String,
13 | val width: Int,
14 | val height: Int,
15 | ) : DomainAttachment
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/attachment/DomainVideoAttachment.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.attachment
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainVideoAttachment(
7 | override val id: Long,
8 | override val filename: String,
9 | override val size: Int,
10 | override val url: String,
11 | override val proxyUrl: String,
12 | override val type: String,
13 | val width: Int,
14 | val height: Int,
15 | ) : DomainAttachment
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/channel/DomainAnnouncementChannel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.channel
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainAnnouncementChannel(
7 | override val id: Long,
8 | override val guildId: Long?,
9 | override val name: String,
10 | override val position: Int,
11 | override val parentId: Long?,
12 | val nsfw: Boolean,
13 | ) : DomainChannel() {
14 | override val sortingPriority: Short get() = 2
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/channel/DomainCategoryChannel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.channel
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainCategoryChannel(
7 | override val id: Long,
8 | override val guildId: Long?,
9 | override val name: String,
10 | override val position: Int,
11 | override val parentId: Long? = null,
12 | ) : DomainChannel() {
13 | override val sortingPriority: Short get() = 0
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/channel/DomainTextChannel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.channel
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainTextChannel(
7 | override val id: Long,
8 | override val guildId: Long?,
9 | override val name: String,
10 | override val position: Int,
11 | override val parentId: Long?,
12 | val nsfw: Boolean,
13 | ) : DomainChannel() {
14 | override val sortingPriority: Short get() = 2
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/channel/DomainUnreadState.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.channel
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.db.entity.channel.EntityUnreadState
5 |
6 | @Immutable
7 | data class DomainUnreadState(
8 | val channelId: Long,
9 | val mentionCount: Int,
10 | val lastMessageId: Long,
11 | )
12 |
13 | fun EntityUnreadState.toDomain(): DomainUnreadState {
14 | return DomainUnreadState(
15 | channelId = channelId,
16 | mentionCount = mentionCount,
17 | lastMessageId = lastMessageId,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/channel/DomainVoiceChannel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.channel
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainVoiceChannel(
7 | override val id: Long,
8 | override val guildId: Long?,
9 | override val name: String,
10 | override val position: Int,
11 | override val parentId: Long?,
12 | ) : DomainChannel() {
13 | override val sortingPriority: Short get() = 3
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/embed/DomainEmbedFooter.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.embed
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.embed.ApiEmbedFooter
5 | import com.xinto.opencord.util.Timestamp
6 | import kotlinx.datetime.Instant
7 |
8 | @Immutable
9 | data class DomainEmbedFooter(
10 | val text: String,
11 | val timestamp: Instant?,
12 | val iconUrl: String?,
13 | val proxyIconUrl: String?,
14 | ) {
15 | val formattedTimestamp: String? by lazy {
16 | timestamp?.let { Timestamp.getFormattedTimestamp(it) }
17 | }
18 |
19 | val displayUrl: String? by lazy {
20 | if (proxyIconUrl != null) {
21 | "$proxyIconUrl?width=64&height=64"
22 | } else {
23 | iconUrl
24 | }
25 | }
26 | }
27 |
28 | fun ApiEmbedFooter.toDomain(timestamp: Instant?): DomainEmbedFooter {
29 | return DomainEmbedFooter(
30 | text = text,
31 | timestamp = timestamp,
32 | iconUrl = iconUrl,
33 | proxyIconUrl = proxyIconUrl,
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/emoji/DomainEmoji.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.emoji
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.github.materiiapps.partial.getOrNull
5 | import com.xinto.opencord.rest.models.emoji.ApiEmoji
6 |
7 | /**
8 | * A unique identifier of a [DomainEmoji] that is obtained through [DomainEmoji.identifier]
9 | */
10 | @Immutable
11 | @JvmInline
12 | value class DomainEmojiIdentifier(private val hashCode: Int?) {
13 | val exists: Boolean
14 | get() = hashCode != null
15 | }
16 |
17 | @Immutable
18 | sealed interface DomainEmoji {
19 | val identifier: DomainEmojiIdentifier
20 | }
21 |
22 | fun ApiEmoji.toDomain(): DomainEmoji {
23 | return when {
24 | id == null && name != null -> DomainUnicodeEmoji(
25 | emoji = name,
26 | )
27 | id != null -> DomainGuildEmoji(
28 | name = name,
29 | id = id.value,
30 | animated = animated.getOrNull() ?: false,
31 | )
32 | else -> DomainUnknownEmoji
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/emoji/DomainGuildEmoji.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.emoji
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.service.DiscordCdnServiceImpl
5 |
6 | @Immutable
7 | data class DomainGuildEmoji(
8 | val id: Long,
9 | val name: String?,
10 | val animated: Boolean,
11 | ) : DomainEmoji {
12 | override val identifier: DomainEmojiIdentifier
13 | get() = DomainEmojiIdentifier(id.hashCode())
14 |
15 | val url: String by lazy {
16 | DiscordCdnServiceImpl.getGuildEmojiUrl(id, animated)
17 | }
18 |
19 | // val isDeleted: Boolean
20 | // get() = name == null
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/emoji/DomainUnicodeEmoji.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.emoji
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainUnicodeEmoji(
7 | val emoji: String,
8 | ) : DomainEmoji {
9 | override val identifier: DomainEmojiIdentifier
10 | get() = DomainEmojiIdentifier(emoji.hashCode())
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/emoji/DomainUnknownEmoji.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.emoji
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | object DomainUnknownEmoji : DomainEmoji {
7 | override val identifier: DomainEmojiIdentifier
8 | get() = DomainEmojiIdentifier(null)
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/login/DomainLogin.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.login
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.login.ApiLogin
5 |
6 | @Immutable
7 | sealed interface DomainLogin {
8 | @Immutable
9 | data class Login(
10 | val token: String,
11 | val mfa: Boolean,
12 | val locale: String,
13 | val theme: String,
14 | ) : DomainLogin
15 |
16 | @Immutable
17 | data class Captcha(val captchaSiteKey: String) : DomainLogin
18 |
19 | @Immutable
20 | data class TwoFactorAuth(val ticket: String) : DomainLogin
21 |
22 | @Immutable
23 | object Error : DomainLogin
24 | }
25 |
26 | fun ApiLogin.toDomain(): DomainLogin {
27 | return when {
28 | ticket != null -> {
29 | DomainLogin.TwoFactorAuth(
30 | ticket = ticket,
31 | )
32 | }
33 | captchaSiteKey != null -> {
34 | DomainLogin.Captcha(
35 | captchaSiteKey = captchaSiteKey,
36 | )
37 | }
38 | token != null -> {
39 | DomainLogin.Login(
40 | token = token,
41 | mfa = mfa,
42 | theme = userSettings?.theme!!,
43 | locale = userSettings.locale,
44 | )
45 | }
46 | else -> DomainLogin.Error
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/member/DomainGuildMember.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.member
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.domain.user.DomainUser
5 | import com.xinto.opencord.domain.user.toDomain
6 | import com.xinto.opencord.rest.models.ApiGuildMember
7 | import com.xinto.opencord.rest.service.DiscordCdnServiceImpl
8 |
9 | @Immutable
10 | data class DomainGuildMember(
11 | val user: DomainUser?,
12 | val nick: String? = null,
13 | val avatarUrl: String? = null,
14 | )
15 |
16 | fun ApiGuildMember.toDomain(): DomainGuildMember {
17 | val avatarUrl = user?.let { user ->
18 | avatar
19 | ?.let { DiscordCdnServiceImpl.getUserAvatarUrl(user.id.value, it) }
20 | ?: DiscordCdnServiceImpl.getDefaultAvatarUrl(user.discriminator.toInt().rem(5))
21 | }
22 | return DomainGuildMember(
23 | user = user?.toDomain(),
24 | nick = nick,
25 | avatarUrl = avatarUrl,
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/message/DomainMessageMemberJoin.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.message
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.github.materiiapps.partial.Partialize
5 | import com.xinto.opencord.domain.user.DomainUser
6 | import com.xinto.opencord.util.SimpleAstParser
7 | import com.xinto.opencord.util.Timestamp
8 | import com.xinto.simpleast.render
9 | import kotlinx.datetime.Instant
10 | import org.koin.core.component.KoinComponent
11 | import org.koin.core.component.get
12 |
13 | @Immutable
14 | @Partialize // Is this even able to be partial?
15 | data class DomainMessageMemberJoin(
16 | override val id: Long,
17 | override val channelId: Long,
18 | override val guildId: Long?,
19 | override val timestamp: Instant,
20 | override val pinned: Boolean,
21 | override val content: String,
22 | override val author: DomainUser
23 | ) : DomainMessage, KoinComponent {
24 | override val contentRendered by lazy {
25 | render(
26 | nodes = get()
27 | .parse(content, null),
28 | renderContext = null,
29 | ).toAnnotatedString()
30 | }
31 | override val formattedTimestamp by lazy {
32 | Timestamp.getFormattedTimestamp(timestamp)
33 | }
34 | override val isDeletable get() = true
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/message/DomainMessageUnknown.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.message
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.text.buildAnnotatedString
5 | import com.github.materiiapps.partial.Partialize
6 | import com.xinto.opencord.domain.user.DomainUser
7 | import com.xinto.opencord.util.Timestamp
8 | import kotlinx.datetime.Instant
9 |
10 | @Immutable
11 | @Partialize
12 | data class DomainMessageUnknown(
13 | override val id: Long,
14 | override val channelId: Long,
15 | override val guildId: Long?,
16 | override val timestamp: Instant,
17 | override val pinned: Boolean,
18 | override val content: String,
19 | override val author: DomainUser
20 | ) : DomainMessage {
21 | override val contentRendered get() = buildAnnotatedString {}
22 | override val formattedTimestamp by lazy {
23 | Timestamp.getFormattedTimestamp(timestamp)
24 | }
25 | override val isDeletable get() = true
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/message/reaction/DomainReaction.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.message.reaction
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.db.entity.reactions.EntityReaction
5 | import com.xinto.opencord.domain.emoji.*
6 | import com.xinto.opencord.rest.models.reaction.ApiReaction
7 |
8 | @Immutable
9 | data class DomainReaction(
10 | val count: Int,
11 | val meReacted: Boolean,
12 | val emoji: DomainEmoji,
13 | )
14 |
15 | fun ApiReaction.toDomain(): DomainReaction {
16 | return DomainReaction(
17 | count = count,
18 | meReacted = meReacted,
19 | emoji = emoji.toDomain(),
20 | )
21 | }
22 |
23 | fun EntityReaction.toDomain(): DomainReaction {
24 | val emoji = when {
25 | emojiId > 0L -> DomainGuildEmoji(
26 | id = emojiId,
27 | name = emojiName,
28 | animated = animated,
29 | )
30 | emojiName.isNotEmpty() -> DomainUnicodeEmoji(
31 | emoji = emojiName,
32 | )
33 | else -> DomainUnknownEmoji
34 | }
35 |
36 | return DomainReaction(
37 | count = count,
38 | meReacted = meReacted,
39 | emoji = emoji,
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/user/DomainUserPrivate.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.user
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainUserPrivate(
7 | override val id: Long,
8 | override val username: String,
9 | override val discriminator: String,
10 | override val avatarUrl: String,
11 | override val bot: Boolean,
12 | override val bio: String?,
13 | override val flags: Int,
14 | val pronouns: String?,
15 | val mfaEnabled: Boolean,
16 | val verified: Boolean,
17 | val email: String,
18 | val phone: String?, // TODO: verify this is nullable
19 | val locale: String,
20 | ) : DomainUser()
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/user/DomainUserPrivateReady.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.user
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | /**
6 | * Sent on the `user` field of the READY event
7 | */
8 | @Immutable
9 | data class DomainUserPrivateReady(
10 | override val id: Long,
11 | override val username: String,
12 | override val discriminator: String,
13 | override val avatarUrl: String,
14 | override val bot: Boolean,
15 | override val bio: String?,
16 | override val flags: Int,
17 | val mfaEnabled: Boolean,
18 | val verified: Boolean,
19 | val premium: Boolean,
20 | val purchasedFlags: Int, // TODO: implement bitfield
21 | ) : DomainUser()
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/user/DomainUserPublic.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.user
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class DomainUserPublic(
7 | override val id: Long,
8 | override val username: String,
9 | override val discriminator: String,
10 | override val avatarUrl: String,
11 | override val bot: Boolean,
12 | override val bio: String?,
13 | override val flags: Int,
14 | val pronouns: String?,
15 | ) : DomainUser()
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/usersettings/DomainCustomStatus.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.usersettings
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.user.settings.ApiCustomStatus
5 | import kotlinx.datetime.Instant
6 |
7 | @Immutable
8 | data class DomainCustomStatus(
9 | val text: String?,
10 | val expiresAt: Instant?,
11 | val emojiId: Long?,
12 | val emojiName: String?
13 | )
14 |
15 | fun ApiCustomStatus.toDomain(): DomainCustomStatus {
16 | return DomainCustomStatus(
17 | text = text,
18 | expiresAt = expiresAt?.let { Instant.parse(it) },
19 | emojiId = emojiId?.value,
20 | emojiName = emojiName,
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/usersettings/DomainFriendSources.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.usersettings
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.user.settings.ApiFriendSources
5 |
6 | @Immutable
7 | data class DomainFriendSources(
8 | val all: Boolean,
9 | val mutualFriends: Boolean,
10 | val mutualGuilds: Boolean,
11 | )
12 |
13 | fun ApiFriendSources.toDomain(): DomainFriendSources {
14 | return DomainFriendSources(
15 | all = (all ?: false) && mutualFriends == null && mutualGuilds == null,
16 | mutualFriends = mutualFriends ?: false,
17 | mutualGuilds = mutualGuilds ?: false,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/usersettings/DomainGuildFolder.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.usersettings
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.xinto.opencord.rest.models.user.settings.ApiGuildFolder
5 |
6 | @Immutable
7 | data class DomainGuildFolder(
8 | val id: Long?,
9 | val guildIds: List,
10 | val name: String?,
11 | )
12 |
13 | fun ApiGuildFolder.toDomain(): DomainGuildFolder {
14 | return DomainGuildFolder(
15 | id = id?.value,
16 | guildIds = guildIds.map { it.value },
17 | name = name,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/usersettings/DomainThemeSetting.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.usersettings
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.github.materiiapps.enumutil.FromValue
5 |
6 | @Immutable
7 | @FromValue
8 | enum class DomainThemeSetting(val value: String) {
9 | Dark("dark"),
10 | Light("light");
11 |
12 | companion object
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/domain/usersettings/DomainUserStatus.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.domain.usersettings
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.github.materiiapps.enumutil.FromValue
5 |
6 | @Immutable
7 | @FromValue
8 | enum class DomainUserStatus(val value: String) {
9 | Online("online"),
10 | Idle("idle"),
11 | Dnd("dnd"),
12 | Invisible("invisible");
13 |
14 | companion object
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/CloseCode.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway
2 |
3 | import com.github.materiiapps.enumutil.FromValue
4 |
5 | @FromValue
6 | enum class CloseCode(val code: Int) {
7 | Unknown(-1),
8 | UnknownError(4000),
9 | UnknownOpCode(4001),
10 | DecodeError(4002),
11 | NotAuthorized(4003),
12 | AuthenticationFailed(4004),
13 | AlreadyAuthenticated(4005),
14 | InvalidSequenceNumber(4007),
15 | RateLimited(4008),
16 | SessionTimedOut(4009);
17 |
18 | companion object
19 | }
20 |
21 | val CloseCode?.canReconnect: Boolean
22 | get() {
23 | return when (this) {
24 | CloseCode.Unknown -> false
25 | CloseCode.UnknownError -> true
26 | CloseCode.UnknownOpCode -> true
27 | CloseCode.DecodeError -> true
28 | CloseCode.NotAuthorized -> true
29 | CloseCode.AuthenticationFailed -> false
30 | CloseCode.AlreadyAuthenticated -> true
31 | CloseCode.InvalidSequenceNumber -> true
32 | CloseCode.RateLimited -> true
33 | CloseCode.SessionTimedOut -> true
34 | else -> false
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/GuildDeleteData.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import com.xinto.opencord.rest.models.ApiSnowflake
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class GuildDeleteData(
9 | @SerialName("id")
10 | val id: ApiSnowflake,
11 |
12 | @SerialName("unavailable")
13 | val unavailable: Boolean,
14 | )
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/Heartbeat.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Heartbeat(
8 | @SerialName("heartbeat_interval")
9 | val heartbeatInterval: Long,
10 | )
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/MessageAckData.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class MessageAckData(
8 | @SerialName("ack_type")
9 | val ackType: Int? = null,
10 |
11 | @SerialName("channel_id")
12 | val channelId: Long,
13 |
14 | @SerialName("message_id")
15 | val messageId: Long,
16 |
17 | @SerialName("version")
18 | val version: Int,
19 |
20 | @SerialName("mention_count")
21 | val mentionCount: Int? = 0,
22 | )
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/MessageDeleteData.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import com.xinto.opencord.rest.models.ApiSnowflake
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class MessageDeleteData(
9 | @SerialName("id")
10 | val messageId: ApiSnowflake,
11 |
12 | @SerialName("channel_id")
13 | val channelId: ApiSnowflake,
14 |
15 | @SerialName("guild_id")
16 | val guildId: ApiSnowflake,
17 | )
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/Presence.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import com.xinto.opencord.rest.models.activity.ApiActivity
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class UpdatePresence(
8 | val since: Long,
9 | val status: String,
10 | val afk: Boolean?,
11 | val activities: List,
12 | )
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/Reaction.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import com.xinto.opencord.rest.models.ApiSnowflake
4 | import com.xinto.opencord.rest.models.emoji.ApiEmoji
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | data class MessageReactionAddData(
10 | @SerialName("user_id")
11 | val userId: ApiSnowflake,
12 |
13 | @SerialName("channel_id")
14 | val channelId: ApiSnowflake,
15 |
16 | @SerialName("message_id")
17 | val messageId: ApiSnowflake,
18 |
19 | @SerialName("guild_id")
20 | val guildId: ApiSnowflake? = null,
21 |
22 | @SerialName("emoji")
23 | val emoji: ApiEmoji,
24 |
25 | // @SerialName("member")
26 | // val member: ApiGuildMember? = null,
27 | )
28 |
29 | @Serializable
30 | data class MessageReactionRemoveData(
31 | @SerialName("user_id")
32 | val userId: ApiSnowflake,
33 |
34 | @SerialName("channel_id")
35 | val channelId: ApiSnowflake,
36 |
37 | @SerialName("message_id")
38 | val messageId: ApiSnowflake,
39 |
40 | @SerialName("guild_id")
41 | val guildId: ApiSnowflake? = null,
42 |
43 | @SerialName("emoji")
44 | val emoji: ApiEmoji,
45 | )
46 |
47 | @Serializable
48 | data class MessageReactionRemoveAllData(
49 | @SerialName("message_id")
50 | val messageId: ApiSnowflake,
51 |
52 | @SerialName("channel_id")
53 | val channelId: ApiSnowflake,
54 |
55 | @SerialName("guild_id")
56 | val guildId: ApiSnowflake? = null,
57 | )
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/Ready.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import com.xinto.opencord.rest.models.ApiGuild
4 | import com.xinto.opencord.rest.models.user.ApiUser
5 | import com.xinto.opencord.rest.models.user.settings.ApiUserSettings
6 | import kotlinx.datetime.Instant
7 | import kotlinx.serialization.SerialName
8 | import kotlinx.serialization.Serializable
9 |
10 | @Serializable
11 | data class Ready(
12 | @SerialName("user")
13 | val user: ApiUser,
14 |
15 | @SerialName("guilds")
16 | val guilds: List,
17 |
18 | @SerialName("sessions")
19 | val sessions: List,
20 |
21 | @SerialName("session_id")
22 | val sessionId: String,
23 |
24 | @SerialName("user_settings")
25 | val userSettings: ApiUserSettings,
26 |
27 | @SerialName("read_state")
28 | val readState: ReadState,
29 |
30 | @SerialName("auth_token")
31 | val newAuthToken: String? = null,
32 | )
33 |
34 | @Serializable
35 | data class ReadState(
36 | val version: Int,
37 | val partial: Boolean,
38 | val entries: List
39 | )
40 |
41 | @Serializable
42 | data class ReadStateEntry(
43 | @SerialName("id")
44 | val channelId: Long,
45 |
46 | @SerialName("mention_count")
47 | val mentionCount: Int,
48 |
49 | @SerialName("last_pin_timestamp")
50 | val lastPinTimestamp: Instant,
51 |
52 | @SerialName("last_message_id")
53 | val lastMessageId: Long,
54 | )
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/RequestGuildMembers.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import com.xinto.opencord.rest.models.ApiSnowflake
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class RequestGuildMembers(
9 | @SerialName("guild_id")
10 | val guildId: ApiSnowflake,
11 |
12 | @SerialName("query")
13 | val query: String = "",
14 |
15 | @SerialName("limit")
16 | val limit: Int = 0,
17 |
18 | @SerialName("presences")
19 | val presences: Boolean? = null,
20 |
21 | @SerialName("user_ids")
22 | val userIds: List? = null,
23 | )
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/Resume.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Resume(
8 | @SerialName("token")
9 | val token: String,
10 |
11 | @SerialName("session_id")
12 | val sessionId: String,
13 |
14 | @SerialName("seq")
15 | val sequenceNumber: Int
16 | )
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/dto/SessionsReplaceData.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.dto
2 |
3 | import com.xinto.opencord.rest.models.activity.ApiActivity
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | typealias ReplaceSessionsData = List
8 |
9 | @Serializable
10 | data class SessionData(
11 | @SerialName("session_id")
12 | val sessionId: String,
13 |
14 | @SerialName("client_info")
15 | val clientInfo: ClientInfo,
16 |
17 | @SerialName("status")
18 | val status: String,
19 |
20 | @SerialName("activities")
21 | val activities: List,
22 | )
23 |
24 | @Serializable
25 | data class ClientInfo(
26 | val client: String,
27 | val os: String,
28 | )
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/Channel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.rest.models.ApiChannel
4 |
5 | data class ChannelCreateEvent(
6 | val data: ApiChannel,
7 | ) : Event
8 |
9 | data class ChannelUpdateEvent(
10 | val data: ApiChannel,
11 | ) : Event
12 |
13 | data class ChannelDeleteEvent(
14 | val data: ApiChannel,
15 | ) : Event
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/Guild.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.gateway.dto.GuildDeleteData
4 | import com.xinto.opencord.rest.models.ApiGuild
5 |
6 | data class GuildCreateEvent(
7 | val data: ApiGuild
8 | ) : Event
9 |
10 | data class GuildUpdateEvent(
11 | val data: ApiGuild
12 | ) : Event
13 |
14 | data class GuildDeleteEvent(
15 | val data: GuildDeleteData
16 | ) : Event
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/GuildMemberChunk.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.rest.models.ApiGuildMemberChunk
4 |
5 | data class GuildMemberChunkEvent(
6 | val data: ApiGuildMemberChunk
7 | ) : Event
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/Message.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.gateway.dto.MessageAckData
4 | import com.xinto.opencord.gateway.dto.MessageDeleteData
5 | import com.xinto.opencord.rest.models.message.ApiMessage
6 | import com.xinto.opencord.rest.models.message.ApiMessagePartial
7 |
8 | data class MessageCreateEvent(
9 | val data: ApiMessage,
10 | ) : Event
11 |
12 | data class MessageUpdateEvent(
13 | val data: ApiMessagePartial,
14 | ) : Event
15 |
16 | data class MessageDeleteEvent(
17 | val data: MessageDeleteData,
18 | ) : Event
19 |
20 | data class MessageAckEvent(
21 | val data: MessageAckData,
22 | ) : Event
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/Reaction.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.gateway.dto.MessageReactionAddData
4 | import com.xinto.opencord.gateway.dto.MessageReactionRemoveAllData
5 | import com.xinto.opencord.gateway.dto.MessageReactionRemoveData
6 |
7 | data class MessageReactionAddEvent(
8 | val data: MessageReactionAddData
9 | ) : Event
10 |
11 | data class MessageReactionRemoveEvent(
12 | val data: MessageReactionRemoveData
13 | ) : Event
14 |
15 | data class MessageReactionRemoveAllEvent(
16 | val data: MessageReactionRemoveAllData,
17 | ) : Event
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/Ready.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.gateway.dto.Ready
4 |
5 | data class ReadyEvent(
6 | val data: Ready
7 | ) : Event
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/Sessions.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.gateway.dto.ReplaceSessionsData
4 |
5 | data class SessionsReplaceEvent(
6 | val data: ReplaceSessionsData,
7 | ) : Event
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/UserSettings.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.rest.models.user.settings.ApiUserSettingsPartial
4 |
5 | data class UserSettingsUpdateEvent(
6 | val data: ApiUserSettingsPartial
7 | ) : Event
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/event/UserUpdateEvent.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.event
2 |
3 | import com.xinto.opencord.rest.models.user.ApiUser
4 |
5 | data class UserUpdateEvent(
6 | val data: ApiUser
7 | ) : Event
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/io/Capabilities.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.io
2 |
3 | @Suppress("unused")
4 | enum class Capabilities(val value: Int) {
5 | LAZY_USER_NOTES(1 shl 0),
6 | NO_AFFINE_USER_IDS(1 shl 1),
7 | VERSIONED_READ_STATES(1 shl 2),
8 | VERSIONED_USER_GUILD_SETTINGS(1 shl 3),
9 | DEDUPLICATE_USER_OBJECTS(1 shl 4),
10 | PRIORITIZED_READY_PAYLOAD(1 shl 5),
11 | MULTIPLE_GUILD_EXPERIMENT_POPULATIONS(1 shl 6),
12 | NON_CHANNEL_READ_STATES(1 shl 7),
13 | AUTH_TOKEN_REFRESH(1 shl 8),
14 | USER_SETTINGS_PROTO(1 shl 9),
15 | CLIENT_STATE_V2(1 shl 10),
16 | PASSIVE_GUILD_UPDATE(1 shl 11),
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/io/OpCode.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.io
2 |
3 | import com.github.materiiapps.enumutil.FromValue
4 | import kotlinx.serialization.KSerializer
5 | import kotlinx.serialization.Serializable
6 | import kotlinx.serialization.descriptors.PrimitiveKind
7 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
8 | import kotlinx.serialization.descriptors.SerialDescriptor
9 | import kotlinx.serialization.encoding.Decoder
10 | import kotlinx.serialization.encoding.Encoder
11 |
12 | @FromValue
13 | @Serializable(OpCode.Serializer::class)
14 | enum class OpCode(val code: Int) {
15 | Dispatch(0),
16 | Heartbeat(1),
17 | Identify(2),
18 | PresenceUpdate(3),
19 | VoiceStateUpdate(4),
20 | Resume(6),
21 | Reconnect(7),
22 | RequestGuildMembers(8),
23 | InvalidSession(9),
24 | Hello(10),
25 | HeartbeatAck(11);
26 |
27 | companion object Serializer : KSerializer {
28 | override val descriptor: SerialDescriptor
29 | get() = PrimitiveSerialDescriptor("OpCode", PrimitiveKind.INT)
30 |
31 | override fun deserialize(decoder: Decoder): OpCode {
32 | val opCode = decoder.decodeInt()
33 | return fromValue(opCode) ?: throw IllegalArgumentException("Unknown OpCode $opCode")
34 | }
35 |
36 | override fun serialize(encoder: Encoder, value: OpCode) {
37 | encoder.encodeInt(value.code)
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/gateway/io/Payload.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.gateway.io
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 | import kotlinx.serialization.json.JsonElement
6 |
7 | @Serializable
8 | data class IncomingPayload(
9 | @SerialName("op")
10 | val opCode: OpCode,
11 |
12 | @SerialName("d")
13 | val data: JsonElement?,
14 |
15 | @SerialName("s")
16 | val sequenceNumber: Int?,
17 |
18 | @SerialName("t")
19 | val eventName: EventName?,
20 | )
21 |
22 | @Serializable
23 | data class OutgoingPayload(
24 | @SerialName("op")
25 | val opCode: OpCode,
26 |
27 | @SerialName("d")
28 | val data: T?,
29 | )
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/manager/ActivityManager.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.manager
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import kotlin.reflect.KClass
7 |
8 | interface ActivityManager {
9 | fun startActivity(activity: KClass, clearOld: Boolean = true)
10 | }
11 |
12 | class ActivityManagerImpl(
13 | private val appContext: Context
14 | ) : ActivityManager {
15 | override fun startActivity(activity: KClass, clearOld: Boolean) {
16 | val flags = if (clearOld) {
17 | Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
18 | } else {
19 | Intent.FLAG_ACTIVITY_NEW_TASK
20 | }
21 |
22 | val intent = Intent(appContext, activity.java)
23 | .apply { this.flags = flags }
24 |
25 | appContext.startActivity(intent)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/manager/ClipboardManager.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.manager
2 |
3 | import android.content.ClipData
4 | import android.content.Context
5 | import android.net.Uri
6 | import androidx.core.content.getSystemService
7 |
8 | interface ClipboardManager {
9 | fun setText(text: String)
10 | fun setLink(url: String)
11 | }
12 |
13 | class ClipboardManagerImpl(
14 | appContext: Context,
15 | ) : ClipboardManager {
16 | private val clipboard = appContext.getSystemService()
17 | ?: error("could not get ClipboardManager")
18 |
19 | override fun setText(text: String) {
20 | clipboard.setPrimaryClip(ClipData.newPlainText(null, text))
21 | }
22 |
23 | override fun setLink(url: String) {
24 | clipboard.setPrimaryClip(ClipData.newRawUri(null, Uri.parse(url)))
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/manager/ToastManager.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.manager
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import androidx.annotation.StringRes
6 |
7 | interface ToastManager {
8 | fun showToast(
9 | @StringRes stringId: Int,
10 | vararg args: Any,
11 | duration: Int = Toast.LENGTH_LONG
12 | )
13 |
14 | fun showToast(
15 | text: CharSequence,
16 | duration: Int = Toast.LENGTH_LONG,
17 | )
18 | }
19 |
20 | class ToastManagerImpl(
21 | private val appContext: Context,
22 | ) : ToastManager {
23 | override fun showToast(stringId: Int, vararg args: Any, duration: Int) {
24 | Toast.makeText(
25 | appContext,
26 | appContext.getString(stringId, *args),
27 | duration,
28 | ).show()
29 | }
30 |
31 | override fun showToast(text: CharSequence, duration: Int) {
32 | Toast.makeText(
33 | appContext,
34 | text,
35 | duration,
36 | ).show()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/manager/base/BasePreferenceManager.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.manager.base
2 |
3 | import android.content.SharedPreferences
4 | import androidx.core.content.edit
5 |
6 | abstract class BasePreferenceManager(
7 | private val prefs: SharedPreferences
8 | ) {
9 | protected fun getString(key: String, defaultValue: String?): String? {
10 | return prefs.getString(key, defaultValue)
11 | }
12 |
13 | protected fun getBoolean(key: String, defaultValue: Boolean): Boolean {
14 | return prefs.getBoolean(key, defaultValue)
15 | }
16 |
17 | protected fun getLong(key: String, defaultValue: Long): Long {
18 | return prefs.getLong(key, defaultValue)
19 | }
20 |
21 | protected fun getInt(key: String, defaultValue: Int): Int {
22 | return prefs.getInt(key, defaultValue)
23 | }
24 |
25 | protected fun getStringSet(key: String, defaultValue: Set): Set? {
26 | return prefs.getStringSet(key, defaultValue)
27 | }
28 |
29 | protected fun putString(key: String, value: String?) {
30 | return prefs.edit { putString(key, value) }
31 | }
32 |
33 | protected fun putBoolean(key: String, value: Boolean) {
34 | return prefs.edit { putBoolean(key, value) }
35 | }
36 |
37 | protected fun putLong(key: String, value: Long) {
38 | return prefs.edit { putLong(key, value) }
39 | }
40 |
41 | protected fun putInt(key: String, value: Int) {
42 | return prefs.edit { putInt(key, value) }
43 | }
44 |
45 | protected fun putStringSet(key: String, value: Set) {
46 | prefs.edit { putStringSet(key, value) }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/body/Login.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.body
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class LoginBody(
8 | @SerialName("login")
9 | val login: String,
10 |
11 | @SerialName("password")
12 | val password: String,
13 |
14 | @SerialName("undelete")
15 | val undelete: Boolean,
16 |
17 | @SerialName("captcha_key")
18 | val captchaKey: String? = null,
19 | )
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/body/Message.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.body
2 |
3 | import com.xinto.opencord.rest.models.ApiSnowflake
4 | import com.xinto.opencord.util.NonceGenerator
5 | import kotlinx.serialization.EncodeDefault
6 | import kotlinx.serialization.ExperimentalSerializationApi
7 | import kotlinx.serialization.SerialName
8 | import kotlinx.serialization.Serializable
9 |
10 | @OptIn(ExperimentalSerializationApi::class)
11 | @Serializable
12 | data class MessageBody(
13 | val content: String,
14 | val flags: Int = 0,
15 | val tts: Boolean = false,
16 |
17 | @EncodeDefault
18 | @SerialName("sticker_ids")
19 | val stickers: List = emptyList(),
20 |
21 | @EncodeDefault
22 | val nonce: String = NonceGenerator.nowSnowflake(),
23 | )
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/body/TwoFactor.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.body
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class TwoFactorBody(
8 | @SerialName("code")
9 | val code: String,
10 |
11 | @SerialName("ticket")
12 | val ticket: String,
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/body/XSuperProperties.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.body
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class XSuperProperties(
8 | @SerialName("browser")
9 | val browser: String,
10 |
11 | @SerialName("browser_user_agent")
12 | val userAgent: String,
13 |
14 | @SerialName("client_build_number")
15 | val clientBuildNumber: Int,
16 |
17 | @SerialName("client_version")
18 | val clientBuildVersion: String,
19 |
20 | @SerialName("device")
21 | val deviceName: String,
22 |
23 | @SerialName("os")
24 | val os: String,
25 |
26 | @SerialName("os_sdk_version")
27 | val osSdkVersion: String,
28 |
29 | @SerialName("os_version")
30 | val osVersion: String,
31 |
32 | @SerialName("system_locale")
33 | val systemLocale: String,
34 |
35 | @SerialName("client_performance_cpu")
36 | val cpuPerformance: Int,
37 |
38 | @SerialName("client_performance_memory")
39 | val memoryPerformance: Int,
40 |
41 | @SerialName("cpu_core_count")
42 | val cpuCores: Int,
43 |
44 | @SerialName("accessibility_support_enabled")
45 | val accessibilitySupport: Boolean,
46 |
47 | @SerialName("accessibility_features")
48 | val accessibilityFeatures: Int,
49 |
50 | @SerialName("device_advertiser_id")
51 | val deviceAdId: String,
52 | )
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/ApiChannel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiChannel(
8 | @SerialName("id")
9 | val id: ApiSnowflake,
10 |
11 | @SerialName("guild_id")
12 | val guildId: ApiSnowflake? = null,
13 |
14 | @SerialName("name")
15 | val name: String,
16 |
17 | @SerialName("type")
18 | val type: Int,
19 |
20 | @SerialName("position")
21 | val position: Int = 0,
22 |
23 | @SerialName("parent_id")
24 | val parentId: ApiSnowflake? = null,
25 |
26 | @SerialName("nsfw")
27 | val nsfw: Boolean = false,
28 |
29 | @SerialName("last_message_id")
30 | val lastMessageId: ApiSnowflake? = null,
31 | )
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/ApiColor.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.toArgb
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | @JvmInline
9 | value class ApiColor(val internalColor: Int) {
10 | val fullArgbColor get() = internalColor or (0xFF shl 24)
11 | // val rgbColor get() = color and 0xFFFFFF
12 |
13 | // val red: Int get() = (rgbColor shr 16) and 0xFF
14 | // val green: Int get() = (rgbColor shr 8) and 0xFF
15 | // val blue: Int get() = (rgbColor shr 0) and 0xFF
16 | }
17 |
18 | fun Color.toApi(): ApiColor = ApiColor(toArgb())
19 |
20 | fun ApiColor.toColor(): Color = Color(fullArgbColor)
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/ApiExperiments.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | // Only used for fetching the fingerprint in auth
6 | @Serializable
7 | data class ApiExperiments(
8 | val fingerprint: String,
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/ApiGuild.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiGuild(
8 | @SerialName("id")
9 | val id: ApiSnowflake,
10 |
11 | @SerialName("name")
12 | val name: String,
13 |
14 | @SerialName("icon")
15 | val icon: String?,
16 |
17 | @SerialName("banner")
18 | val banner: String? = null,
19 |
20 | @SerialName("premium_tier")
21 | val premiumTier: Int,
22 |
23 | @SerialName("premium_subscription_count")
24 | val premiumSubscriptionCount: Int? = null,
25 |
26 | @SerialName("channels")
27 | val channels: List? = null,
28 | )
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/ApiGuildMemberChunk.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiGuildMemberChunk(
8 | @SerialName("guild_id")
9 | val guildId: ApiSnowflake,
10 |
11 | @SerialName("members")
12 | val members: List,
13 |
14 | @SerialName("chunk_index")
15 | val chunkIndex: Int,
16 |
17 | @SerialName("chunk_count")
18 | val chunkCount: Int,
19 | )
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/ApiPermissions.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | @JvmInline
7 | value class ApiPermissions(val value: Long) {
8 | constructor(flags: String) : this(flags.toLong())
9 |
10 | override fun toString(): String = value.toString()
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/GuildMember.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models
2 |
3 | import com.xinto.opencord.rest.models.user.ApiUser
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ApiGuildMember(
9 | @SerialName("user")
10 | val user: ApiUser?,
11 |
12 | @SerialName("nick")
13 | val nick: String?,
14 |
15 | @SerialName("avatar")
16 | val avatar: String?
17 | )
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/Snowflake.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | @JvmInline
7 | value class ApiSnowflake(val value: Long) {
8 | constructor(value: String) : this(value.toLong())
9 |
10 | override fun toString(): String = value.toString()
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/activity/ApiActivityAssets.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.activity
2 |
3 | import com.xinto.opencord.domain.activity.DomainActivityAssets
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ApiActivityAssets(
9 | @SerialName("large_image")
10 | val largeImage: String? = null,
11 |
12 | @SerialName("large_text")
13 | val largeText: String? = null,
14 |
15 | @SerialName("small_image")
16 | val smallImage: String? = null,
17 |
18 | @SerialName("small_text")
19 | val smallText: String? = null,
20 | )
21 |
22 | fun DomainActivityAssets.toApi(): ApiActivityAssets {
23 | return ApiActivityAssets(
24 | largeImage = largeImage,
25 | largeText = largeText,
26 | smallImage = smallImage,
27 | smallText = smallText,
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/activity/ApiActivityButton.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.activity
2 |
3 | //@Serializable
4 | //data class ApiActivityButton(
5 | // val label: String,
6 | // val url: String,
7 | //)
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/activity/ApiActivityMetadata.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.activity
2 |
3 | import com.xinto.opencord.domain.activity.DomainActivityMetadata
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ApiActivityMetadata(
9 | @SerialName("album_id")
10 | val albumId: String? = null,
11 |
12 | @SerialName("artist_ids")
13 | val artistIds: List? = null,
14 |
15 | @SerialName("context_uri")
16 | val contextUri: String? = null,
17 | )
18 |
19 | fun DomainActivityMetadata.toApi(): ApiActivityMetadata {
20 | return ApiActivityMetadata(
21 | albumId = albumId,
22 | artistIds = artistIds,
23 | contextUri = contextUri,
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/activity/ApiActivityParty.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.activity
2 |
3 | import com.xinto.opencord.domain.activity.DomainActivityParty
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiActivityParty(
8 | val id: String? = null,
9 | val size: List? = null,
10 | )
11 |
12 | fun DomainActivityParty.toApi(): ApiActivityParty {
13 | return ApiActivityParty(
14 | id = id,
15 | size = if (currentSize == null || maxSize == null) null else {
16 | listOf(currentSize, maxSize)
17 | },
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/activity/ApiActivitySecrets.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.activity
2 |
3 | import com.xinto.opencord.domain.activity.DomainActivitySecrets
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiActivitySecrets(
8 | val join: String? = null,
9 | val spectate: String? = null,
10 | val match: String? = null,
11 | )
12 |
13 | fun DomainActivitySecrets.toApi(): ApiActivitySecrets {
14 | return ApiActivitySecrets(
15 | join = join,
16 | spectate = spectate,
17 | match = match,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/activity/ApiActivityTimestamp.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.activity
2 |
3 | import com.xinto.opencord.domain.activity.DomainActivityTimestamp
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiActivityTimestamp(
8 | val start: String? = null,
9 | val end: String? = null,
10 | )
11 |
12 | fun DomainActivityTimestamp.toApi(): ApiActivityTimestamp {
13 | return ApiActivityTimestamp(
14 | start = start?.toEpochMilliseconds().toString(),
15 | end = end?.toEpochMilliseconds().toString(),
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/embed/ApiEmbedAuthor.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.embed
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiEmbedAuthor(
8 | @SerialName("name")
9 | val name: String,
10 |
11 | @SerialName("url")
12 | val url: String? = null,
13 |
14 | @SerialName("icon_url")
15 | val iconUrl: String? = null,
16 |
17 | @SerialName("proxy_icon_url")
18 | val proxyIconUrl: String? = null,
19 | )
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/embed/ApiEmbedField.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.embed
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiEmbedField(
8 | @SerialName("name")
9 | val name: String,
10 |
11 | @SerialName("value")
12 | val value: String,
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/embed/ApiEmbedFooter.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.embed
2 |
3 | import com.xinto.opencord.domain.embed.DomainEmbedFooter
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ApiEmbedFooter(
9 | @SerialName("text")
10 | val text: String,
11 |
12 | @SerialName("icon_url")
13 | val iconUrl: String? = null,
14 |
15 | @SerialName("proxy_icon_url")
16 | val proxyIconUrl: String? = null,
17 | )
18 |
19 | fun DomainEmbedFooter.toApi(): ApiEmbedFooter {
20 | return ApiEmbedFooter(
21 | text = text,
22 | iconUrl = iconUrl,
23 | proxyIconUrl = proxyIconUrl,
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/embed/ApiEmbedMedia.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.embed
2 |
3 | import com.xinto.opencord.util.queryParameters
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ApiEmbedMedia(
9 | @SerialName("url")
10 | val url: String,
11 |
12 | @SerialName("proxy_url")
13 | val proxyUrl: String? = null,
14 |
15 | @SerialName("height")
16 | val height: Int? = null,
17 |
18 | @SerialName("width")
19 | val width: Int? = null,
20 | ) {
21 | /**
22 | * Calculates the proxy url with size or raw url if none
23 | * Only used for images
24 | */
25 | val sizedUrl: String by lazy {
26 | if (proxyUrl != null) {
27 | val params = queryParameters(2) {
28 | width?.also { append("width", it.toString()) }
29 | height?.also { append("height", it.toString()) }
30 | }
31 | "$proxyUrl$params"
32 | } else {
33 | url
34 | }
35 | }
36 |
37 | val aspectRatio: Float
38 | get() = (width?.toFloat() ?: 1f) / (height?.toFloat() ?: 1f)
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/embed/ApiEmbedProvider.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.embed
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiEmbedProvider(
8 | @SerialName("name")
9 | val name: String? = null,
10 |
11 | @SerialName("url")
12 | val url: String? = null,
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/emoji/ApiEmoji.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.emoji
2 |
3 | import com.github.materiiapps.partial.Partial
4 | import com.github.materiiapps.partial.missing
5 | import com.github.materiiapps.partial.partial
6 | import com.xinto.opencord.domain.emoji.DomainEmoji
7 | import com.xinto.opencord.domain.emoji.DomainGuildEmoji
8 | import com.xinto.opencord.domain.emoji.DomainUnicodeEmoji
9 | import com.xinto.opencord.domain.emoji.DomainUnknownEmoji
10 | import com.xinto.opencord.rest.models.ApiSnowflake
11 | import kotlinx.serialization.Serializable
12 |
13 | @Serializable
14 | data class ApiEmoji(
15 | val name: String?,
16 | val id: ApiSnowflake?,
17 | val animated: Partial = missing(),
18 | )
19 |
20 | fun DomainEmoji.toApi(): ApiEmoji? {
21 | return when (this) {
22 | is DomainUnicodeEmoji -> ApiEmoji(
23 | name = emoji,
24 | id = null,
25 | )
26 | is DomainGuildEmoji -> ApiEmoji(
27 | name = name,
28 | id = ApiSnowflake(id),
29 | animated = if (animated) partial(true) else missing(),
30 | )
31 | is DomainUnknownEmoji -> null
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/login/ApiLogin.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.login
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiLogin(
8 | @SerialName("token")
9 | val token: String? = null,
10 |
11 | @SerialName("mfa")
12 | val mfa: Boolean = false,
13 |
14 | @SerialName("user_settings")
15 | val userSettings: ApiLoginUserSettings? = null,
16 |
17 | @SerialName("captcha_sitekey")
18 | val captchaSiteKey: String? = null,
19 |
20 | @SerialName("ticket")
21 | val ticket: String? = null,
22 | )
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/login/ApiLoginUserSettings.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.login
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ApiLoginUserSettings(
8 | @SerialName("locale")
9 | val locale: String,
10 |
11 | @SerialName("theme")
12 | val theme: String,
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/message/ApiMessageType.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.message
2 |
3 | import com.github.materiiapps.enumutil.FromValue
4 | import kotlinx.serialization.KSerializer
5 | import kotlinx.serialization.Serializable
6 | import kotlinx.serialization.descriptors.PrimitiveKind
7 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
8 | import kotlinx.serialization.descriptors.SerialDescriptor
9 | import kotlinx.serialization.encoding.Decoder
10 | import kotlinx.serialization.encoding.Encoder
11 |
12 | @FromValue
13 | @Serializable(ApiMessageType.Serializer::class)
14 | enum class ApiMessageType(val value: Int) {
15 | Unknown(-1),
16 | Default(0),
17 | GuildMemberJoin(7),
18 | Reply(19);
19 |
20 | companion object Serializer : KSerializer {
21 | override val descriptor: SerialDescriptor
22 | get() = PrimitiveSerialDescriptor("ApiMessageType", PrimitiveKind.INT)
23 |
24 | override fun deserialize(decoder: Decoder): ApiMessageType {
25 | return fromValue(decoder.decodeInt()) ?: Unknown
26 | }
27 |
28 | override fun serialize(encoder: Encoder, value: ApiMessageType) {
29 | encoder.encodeInt(value.value)
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/reaction/ApiReaction.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.reaction
2 |
3 | import com.xinto.opencord.rest.models.emoji.ApiEmoji
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ApiReaction(
9 | @SerialName("emoji")
10 | val emoji: ApiEmoji,
11 |
12 | @SerialName("count")
13 | val count: Int,
14 |
15 | @SerialName("me")
16 | val meReacted: Boolean,
17 | )
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/user/settings/ApiCustomStatus.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.user.settings
2 |
3 | import com.xinto.opencord.domain.usersettings.DomainCustomStatus
4 | import com.xinto.opencord.rest.models.ApiSnowflake
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | data class ApiCustomStatus(
10 | @SerialName("text")
11 | val text: String?,
12 |
13 | @SerialName("expires_at")
14 | val expiresAt: String?,
15 |
16 | @SerialName("emoji_id")
17 | val emojiId: ApiSnowflake?,
18 |
19 | @SerialName("emoji_name")
20 | val emojiName: String?
21 | )
22 |
23 | fun DomainCustomStatus.toApi(): ApiCustomStatus {
24 | return ApiCustomStatus(
25 | text = text,
26 | expiresAt = null, // TODO: make timestamp serializer
27 | emojiId = emojiId?.let { ApiSnowflake(it) },
28 | emojiName = emojiName,
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/user/settings/ApiFriendSources.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.user.settings
2 |
3 | import com.xinto.opencord.domain.usersettings.DomainFriendSources
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ApiFriendSources(
9 | @SerialName("all")
10 | val all: Boolean? = null,
11 |
12 | @SerialName("mutual_friends")
13 | val mutualFriends: Boolean? = null,
14 |
15 | @SerialName("mutual_guilds")
16 | val mutualGuilds: Boolean? = null,
17 | )
18 |
19 | fun DomainFriendSources.toApi(): ApiFriendSources {
20 | return ApiFriendSources(
21 | all = all,
22 | mutualFriends = mutualFriends,
23 | mutualGuilds = mutualGuilds,
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/models/user/settings/ApiGuildFolder.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.models.user.settings
2 |
3 | import com.xinto.opencord.domain.usersettings.DomainGuildFolder
4 | import com.xinto.opencord.rest.models.ApiSnowflake
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | data class ApiGuildFolder(
10 | @SerialName("guild_ids")
11 | val guildIds: List,
12 |
13 | @SerialName("id")
14 | val id: ApiSnowflake? = null,
15 |
16 | @SerialName("name")
17 | val name: String? = null,
18 | )
19 |
20 | fun DomainGuildFolder.toApi(): ApiGuildFolder {
21 | return ApiGuildFolder(
22 | id = id?.let { ApiSnowflake(it) },
23 | guildIds = guildIds.map { ApiSnowflake(it) },
24 | name = name,
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/rest/service/DiscordCdnService.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.rest.service
2 |
3 | import com.xinto.opencord.BuildConfig
4 |
5 | interface DiscordCdnService
6 |
7 | class DiscordCdnServiceImpl : DiscordCdnService {
8 | companion object {
9 | private const val BASE = BuildConfig.URL_CDN
10 |
11 | fun getDefaultAvatarUrl(avatar: Int): String {
12 | return "$BASE/embed/avatars/$avatar.png"
13 | }
14 |
15 | fun getUserAvatarUrl(userId: Long, avatarHash: String): String {
16 | return "$BASE/avatars/${userId}/$avatarHash.webp?size=100"
17 | }
18 |
19 | fun getGuildIconUrl(guildId: Long, iconHash: String): String {
20 | return "$BASE/icons/$guildId/$iconHash.webp?size=128"
21 | }
22 |
23 | fun getGuildBannerUrl(guildId: Long, iconHash: String): String {
24 | return "$BASE/banners/$guildId/$iconHash.webp?size=1024"
25 | }
26 |
27 | fun getGuildEmojiUrl(emojiId: Long, animated: Boolean): String {
28 | val ext = if (animated) "gif" else "webp"
29 | return "$BASE/emojis/$emojiId.$ext?size=64&quality=lossless"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/store/Event.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("NOTHING_TO_INLINE")
2 |
3 | package com.xinto.opencord.store
4 |
5 | sealed interface Event {
6 | data class Add(val data: A) : Event
7 | data class Update(val data: U) : Event
8 | data class Delete(val data: D) : Event
9 |
10 | @Suppress("FunctionName")
11 | companion object {
12 | inline fun Add(data: A) = Event.Add(data)
13 | inline fun Update(data: U) = Event.Update(data)
14 | inline fun Delete(data: D) = Event.Delete(data)
15 | }
16 | }
17 |
18 | inline fun Event.fold(
19 | onAdd: (A) -> R,
20 | onUpdate: (U) -> R,
21 | onDelete: (D) -> R,
22 | ): R {
23 | return when (this) {
24 | is Event.Add -> onAdd.invoke(data)
25 | is Event.Update -> onUpdate.invoke(data)
26 | is Event.Delete -> onDelete.invoke(data)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/SplashScreenActivity.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
7 | import com.xinto.opencord.manager.AccountManager
8 | import com.xinto.opencord.manager.ActivityManager
9 | import org.koin.android.ext.android.inject
10 |
11 | @SuppressLint("CustomSplashScreen")
12 | class SplashScreenActivity : ComponentActivity() {
13 | private val accountManager: AccountManager by inject()
14 | private val activityManager: ActivityManager by inject()
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | installSplashScreen()
18 | super.onCreate(savedInstanceState)
19 |
20 | val activity = if (accountManager.isLoggedIn) AppActivity::class else LoginActivity::class
21 |
22 | finishAndRemoveTask()
23 | activityManager.startActivity(activity)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/OCBadgeBox.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.shape.CircleShape
7 | import androidx.compose.material3.Surface
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Shape
12 | import androidx.compose.ui.unit.dp
13 |
14 | @Composable
15 | fun OCBadgeBox(
16 | modifier: Modifier = Modifier,
17 | badge: (@Composable () -> Unit)?,
18 | badgeShape: Shape = CircleShape,
19 | badgeAlignment: Alignment = Alignment.BottomEnd,
20 | badgePadding: PaddingValues = PaddingValues(),
21 | content: @Composable () -> Unit,
22 | ) {
23 | Box(
24 | contentAlignment = Alignment.Center,
25 | modifier = modifier,
26 | ) {
27 | content()
28 | if (badge != null) {
29 | Surface(
30 | shape = badgeShape,
31 | modifier = Modifier
32 | .padding(badgePadding)
33 | .align(badgeAlignment),
34 | ) {
35 | Box(
36 | modifier = Modifier.padding(3.dp),
37 | ) {
38 | badge()
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/attachment/AttachmentPicture.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.attachment
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.draw.clip
7 | import com.xinto.opencord.ui.components.OCImage
8 | import com.xinto.opencord.ui.components.OCSize
9 |
10 | @Composable
11 | fun AttachmentPicture(
12 | url: String,
13 | width: Int,
14 | height: Int,
15 | modifier: Modifier = Modifier,
16 | ) {
17 | OCImage(
18 | url = url,
19 | size = OCSize(width, height),
20 | modifier = modifier
21 | .clip(MaterialTheme.shapes.small),
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/captcha/HCaptcha.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.captcha
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisposableEffect
5 | import androidx.compose.ui.platform.LocalContext
6 | import com.hcaptcha.sdk.HCaptcha
7 | import com.hcaptcha.sdk.HCaptchaError
8 | import org.koin.compose.koinInject
9 | import org.koin.core.parameter.parametersOf
10 |
11 | @Composable
12 | fun HCaptcha(
13 | onSuccess: (token: String) -> Unit,
14 | onFailure: (HCaptchaError, code: Int) -> Unit,
15 | ) {
16 | val context = LocalContext.current
17 | val captcha: HCaptcha = koinInject { parametersOf(context) }
18 | DisposableEffect(captcha) {
19 | captcha.verifyWithHCaptcha()
20 | .addOnSuccessListener {
21 | onSuccess(it.tokenResult)
22 | }
23 | .addOnFailureListener {
24 | onFailure(it.hCaptchaError, it.statusCode)
25 | }
26 | onDispose {}
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/channel/list/ChannelListCategoryItem.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.channel.list
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.material3.LocalTextStyle
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.CompositionLocalProvider
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.clip
12 | import androidx.compose.ui.unit.dp
13 |
14 | @Composable
15 | fun ChannelListCategoryItem(
16 | modifier: Modifier = Modifier,
17 | title: (@Composable () -> Unit),
18 | icon: (@Composable () -> Unit),
19 | onClick: () -> Unit,
20 | ) {
21 | Box(
22 | modifier = modifier
23 | .clip(MaterialTheme.shapes.small)
24 | .clickable(onClick = onClick),
25 | ) {
26 | Row(
27 | horizontalArrangement = Arrangement.spacedBy(4.dp),
28 | modifier = Modifier
29 | .fillMaxWidth()
30 | .padding(4.dp),
31 | verticalAlignment = Alignment.CenterVertically,
32 | ) {
33 | CompositionLocalProvider(
34 | LocalTextStyle provides MaterialTheme.typography.labelMedium,
35 | ) {
36 | Box(
37 | modifier = Modifier.size(18.dp),
38 | contentAlignment = Alignment.Center,
39 | ) {
40 | icon()
41 | }
42 | title()
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/embed/EmbedField.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.embed
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.ProvideTextStyle
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 |
12 | @Composable
13 | fun EmbedField(
14 | name: String,
15 | value: String,
16 | modifier: Modifier = Modifier,
17 | ) {
18 | Column(
19 | modifier = modifier,
20 | verticalArrangement = Arrangement.spacedBy(4.dp),
21 | ) {
22 | ProvideTextStyle(MaterialTheme.typography.labelMedium) {
23 | Text(name)
24 | }
25 | ProvideTextStyle(MaterialTheme.typography.bodySmall) {
26 | Text(value)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/embed/EmbedFooter.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.embed
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.foundation.shape.CircleShape
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.ProvideTextStyle
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clip
14 | import androidx.compose.ui.unit.dp
15 | import com.xinto.opencord.ui.components.OCImage
16 | import com.xinto.opencord.ui.components.OCSize
17 |
18 | @Composable
19 | fun EmbedFooter(
20 | text: String,
21 | iconUrl: String?,
22 | timestamp: String?,
23 | modifier: Modifier = Modifier,
24 | ) {
25 | Row(
26 | modifier = modifier,
27 | horizontalArrangement = Arrangement.spacedBy(4.dp),
28 | verticalAlignment = Alignment.CenterVertically,
29 | ) {
30 | if (iconUrl != null) {
31 | OCImage(
32 | url = iconUrl,
33 | size = OCSize(64, 64),
34 | modifier = Modifier
35 | .size(24.dp)
36 | .clip(CircleShape),
37 | )
38 | }
39 |
40 | ProvideTextStyle(MaterialTheme.typography.labelSmall) {
41 | Text(text)
42 |
43 | if (timestamp != null) {
44 | Text("•")
45 | Text(timestamp)
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/embed/SpotifyEmbed.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.embed
2 |
3 | import android.annotation.SuppressLint
4 | import android.graphics.Color
5 | import android.webkit.WebSettings
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.unit.dp
10 | import com.google.accompanist.web.WebView
11 | import com.google.accompanist.web.rememberWebViewState
12 | import com.xinto.opencord.ui.util.ScrollableWebView
13 |
14 | @Composable
15 | fun SpotifyEmbed(
16 | embedUrl: String,
17 | isSpotifyTrack: Boolean,
18 | ) {
19 | WebView(
20 | state = rememberWebViewState(embedUrl),
21 | captureBackPresses = false,
22 | onCreated = { webView ->
23 | webView.setBackgroundColor(Color.TRANSPARENT)
24 | webView.settings.apply {
25 | @SuppressLint("SetJavaScriptEnabled")
26 | javaScriptEnabled = true
27 | allowFileAccess = false
28 | mediaPlaybackRequiresUserGesture = false
29 | cacheMode = WebSettings.LOAD_NO_CACHE
30 | setGeolocationEnabled(false)
31 | }
32 | },
33 | factory = { ScrollableWebView(it) },
34 | modifier = Modifier
35 | .height(if (isSpotifyTrack) 80.dp else 500.dp),
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/guild/list/GuildsListHeaderItem.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.guild.list
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.res.painterResource
11 | import androidx.compose.ui.res.stringResource
12 | import androidx.compose.ui.unit.dp
13 | import com.xinto.opencord.R
14 |
15 | @Composable
16 | fun GuildsListHeaderItem(
17 | modifier: Modifier = Modifier,
18 | ) {
19 | Box(
20 | contentAlignment = Alignment.Center,
21 | modifier = modifier.size(48.dp),
22 | ) {
23 | Icon(
24 | modifier = Modifier
25 | .size(35.dp),
26 | tint = MaterialTheme.colorScheme.primary,
27 | painter = painterResource(R.drawable.ic_discord_logo),
28 | contentDescription = stringResource(R.string.guilds_home),
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/guild/list/GuildsListItemImage.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.guild.list
2 |
3 | import androidx.compose.foundation.layout.size
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.unit.dp
7 | import com.xinto.opencord.ui.components.OCImage
8 |
9 | @Composable
10 | fun GuildsListItemImage(
11 | url: String,
12 | modifier: Modifier = Modifier,
13 | ) {
14 | OCImage(
15 | url = url,
16 | memoryCaching = true,
17 | modifier = modifier
18 | .size(48.dp),
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/indicator/UnreadIndicator.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.indicator
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.width
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.draw.clip
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.unit.dp
12 |
13 | @Composable
14 | fun UnreadIndicator(
15 | modifier: Modifier = Modifier,
16 | ) {
17 | Box(
18 | modifier = modifier
19 | .clip(
20 | RoundedCornerShape(
21 | topEnd = 12.dp,
22 | bottomEnd = 12.dp,
23 | ),
24 | )
25 | .background(Color.White)
26 | .width(4.dp),
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/message/MessageAvatar.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.message
2 |
3 | import androidx.compose.foundation.shape.CircleShape
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.draw.clip
7 | import com.xinto.opencord.ui.components.OCImage
8 | import com.xinto.opencord.ui.components.OCSize
9 |
10 | @Composable
11 | fun MessageAvatar(
12 | url: String,
13 | modifier: Modifier = Modifier,
14 | ) {
15 | OCImage(
16 | url = url,
17 | memoryCaching = true,
18 | size = OCSize(100, 100),
19 | modifier = modifier
20 | .clip(CircleShape),
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/message/MessageContent.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.message
2 |
3 | import androidx.compose.material3.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.text.AnnotatedString
7 | import com.xinto.opencord.ui.util.messageInlineContent
8 |
9 | @Composable
10 | fun MessageContent(
11 | text: AnnotatedString,
12 | modifier: Modifier = Modifier,
13 | ) {
14 | Text(
15 | modifier = modifier,
16 | text = text,
17 | inlineContent = messageInlineContent(),
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/message/reply/MessageReferenced.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.message.reply
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.ProvideTextStyle
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.unit.dp
13 |
14 | @Composable
15 | fun MessageReferenced(
16 | modifier: Modifier = Modifier,
17 | avatar: @Composable (() -> Unit)? = null,
18 | author: @Composable (() -> Unit)? = null,
19 | content: @Composable (() -> Unit)? = null,
20 | ) {
21 | Row(
22 | modifier = modifier,
23 | horizontalArrangement = Arrangement.spacedBy(4.dp),
24 | verticalAlignment = Alignment.CenterVertically,
25 | ) {
26 | if (avatar != null) {
27 | Box(
28 | modifier = Modifier.size(20.dp),
29 | contentAlignment = Alignment.Center,
30 | ) {
31 | avatar()
32 | }
33 | }
34 | if (author != null) {
35 | ProvideTextStyle(MaterialTheme.typography.labelMedium) {
36 | author()
37 | }
38 | }
39 | if (content != null) {
40 | ProvideTextStyle(MaterialTheme.typography.bodySmall) {
41 | content()
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/message/reply/MessageReferencedAuthor.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.message.reply
2 |
3 | import androidx.compose.material3.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.text.style.TextOverflow
7 |
8 | @Composable
9 | fun MessageReferencedAuthor(
10 | author: String,
11 | modifier: Modifier = Modifier
12 | ) {
13 | Text(
14 | modifier = modifier,
15 | text = author,
16 | overflow = TextOverflow.Ellipsis,
17 | maxLines = 1,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/message/reply/MessageReferencedContent.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.message.reply
2 |
3 | import androidx.compose.material3.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.text.AnnotatedString
7 | import androidx.compose.ui.text.style.TextOverflow
8 | import com.xinto.opencord.ui.util.messageInlineContent
9 |
10 | @Composable
11 | fun MessageReferencedContent(
12 | text: AnnotatedString,
13 | modifier: Modifier = Modifier,
14 | ) {
15 | Text(
16 | modifier = modifier,
17 | text = text,
18 | inlineContent = messageInlineContent(),
19 | overflow = TextOverflow.Ellipsis,
20 | maxLines = 1,
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/components/message/reply/MessageReplyBranch.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.components.message.reply
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.graphics.Path
9 | import androidx.compose.ui.graphics.drawscope.Stroke
10 |
11 | @Composable
12 | fun MessageReplyBranch(
13 | modifier: Modifier = Modifier,
14 | color: Color = MaterialTheme.colorScheme.outline
15 | ) {
16 | Canvas(modifier = modifier) {
17 | drawPath(
18 | path = Path().apply {
19 | moveTo(
20 | x = 0f,
21 | y = size.height,
22 | )
23 | cubicTo(
24 | x1 = 0f,
25 | y1 = 0f,
26 | x2 = 0f,
27 | y2 = 0f,
28 | x3 = size.width,
29 | y3 = 0f,
30 | )
31 | },
32 | color = color,
33 | style = Stroke(
34 | width = 5f,
35 | ),
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/navigation/AppDestination.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.navigation
2 |
3 | import android.app.Activity
4 | import android.os.Parcelable
5 | import androidx.compose.runtime.Immutable
6 | import com.xinto.opencord.ui.util.topDestination
7 | import dev.olshevski.navigation.reimagined.NavController
8 | import dev.olshevski.navigation.reimagined.pop
9 | import dev.olshevski.navigation.reimagined.replaceAll
10 | import kotlinx.parcelize.Parcelize
11 |
12 | @Parcelize
13 | sealed interface AppDestination : Parcelable {
14 | @Parcelize
15 | object Main : AppDestination
16 |
17 | @Parcelize
18 | object Settings : AppDestination
19 |
20 | @Parcelize
21 | object Mentions : AppDestination
22 |
23 | @Parcelize
24 | data class Pins(val data: PinsScreenData) : AppDestination
25 | }
26 |
27 | @Parcelize
28 | @Immutable
29 | data class PinsScreenData(
30 | val channelId: Long,
31 | ) : Parcelable
32 |
33 | context(Activity)
34 | fun NavController.back() {
35 | val top = topDestination()
36 |
37 | if (top == AppDestination.Main) {
38 | finish()
39 | } else if (backstack.entries.size > 1) {
40 | pop()
41 | } else {
42 | replaceAll(AppDestination.Main)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/navigation/LoginDestination.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.navigation
2 |
3 | import android.app.Activity
4 | import android.os.Parcelable
5 | import com.xinto.opencord.ui.util.topDestination
6 | import dev.olshevski.navigation.reimagined.NavController
7 | import dev.olshevski.navigation.reimagined.pop
8 | import dev.olshevski.navigation.reimagined.replaceAll
9 | import kotlinx.parcelize.Parcelize
10 |
11 | @Parcelize
12 | sealed interface LoginDestination : Parcelable {
13 | @Parcelize
14 | object Login : LoginDestination
15 |
16 | @Parcelize
17 | object Landing : LoginDestination
18 | }
19 |
20 | context(Activity)
21 | fun NavController.back() {
22 | val top = topDestination()
23 |
24 | if (top == LoginDestination.Landing) {
25 | finish()
26 | } else if (backstack.entries.size > 1) {
27 | pop()
28 | } else {
29 | replaceAll(LoginDestination.Landing)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/screens/SettingsScreen.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.screens
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.material3.*
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.res.painterResource
10 | import androidx.compose.ui.res.stringResource
11 | import com.xinto.opencord.R
12 |
13 | @Composable
14 | fun Settings(
15 | onBackClick: () -> Unit,
16 | modifier: Modifier = Modifier,
17 | ) {
18 | Scaffold(
19 | modifier = modifier,
20 | topBar = {
21 | TopAppBar(
22 | title = { Text(stringResource(R.string.settings_title)) },
23 | navigationIcon = {
24 | IconButton(onClick = onBackClick) {
25 | Icon(
26 | painter = painterResource(R.drawable.ic_arrow_back),
27 | contentDescription = null,
28 | )
29 | }
30 | },
31 | )
32 | },
33 | ) { paddingValues ->
34 | LazyColumn(
35 | modifier = Modifier
36 | .fillMaxSize()
37 | .padding(paddingValues),
38 | ) {
39 |
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/screens/home/panels/channel/ChannelsListUnselected.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.screens.home.panels.channel
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.ProvideTextStyle
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.res.stringResource
11 | import com.xinto.opencord.R
12 |
13 | @Composable
14 | fun ChannelsListUnselected(
15 | modifier: Modifier = Modifier,
16 | ) {
17 | Box(
18 | modifier = modifier,
19 | contentAlignment = Alignment.Center,
20 | ) {
21 | ProvideTextStyle(MaterialTheme.typography.titleMedium) {
22 | Text(stringResource(R.string.channel_unselected_message))
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/screens/home/panels/chat/ChatError.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.screens.home.panels.chat
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.material3.*
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.CompositionLocalProvider
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.res.painterResource
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.compose.ui.text.style.TextAlign
14 | import androidx.compose.ui.unit.dp
15 | import com.xinto.opencord.R
16 |
17 | @Composable
18 | fun ChatError(
19 | modifier: Modifier = Modifier,
20 | ) {
21 | Column(
22 | modifier = modifier,
23 | horizontalAlignment = Alignment.CenterHorizontally,
24 | verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically),
25 | ) {
26 | CompositionLocalProvider(
27 | LocalContentColor provides MaterialTheme.colorScheme.error,
28 | LocalTextStyle provides MaterialTheme.typography.titleMedium,
29 | ) {
30 | Icon(
31 | modifier = Modifier.size(56.dp),
32 | painter = painterResource(R.drawable.ic_error),
33 | contentDescription = null,
34 | )
35 | Text(
36 | text = stringResource(R.string.chat_loading_error),
37 | textAlign = TextAlign.Center,
38 | )
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/screens/home/panels/chat/ChatUnselected.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.screens.home.panels.chat
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.ProvideTextStyle
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.res.stringResource
11 | import com.xinto.opencord.R
12 |
13 | @Composable
14 | fun ChatUnselected(
15 | modifier: Modifier = Modifier,
16 | ) {
17 | Box(
18 | modifier = modifier,
19 | contentAlignment = Alignment.Center,
20 | ) {
21 | ProvideTextStyle(MaterialTheme.typography.titleMedium) {
22 | Text(stringResource(R.string.chat_unselected_message))
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/screens/home/panels/guild/GuildsList.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.screens.home.panels.guild
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import com.xinto.opencord.ui.viewmodel.GuildsViewModel
6 | import org.koin.androidx.compose.getViewModel
7 |
8 | @Composable
9 | fun GuildsList(
10 | onGuildSelect: () -> Unit,
11 | modifier: Modifier = Modifier,
12 | viewModel: GuildsViewModel = getViewModel(),
13 | ) {
14 | when (viewModel.state) {
15 | GuildsViewModel.State.Loading -> {
16 | GuildsListLoading(modifier = modifier)
17 | }
18 | GuildsViewModel.State.Loaded -> {
19 | GuildsListLoaded(
20 | items = viewModel.listItems,
21 | modifier = modifier,
22 | onGuildSelect = {
23 | viewModel.selectGuild(it)
24 | onGuildSelect()
25 | },
26 | )
27 | }
28 | GuildsViewModel.State.Error -> {
29 |
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/screens/home/panels/member/MembersList.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.screens.home.panels.member
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.Surface
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.unit.dp
12 |
13 | @Composable
14 | fun MembersList(
15 | modifier: Modifier,
16 | ) {
17 | Surface(
18 | shape = MaterialTheme.shapes.medium,
19 | tonalElevation = 1.dp,
20 | modifier = modifier,
21 | ) {
22 | Box(
23 | modifier = Modifier.fillMaxSize(),
24 | contentAlignment = Alignment.Center,
25 | ) {
26 | Text(
27 | text = "Members List (soon)",
28 | style = MaterialTheme.typography.bodyMedium,
29 | )
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/screens/pins/PinsScreenError.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.screens.pins
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.size
6 | import androidx.compose.material3.*
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.CompositionLocalProvider
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.res.painterResource
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.compose.ui.text.style.TextAlign
14 | import androidx.compose.ui.unit.dp
15 | import com.xinto.opencord.R
16 |
17 | @Composable
18 | fun PinsScreenError(
19 | modifier: Modifier = Modifier,
20 | ) {
21 | Column(
22 | modifier = modifier,
23 | horizontalAlignment = Alignment.CenterHorizontally,
24 | verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically),
25 | ) {
26 | CompositionLocalProvider(
27 | LocalContentColor provides MaterialTheme.colorScheme.error,
28 | LocalTextStyle provides MaterialTheme.typography.titleMedium,
29 | ) {
30 | Icon(
31 | modifier = Modifier.size(56.dp),
32 | painter = painterResource(R.drawable.ic_error),
33 | contentDescription = null,
34 | )
35 | Text(
36 | text = stringResource(R.string.pins_loading_error),
37 | textAlign = TextAlign.Center,
38 | )
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/screens/pins/PinsScreenLoading.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.screens.pins
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.material3.CircularProgressIndicator
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 |
9 | @Composable
10 | fun PinsScreenLoading(
11 | modifier: Modifier = Modifier
12 | ) {
13 | Box(
14 | modifier = modifier,
15 | contentAlignment = Alignment.Center,
16 | ) {
17 | CircularProgressIndicator()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.theme
2 |
3 | import androidx.compose.material3.Shapes
4 |
5 | val Shapes = Shapes()
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/CompositePaddingValues.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.ui.unit.Dp
5 | import androidx.compose.ui.unit.LayoutDirection
6 | import androidx.compose.ui.unit.dp
7 |
8 | class CompositePaddingValues(private vararg val items: PaddingValues) : PaddingValues {
9 | override fun calculateBottomPadding(): Dp {
10 | return items.fold(0.dp) { acc, paddingValues ->
11 | acc + paddingValues.calculateBottomPadding()
12 | }
13 | }
14 |
15 | override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp {
16 | return items.fold(0.dp) { acc, paddingValues ->
17 | acc + paddingValues.calculateLeftPadding(layoutDirection)
18 | }
19 | }
20 |
21 | override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp {
22 | return items.fold(0.dp) { acc, paddingValues ->
23 | acc + paddingValues.calculateRightPadding(layoutDirection)
24 | }
25 | }
26 |
27 | override fun calculateTopPadding(): Dp {
28 | return items.fold(0.dp) { acc, paddingValues ->
29 | acc + paddingValues.calculateTopPadding()
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/ContentAlpha.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import androidx.compose.material3.LocalContentColor
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.CompositionLocalProvider
6 | import androidx.compose.runtime.ReadOnlyComposable
7 |
8 | object ContentAlpha {
9 | val full: Float
10 | @Composable
11 | @ReadOnlyComposable
12 | get() = 1.0f
13 |
14 | val extraHigh: Float
15 | @Composable
16 | @ReadOnlyComposable
17 | get() = 0.90f
18 |
19 | val veryHigh: Float
20 | @Composable
21 | @ReadOnlyComposable
22 | get() = 0.80f
23 |
24 | val high: Float
25 | @Composable
26 | @ReadOnlyComposable
27 | get() = 0.70f
28 |
29 | val medium: Float
30 | @Composable
31 | @ReadOnlyComposable
32 | get() = 0.60f
33 |
34 | val low: Float
35 | @Composable
36 | @ReadOnlyComposable
37 | get() = 0.50f
38 |
39 | val veryLow: Float
40 | @Composable
41 | @ReadOnlyComposable
42 | get() = 0.40f
43 |
44 | val extraLow: Float
45 | @Composable
46 | @ReadOnlyComposable
47 | get() = 0.30f
48 | }
49 |
50 | @Composable
51 | fun ProvideContentAlpha(value: Float, content: @Composable () -> Unit) {
52 | val opaqueColor = LocalContentColor.current.copy(alpha = value)
53 | CompositionLocalProvider(
54 | LocalContentColor provides opaqueColor,
55 | content = content,
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/CornerBasedShape.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import androidx.compose.foundation.shape.CornerBasedShape
4 | import androidx.compose.foundation.shape.CornerSize
5 | import androidx.compose.ui.geometry.Size
6 | import androidx.compose.ui.graphics.Outline
7 | import androidx.compose.ui.graphics.Path
8 | import androidx.compose.ui.unit.LayoutDirection
9 |
10 | object EmptyCornerBasedShape : CornerBasedShape(
11 | topStart = CornerSize(0f),
12 | topEnd = CornerSize(0f),
13 | bottomStart = CornerSize(0f),
14 | bottomEnd = CornerSize(0f),
15 | ) {
16 | override fun copy(
17 | topStart: CornerSize,
18 | topEnd: CornerSize,
19 | bottomEnd: CornerSize,
20 | bottomStart: CornerSize
21 | ): CornerBasedShape {
22 | return this
23 | }
24 |
25 | override fun createOutline(
26 | size: Size,
27 | topStart: Float,
28 | topEnd: Float,
29 | bottomEnd: Float,
30 | bottomStart: Float,
31 | layoutDirection: LayoutDirection
32 | ): Outline {
33 | return Outline.Generic(Path())
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/LocalViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisposableEffect
5 | import androidx.compose.runtime.remember
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.ViewModelStore
8 | import androidx.lifecycle.ViewModelStoreOwner
9 | import org.koin.androidx.compose.getViewModel
10 | import org.koin.core.annotation.KoinInternalApi
11 | import org.koin.core.context.GlobalContext
12 | import org.koin.core.parameter.ParametersDefinition
13 | import org.koin.core.qualifier.Qualifier
14 | import org.koin.core.scope.Scope
15 |
16 | /**
17 | * Creates and manages a ViewModel bound to the current composable scope.
18 | * Once the composable goes out of scope, the ViewModel is destroyed.
19 | */
20 | @OptIn(KoinInternalApi::class)
21 | @Composable
22 | inline fun getLocalViewModel(
23 | qualifier: Qualifier? = null,
24 | scope: Scope = GlobalContext.get().scopeRegistry.rootScope,
25 | noinline parameters: ParametersDefinition? = null,
26 | ): T {
27 | val owner = remember {
28 | object : ViewModelStoreOwner {
29 | override val viewModelStore = ViewModelStore()
30 | }
31 | }
32 |
33 | DisposableEffect(owner) {
34 | onDispose {
35 | owner.viewModelStore.clear()
36 | }
37 | }
38 |
39 | return getViewModel(
40 | qualifier = qualifier,
41 | viewModelStoreOwner = owner,
42 | scope = scope,
43 | parameters = parameters,
44 | )
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/MessageInlineContent.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import androidx.compose.foundation.text.InlineTextContent
4 | import androidx.compose.material3.LocalTextStyle
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.text.Placeholder
8 | import androidx.compose.ui.text.PlaceholderVerticalAlign
9 | import androidx.compose.ui.unit.sp
10 | import com.xinto.opencord.BuildConfig
11 | import com.xinto.opencord.ui.components.OCImage
12 | import kotlinx.collections.immutable.ImmutableMap
13 | import kotlinx.collections.immutable.persistentHashMapOf
14 |
15 | @Composable
16 | fun messageInlineContent(): ImmutableMap {
17 | val emoteSize = (LocalTextStyle.current.fontSize.value + 2f).sp
18 |
19 | return remember(emoteSize) {
20 | persistentHashMapOf(
21 | "emote" to InlineTextContent(
22 | placeholder = Placeholder(
23 | width = emoteSize,
24 | height = emoteSize,
25 | placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
26 | ),
27 | ) { emoteId ->
28 | OCImage(
29 | url = "${BuildConfig.URL_CDN}/emojis/$emoteId",
30 | memoryCaching = true,
31 | )
32 | },
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/NavUtil.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import dev.olshevski.navigation.reimagined.NavController
4 |
5 | fun NavController.topDestination() =
6 | backstack.entries.lastOrNull()?.destination
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/ScrollableWebView.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.view.MotionEvent
6 | import android.webkit.WebView
7 |
8 | class ScrollableWebView(context: Context) : WebView(context) {
9 | @SuppressLint("ClickableViewAccessibility")
10 | override fun onTouchEvent(event: MotionEvent?): Boolean {
11 | requestDisallowInterceptTouchEvent(true)
12 | return super.onTouchEvent(event)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/UnsafeImmutableList.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import androidx.compose.runtime.Immutable
4 | import kotlinx.collections.immutable.ImmutableList
5 |
6 | /**
7 | * A compose-stable wrapper over a list for performance.
8 | *
9 | * This does NOT guarantee stability. It is merely a stable wrapper over another list,
10 | * and assumes the reader knows that it shouldn't change through crucial parts of rendering.
11 | */
12 | @Immutable
13 | class UnsafeImmutableList(
14 | private val items: List,
15 | ) : ImmutableList {
16 | override val size = items.size
17 |
18 | override fun get(index: Int) = items[index]
19 | override fun isEmpty() = items.isEmpty()
20 | override fun iterator() = items.iterator()
21 | override fun listIterator() = items.listIterator()
22 | override fun listIterator(index: Int) = items.listIterator(index)
23 | override fun lastIndexOf(element: T) = items.lastIndexOf(element)
24 | override fun indexOf(element: T) = items.indexOf(element)
25 | override fun containsAll(elements: Collection) = items.containsAll(elements)
26 | override fun contains(element: T) = items.contains(element)
27 | }
28 |
29 | fun List.toUnsafeImmutableList(): ImmutableList =
30 | UnsafeImmutableList(this)
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/util/VoidablePaddingValues.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.util
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.ui.unit.Dp
5 | import androidx.compose.ui.unit.LayoutDirection
6 | import androidx.compose.ui.unit.dp
7 |
8 | /**
9 | * Disable any (top/bottom/left/right) padding for [PaddingValues],
10 | * without touching the rest of the values.
11 | */
12 | class VoidablePaddingValues(
13 | private val existing: PaddingValues,
14 | private val top: Boolean = true,
15 | private val bottom: Boolean = true,
16 | private val left: Boolean = true,
17 | private val right: Boolean = true,
18 | ) : PaddingValues {
19 | override fun calculateBottomPadding(): Dp {
20 | return if (!bottom) 0.dp else {
21 | existing.calculateBottomPadding()
22 | }
23 | }
24 |
25 | override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp {
26 | return if (!left) 0.dp else {
27 | existing.calculateLeftPadding(layoutDirection)
28 | }
29 | }
30 |
31 | override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp {
32 | return if (!right) 0.dp else {
33 | existing.calculateRightPadding(layoutDirection)
34 | }
35 | }
36 |
37 | override fun calculateTopPadding(): Dp {
38 | return if (!top) 0.dp else {
39 | existing.calculateTopPadding()
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/ui/viewmodel/base/BasePersistenceViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.ui.viewmodel.base
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.xinto.opencord.manager.PersistentDataManager
5 |
6 | abstract class BasePersistenceViewModel(
7 | private val persistentDataManager: PersistentDataManager
8 | ) : ViewModel() {
9 | // TODO: migrate this to a store
10 |
11 | protected var persistentGuildId
12 | get() = persistentDataManager.persistentGuildId
13 | set(value) {
14 | persistentDataManager.persistentGuildId = value
15 | }
16 |
17 | protected var persistentChannelId
18 | get() = persistentDataManager.persistentChannelId
19 | set(value) {
20 | persistentDataManager.persistentChannelId = value
21 | }
22 |
23 | protected var persistentCollapsedCategories
24 | get() = persistentDataManager.collapsedCategories
25 | set(value) {
26 | persistentDataManager.collapsedCategories = value
27 | }
28 |
29 | protected fun addPersistentCollapseCategory(id: Long) {
30 | persistentDataManager.addCollapsedCategory(id)
31 | }
32 |
33 | protected fun removePersistentCollapseCategory(id: Long) {
34 | persistentDataManager.removeCollapsedCategory(id)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/util/FlowUtil.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.util
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Job
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.FlowCollector
7 | import kotlinx.coroutines.launch
8 |
9 | fun Flow.collectIn(scope: CoroutineScope, collector: FlowCollector): Job {
10 | return scope.launch {
11 | this@collectIn.collect(collector)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/util/KtorUtil.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.util
2 |
3 | import io.ktor.http.*
4 |
5 | fun queryParameters(internalSize: Int = 4, block: ParametersBuilder.() -> Unit): String {
6 | val builder = ParametersBuilder(internalSize)
7 | block(builder)
8 |
9 | return if (builder.isEmpty()) {
10 | ""
11 | } else {
12 | "?${builder.build().formUrlEncode()}"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/util/Logger.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.util
2 |
3 | import android.util.Log
4 | import com.xinto.opencord.BuildConfig
5 |
6 | interface Logger {
7 | fun error(tag: String, message: String?, throwable: Throwable?)
8 | fun warn(tag: String, message: String, throwable: Throwable?)
9 | fun info(tag: String, message: String)
10 | fun debug(tag: String, message: String)
11 | }
12 |
13 | class LoggerImpl : Logger {
14 | private val fieldRegex = """"(login|password|email|phone|token)"\s*:\s*"[^"]+"""".toRegex()
15 |
16 | private fun clean(message: String): String {
17 | return message.replace(fieldRegex) {
18 | "\"${it.groupValues[1]}\":\"[OpenCord censored]\""
19 | }
20 | }
21 |
22 | override fun error(tag: String, message: String?, throwable: Throwable?) {
23 | if (message != null || throwable != null)
24 | Log.e(tag, message, throwable)
25 | }
26 |
27 | override fun warn(tag: String, message: String, throwable: Throwable?) {
28 | Log.w(tag, message, throwable)
29 | }
30 |
31 | override fun info(tag: String, message: String) {
32 | Log.i(tag, message)
33 | }
34 |
35 | override fun debug(tag: String, message: String) {
36 | if (!BuildConfig.DEBUG) return
37 |
38 | for (part in clean(message).chunked(4000)) {
39 | Log.d(tag, part)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/util/NonceGenerator.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.util
2 |
3 | object NonceGenerator {
4 | private const val DISCORD_EPOCH = 1420070400000
5 |
6 | fun nowSnowflake(): String {
7 | val timestamp = System.currentTimeMillis() - DISCORD_EPOCH
8 | val snowflake = timestamp.toULong() shl 22
9 | return snowflake.toString()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/util/Quad.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.util
2 |
3 | data class Quad(
4 | val first: A,
5 | val second: B,
6 | val third: C,
7 | val fourth: D,
8 | ) {
9 | override fun toString() = "($first, $second, $third, $fourth)"
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/util/StdUtil.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.util
2 |
3 | inline fun Pair.map(
4 | first: (A) -> C,
5 | second: (B) -> D
6 | ): Pair {
7 | return first(this.first) to second(this.second)
8 | }
9 |
10 | inline fun MutableList.removeFirst(predicate: (T) -> Boolean): T? {
11 | return indexOfFirst(predicate)
12 | .takeIf { it >= 0 }
13 | ?.let { removeAt(it) }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/util/TextParser.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.util
2 |
3 | import com.xinto.simpleast.Node
4 | import com.xinto.simpleast.Parser
5 |
6 | typealias SimpleAstParser = Parser, Any?>
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xinto/opencord/util/Throttling.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.opencord.util
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Job
5 | import kotlinx.coroutines.delay
6 | import kotlinx.coroutines.launch
7 |
8 | /**
9 | * Constructs a function that executes [destinationFunction] only once per [skipMs].
10 | */
11 | inline fun throttle(
12 | skipMs: Long,
13 | coroutineScope: CoroutineScope,
14 | crossinline destinationFunction: suspend () -> Unit
15 | ): () -> Unit {
16 | var throttleJob: Job? = null
17 | return {
18 | if (throttleJob?.isCompleted != false) {
19 | throttleJob = coroutineScope.launch {
20 | destinationFunction()
21 | delay(skipMs)
22 | }
23 | }
24 | }
25 | }
26 |
27 | inline fun throttle(
28 | skipMs: Long,
29 | coroutineScope: CoroutineScope,
30 | crossinline destinationFunction: suspend (T) -> Unit
31 | ): (T) -> Unit {
32 | var throttleJob: Job? = null
33 | return { arg1 ->
34 | if (throttleJob?.isCompleted != false) {
35 | throttleJob = coroutineScope.launch {
36 | destinationFunction(arg1)
37 | delay(skipMs)
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_reaction.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_at_sign.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_cancel.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_channel_announcements.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_edit.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_error.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_copy.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_filter.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_guild_badge_premium_tier_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_guild_badge_premium_tier_1_banner.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_guild_badge_premium_tier_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_guild_badge_premium_tier_2_banner.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_guild_badge_premium_tier_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_guild_badge_premium_tier_3_banner.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_keyboard_arrow_down.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_link.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_mark_unread.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_people.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
15 |
19 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pin.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_reply.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_status_dnd.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_status_idle.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_status_invisible.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_status_online.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_status_streaming.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_tag.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_volume_up.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_black.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_extrabold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_extrabold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_extralight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_extralight.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_light.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_semibold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/inter_thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/font/inter_thin.ttf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/keep.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4E5D94
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application) apply false
3 | alias(libs.plugins.android.library) apply false
4 | alias(libs.plugins.kotlin.android) apply false
5 | alias(libs.plugins.kotlin.serialization) apply false
6 | alias(libs.plugins.kotlin.parcelize) apply false
7 | alias(libs.plugins.ksp) apply false
8 | }
9 |
10 | allprojects {
11 | repositories {
12 | google()
13 | mavenCentral()
14 | maven("https://jitpack.io")
15 | }
16 | }
17 |
18 | task("clean") {
19 | delete(rootProject.buildDir)
20 | delete(rootDir.resolve("buildSrc/build"))
21 | }
22 |
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | project_id_env: CROWDIN_PROJECT_ID
2 | api_token_env: CROWDIN_PERSONAL_TOKEN
3 |
4 | preserve_hierarchy: true
5 | files:
6 | - source: app/src/main/res/values/strings.xml
7 | translation: app/src/main/res/values-%android_code%/strings.xml
8 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Specifies the JVM arguments used for the daemon process.
2 | # The setting is particularly useful for tweaking memory settings.
3 | org.gradle.jvmargs=-Xmx3072m -Dfile.encoding=UTF-8
4 |
5 | # When configured, Gradle will run in incubating parallel mode.
6 | # This option should only be used with decoupled projects. More details, visit
7 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
8 | org.gradle.parallel=true
9 |
10 | # AndroidX package structure to make it clearer which packages are bundled with the
11 | # Android operating system, and which are packaged with your app"s APK
12 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
13 | android.useAndroidX=true
14 |
15 | # Automatically convert third-party libraries to use AndroidX
16 | android.enableJetifier=false
17 |
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 |
21 | # Enable R8 full mode.
22 | android.enableR8.fullMode=true
23 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Apr 11 10:48:47 GET 2023
2 | networkTimeout=10000
3 | distributionBase=GRADLE_USER_HOME
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
5 | distributionPath=wrapper/dists
6 | zipStorePath=wrapper/dists
7 | zipStoreBase=GRADLE_USER_HOME
8 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 |
9 | rootProject.name = "OpenCord"
10 |
11 | include(":app")
12 | include(":simpleast-compose")
--------------------------------------------------------------------------------
/simpleast-compose/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/simpleast-compose/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | plugins {
4 | alias(libs.plugins.android.library)
5 | alias(libs.plugins.kotlin.android)
6 | }
7 |
8 | android {
9 | namespace = "com.xinto.simpleast"
10 | compileSdk = 32
11 |
12 | defaultConfig {
13 | minSdk = 21
14 |
15 | consumerProguardFiles("consumer-rules.pro")
16 | }
17 |
18 | buildTypes {
19 | release {
20 | isMinifyEnabled = false
21 | proguardFiles(
22 | getDefaultProguardFile("proguard-android-optimize.txt"),
23 | "proguard-rules.pro",
24 | )
25 | }
26 | }
27 |
28 | buildFeatures {
29 | buildConfig = false
30 | resValues = false
31 | }
32 |
33 | compileOptions {
34 | sourceCompatibility = JavaVersion.VERSION_11
35 | targetCompatibility = JavaVersion.VERSION_11
36 | }
37 |
38 | kotlinOptions {
39 | jvmTarget = "11"
40 | freeCompilerArgs += listOf(
41 | "-Xcontext-receivers"
42 | )
43 | }
44 | }
45 |
46 | kotlin {
47 | explicitApi()
48 | }
49 |
50 | dependencies {
51 | implementation(libs.androidx.compose.foundation)
52 | }
53 |
--------------------------------------------------------------------------------
/simpleast-compose/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateriiApps/OpenCord/b06fcaf6aec056030cda4930cd671f38835f86e3/simpleast-compose/consumer-rules.pro
--------------------------------------------------------------------------------
/simpleast-compose/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/simpleast-compose/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/simpleast-compose/src/main/java/com/xinto/simpleast/DSL.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.simpleast
2 |
3 | import java.util.regex.Matcher
4 | import java.util.regex.Pattern
5 |
6 | public inline fun , S> createRule(
7 | pattern: Pattern,
8 | crossinline block: (Matcher, Parser, state: S) -> ParseSpec
9 | ): Rule {
10 | return object : Rule(pattern) {
11 | override fun parse(
12 | matcher: Matcher,
13 | parser: Parser,
14 | state: S
15 | ): ParseSpec {
16 | return block(matcher, parser, state)
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/simpleast-compose/src/main/java/com/xinto/simpleast/Node.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.simpleast
2 |
3 | import androidx.compose.ui.text.AnnotatedString
4 |
5 | public open class Node(
6 | private var children: MutableCollection>? = null,
7 | ) {
8 | public val nodeChildren: Collection>?
9 | get() = children
10 |
11 | public val hasChildren: Boolean
12 | get() = children?.isNotEmpty() == true
13 |
14 | public fun addChild(node: Node) {
15 | if (children == null) {
16 | children = ArrayList()
17 | }
18 |
19 | children!!.add(node)
20 | }
21 |
22 | //@formatter:off
23 | context(AnnotatedString.Builder)
24 | public open fun render(renderContext: RC): Unit = Unit
25 | //@formatter:on
26 |
27 | public open class Parent(
28 | vararg children: Node?,
29 | ) : Node(children.mapNotNull { it }.toMutableList()) {
30 | //@formatter:off
31 | context(AnnotatedString.Builder)
32 | override fun render(renderContext: RC) { //@formatter:on
33 | nodeChildren?.forEach {
34 | it.render(renderContext)
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/simpleast-compose/src/main/java/com/xinto/simpleast/ParseException.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.simpleast
2 |
3 | public class ParseException(
4 | message: String,
5 | source: CharSequence?,
6 | cause: Throwable? = null,
7 | ) : RuntimeException("Error while parsing: $message \n Source: $source", cause)
8 |
--------------------------------------------------------------------------------
/simpleast-compose/src/main/java/com/xinto/simpleast/ParseSpec.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.simpleast
2 |
3 | public class ParseSpec private constructor(
4 | public val root: Node,
5 | public val isTerminal: Boolean,
6 | public val state: S,
7 | public var startIndex: Int,
8 | public var endIndex: Int,
9 | ) {
10 | public fun applyOffset(offset: Int) {
11 | startIndex += offset
12 | endIndex += offset
13 | }
14 |
15 | public companion object {
16 | public fun createNonTerminal(
17 | node: Node,
18 | state: S,
19 | startIndex: Int,
20 | endIndex: Int,
21 | ): ParseSpec {
22 | return ParseSpec(
23 | root = node,
24 | isTerminal = false,
25 | state = state,
26 | startIndex = startIndex,
27 | endIndex = endIndex,
28 | )
29 | }
30 |
31 | public fun createTerminal(
32 | node: Node,
33 | state: S,
34 | ): ParseSpec {
35 | return ParseSpec(
36 | root = node,
37 | isTerminal = true,
38 | state = state,
39 | startIndex = 0,
40 | endIndex = 0,
41 | )
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/simpleast-compose/src/main/java/com/xinto/simpleast/Renderer.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.simpleast
2 |
3 | import androidx.compose.ui.text.AnnotatedString
4 |
5 | public fun Parser, S>.render(
6 | source: String,
7 | initialState: S,
8 | renderContext: RC,
9 | ): AnnotatedString.Builder {
10 | return render(
11 | builder = AnnotatedString.Builder(),
12 | nodes = parse(source, initialState),
13 | renderContext = renderContext,
14 | )
15 | }
16 |
17 | public fun render(
18 | builder: AnnotatedString.Builder = AnnotatedString.Builder(),
19 | nodes: Collection>,
20 | renderContext: RC,
21 | ): AnnotatedString.Builder {
22 | return builder.apply {
23 | for (node in nodes) {
24 | node.render(renderContext)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/simpleast-compose/src/main/java/com/xinto/simpleast/Rule.kt:
--------------------------------------------------------------------------------
1 | package com.xinto.simpleast
2 |
3 | import java.util.regex.Matcher
4 | import java.util.regex.Pattern
5 |
6 | public abstract class Rule, S>(
7 | private val matcher: Matcher
8 | ) {
9 | public constructor(pattern: Pattern) : this(pattern.matcher(""))
10 |
11 | public open fun match(
12 | inspectionSource: CharSequence,
13 | lastCapture: String?,
14 | state: S,
15 | ): Matcher? {
16 | matcher.reset(inspectionSource)
17 | return if (matcher.find()) matcher else null
18 | }
19 |
20 | public abstract fun parse(
21 | matcher: Matcher,
22 | parser: Parser, state: S,
23 | ): ParseSpec
24 |
25 | public abstract class BlockRule, S>(
26 | pattern: Pattern,
27 | ) : Rule(pattern) {
28 |
29 | override fun match(
30 | inspectionSource: CharSequence,
31 | lastCapture: String?,
32 | state: S,
33 | ): Matcher? {
34 | if (lastCapture?.endsWith('\n') != false) {
35 | return super.match(inspectionSource, lastCapture, state)
36 | }
37 | return null
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------