├── app ├── .gitignore ├── keystore │ ├── praxis-debug.jks │ └── praxis-release.jks ├── src │ ├── main │ │ ├── ic_launcher-playstore.png │ │ ├── res │ │ │ ├── font │ │ │ │ ├── unisans_bold.otf │ │ │ │ ├── unisans_book.otf │ │ │ │ ├── unisans_thin.otf │ │ │ │ ├── unisans_heavy.otf │ │ │ │ ├── unisans_italic.otf │ │ │ │ ├── unisans_light.otf │ │ │ │ ├── unisans_regular.otf │ │ │ │ ├── unisans_semibold.otf │ │ │ │ ├── unisans_bold_italic.otf │ │ │ │ ├── unisans_book_italic.otf │ │ │ │ ├── unisans_thin_italic.otf │ │ │ │ ├── unisans_heavy_italic.otf │ │ │ │ ├── unisans_light_italic.otf │ │ │ │ ├── unisans_regular_italic.ttf │ │ │ │ └── unisans_semibold_italic.otf │ │ │ ├── drawable-hdpi │ │ │ │ ├── recentlogo.png │ │ │ │ ├── welcomelogo.webp │ │ │ │ ├── ic_hide_password.png │ │ │ │ ├── ic_show_password.png │ │ │ │ ├── background_image_dark.png │ │ │ │ ├── background_image_light.png │ │ │ │ └── discord_welcome_header_light.png │ │ │ ├── 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 │ │ │ ├── drawable-mdpi │ │ │ │ └── welcomelogo.webp │ │ │ ├── drawable-xhdpi │ │ │ │ └── welcomelogo.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── drawable-xxhdpi │ │ │ │ └── welcomelogo.webp │ │ │ ├── drawable-xxxhdpi │ │ │ │ └── welcomelogo.webp │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── themes.xml │ │ │ ├── drawable │ │ │ │ ├── splash_image.xml │ │ │ │ ├── splash_image_dark.xml │ │ │ │ ├── ic_add.xml │ │ │ │ ├── ic_baseline_circle_24.xml │ │ │ │ ├── ic_chat_bubble.xml │ │ │ │ ├── ic_baseline_info_24.xml │ │ │ │ ├── ic_baseline_laptop_24.xml │ │ │ │ ├── ic_baseline_image_24.xml │ │ │ │ ├── ic_invite.xml │ │ │ │ ├── ic_baseline_security_24.xml │ │ │ │ ├── ic_baseline_edit_24.xml │ │ │ │ ├── ic_baseline_subscriptions_24.xml │ │ │ │ ├── ic_baseline_vpn_key_24.xml │ │ │ │ ├── ic_notifications.xml │ │ │ │ ├── ic_baseline_accessibility_new_24.xml │ │ │ │ ├── ic_refresh.xml │ │ │ │ ├── ic_baseline_mic_24.xml │ │ │ │ ├── ic_send_rounded.xml │ │ │ │ ├── ic_baseline_person_add_alt_1_24.xml │ │ │ │ ├── ic_outline_cancel.xml │ │ │ │ ├── ic_boost.xml │ │ │ │ ├── ic_baseline_notification_important_24.xml │ │ │ │ ├── ic_baseline_account_circle_24.xml │ │ │ │ ├── ic_baseline_contact_support_24.xml │ │ │ │ ├── ic_baseline_account_box_24.xml │ │ │ │ ├── ic_baseline_card_giftcard_24.xml │ │ │ │ ├── ic_baseline_color_lens_24.xml │ │ │ │ ├── ic_nitro.xml │ │ │ │ ├── ic_hashtag_solid.xml │ │ │ │ ├── ic_nitro_verified.xml │ │ │ │ ├── ic_baseline_language_24.xml │ │ │ │ ├── ic_baseline_qr_code_24.xml │ │ │ │ ├── ic_baseline_settings_applications_24.xml │ │ │ │ ├── dark_app_logo.xml │ │ │ │ ├── light_app_logo.xml │ │ │ │ ├── ic_discord_icon.xml │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ ├── ic_emoji_2.xml │ │ │ │ ├── ic_emoji_3.xml │ │ │ │ ├── ic_emoji_5.xml │ │ │ │ ├── ic_emoji_4.xml │ │ │ │ └── ic_emoji_1.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ └── values-night │ │ │ │ └── themes.xml │ │ ├── java │ │ │ └── dev │ │ │ │ └── baseio │ │ │ │ └── discordjetpackcompose │ │ │ │ ├── ui │ │ │ │ ├── utils │ │ │ │ │ └── Typealias.kt │ │ │ │ ├── routes │ │ │ │ │ ├── dashboard │ │ │ │ │ │ ├── notifications │ │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ │ ├── FrequencyType.kt │ │ │ │ │ │ │ │ ├── NotificationSettingsType.kt │ │ │ │ │ │ │ │ └── OverrideItem.kt │ │ │ │ │ │ │ └── components │ │ │ │ │ │ │ │ ├── SectionItem.kt │ │ │ │ │ │ │ │ ├── SubtitleAppBar.kt │ │ │ │ │ │ │ │ ├── MentionsSection.kt │ │ │ │ │ │ │ │ └── NotificationFrequencySection.kt │ │ │ │ │ │ ├── userSettings │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ │ │ └── SettingsEntity.kt │ │ │ │ │ │ │ │ ├── SettingsTitle.kt │ │ │ │ │ │ │ │ └── SettingsAppBar.kt │ │ │ │ │ │ │ ├── UserSettingsListItem.kt │ │ │ │ │ │ │ └── UserSettingsList.kt │ │ │ │ │ │ ├── serverinfo │ │ │ │ │ │ │ └── components │ │ │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ │ ├── ServerQuickAction.kt │ │ │ │ │ │ │ │ └── ServerInfoAction.kt │ │ │ │ │ │ │ │ └── ServerQuickActions.kt │ │ │ │ │ │ ├── createServer │ │ │ │ │ │ │ └── ServerTemplates.kt │ │ │ │ │ │ ├── main │ │ │ │ │ │ │ ├── dasboard │ │ │ │ │ │ │ │ └── DashboardUtil.kt │ │ │ │ │ │ │ └── chatscreen │ │ │ │ │ │ │ │ └── ChatScreenContent.kt │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ └── OnlineIndicator.kt │ │ │ │ │ │ ├── search │ │ │ │ │ │ │ └── components │ │ │ │ │ │ │ │ └── SearchSheetServerSlider.kt │ │ │ │ │ │ ├── serverchannels │ │ │ │ │ │ │ └── ServerChannelList.kt │ │ │ │ │ │ └── DashboardRoute.kt │ │ │ │ │ └── onboarding │ │ │ │ │ │ ├── OnboardingRoute.kt │ │ │ │ │ │ ├── commonui │ │ │ │ │ │ ├── CenteredTitleSubtitle.kt │ │ │ │ │ │ └── OnboardingScreensButton.kt │ │ │ │ │ │ └── screens │ │ │ │ │ │ └── register │ │ │ │ │ │ └── RegistrationTypeSelector.kt │ │ │ │ ├── theme │ │ │ │ │ ├── Shape.kt │ │ │ │ │ └── Surface.kt │ │ │ │ └── components │ │ │ │ │ ├── DiscordAppBar.kt │ │ │ │ │ └── DiscordScaffold.kt │ │ │ │ ├── DiscordApp.kt │ │ │ │ ├── di │ │ │ │ └── NavigationModule.kt │ │ │ │ ├── viewmodels │ │ │ │ ├── LoginScreenViewModel.kt │ │ │ │ ├── RegistrationViewModel.kt │ │ │ │ └── FriendsViewModel.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── dev │ │ └── baseio │ │ └── discordjetpackcompose │ │ └── ui │ │ └── utils │ │ └── ExtensionUtils.kt └── proguard-rules.pro ├── data ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── dev │ │ │ └── baseio │ │ │ └── discordjetpackcompose │ │ │ ├── mappers │ │ │ ├── EntityMapper.kt │ │ │ ├── CountryMapper.kt │ │ │ └── DiscordMessageMapper.kt │ │ │ ├── di │ │ │ ├── dispatcher │ │ │ │ ├── CoroutineDispatcherProvider.kt │ │ │ │ └── RealCoroutineDispatcherProvider.kt │ │ │ ├── DispatcherModule.kt │ │ │ ├── DataMappersModule.kt │ │ │ ├── DataModule.kt │ │ │ └── RepositoryModule.kt │ │ │ ├── local │ │ │ ├── database │ │ │ │ └── DiscordDatabase.kt │ │ │ ├── model │ │ │ │ └── DBDiscordMessage.kt │ │ │ └── dao │ │ │ │ └── DiscordMessageDao.kt │ │ │ ├── models │ │ │ └── Country.kt │ │ │ ├── utils │ │ │ ├── ExtensionUtils.kt │ │ │ ├── NetworkUtils.kt │ │ │ └── SampleData.kt │ │ │ └── repositories │ │ │ ├── CountryRepoImpl.kt │ │ │ ├── FriendsRepoImpl.kt │ │ │ ├── MessagesRepoImpl.kt │ │ │ └── ServerRepoImpl.kt │ ├── test │ │ └── java │ │ │ └── dev │ │ │ └── baseio │ │ │ └── slackclone │ │ │ └── data │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── data │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── domain ├── .gitignore ├── src │ ├── main │ │ └── java │ │ │ └── dev │ │ │ └── baseio │ │ │ └── discordjetpackcompose │ │ │ ├── utils │ │ │ └── Constants.kt │ │ │ ├── entities │ │ │ ├── NetworkState.kt │ │ │ ├── message │ │ │ │ ├── DiscordUrlMetaEntity.kt │ │ │ │ └── DiscordMessageEntity.kt │ │ │ ├── CountryEntity.kt │ │ │ ├── UIState.kt │ │ │ ├── search │ │ │ │ └── SearchSheetListItemEntity.kt │ │ │ ├── ChatUserEntity.kt │ │ │ └── server │ │ │ │ ├── ChannelEntity.kt │ │ │ │ └── ServerEntity.kt │ │ │ ├── repositories │ │ │ ├── CountryRepo.kt │ │ │ ├── FriendsRepo.kt │ │ │ ├── ServerRepo.kt │ │ │ └── MessagesRepo.kt │ │ │ ├── usecases │ │ │ ├── FetchFriendsUseCase.kt │ │ │ ├── server │ │ │ │ └── GetServerListUseCase.kt │ │ │ ├── FetchCountriesUseCase.kt │ │ │ ├── GetServerUseCase.kt │ │ │ ├── FetchFriendSuggestionsUseCase.kt │ │ │ ├── search │ │ │ │ └── GetSearchSheetItemListUseCase.kt │ │ │ ├── chat │ │ │ │ ├── FetchUrlMetadataUseCase.kt │ │ │ │ ├── SendMessageUseCase.kt │ │ │ │ └── FetchMessagesUseCase.kt │ │ │ └── BaseUseCase.kt │ │ │ └── di │ │ │ └── UseCaseModule.kt │ └── test │ │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── domain │ │ └── ExampleUnitTest.kt └── build.gradle.kts ├── benchmark ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── dev │ │ └── baseio │ │ └── discordjetpackcompose │ │ ├── baselineprof │ │ └── BaselineProfileGenerator.kt │ │ └── startup │ │ └── StartupBenchmark.kt └── build.gradle.kts ├── navigator ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── java │ │ │ └── dev │ │ │ │ └── baseio │ │ │ │ └── discordjetpackcompose │ │ │ │ └── navigator │ │ │ │ ├── NavigationKeys.kt │ │ │ │ ├── NavigationCommand.kt │ │ │ │ ├── Screens.kt │ │ │ │ ├── ComposeNavigator.kt │ │ │ │ └── Navigator.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── dev │ │ │ └── baseio │ │ │ └── slackclone │ │ │ └── navigator │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── navigator │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle.kts ├── art ├── discord_flow.gif ├── discord_chat_dark.png ├── discord_chat_light.png ├── discord_friends_dark.png ├── discord_invite_dark.png ├── discord_invite_light.png ├── discord_login_dark.png ├── discord_login_light.png ├── discord_welcome_dark.png ├── discord_chat_list_dark.png ├── discord_friends_light.png ├── discord_register_dark.png ├── discord_register_light.png ├── discord_welcome_light.png ├── discord_chat_list_light.png ├── discord_notification_dark.png ├── discord_create_server_dark.png ├── discord_create_server_light.png ├── discord_notification_light.png ├── discord_channel_members_dark.png ├── discord_channel_members_light.png ├── discord_password_manager_dialog_dark.png └── discord_password_manager_dialog_light.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .github ├── codeowners ├── issue_template │ ├── feature_request.md │ ├── bug_report.md │ └── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── settings.gradle.kts ├── .gitignore ├── team-props ├── git-hooks │ └── pre-commit.sh └── git-hooks.gradle.kts ├── gradle.properties └── gradlew.bat /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /navigator/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /navigator/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /art/discord_flow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_flow.gif -------------------------------------------------------------------------------- /art/discord_chat_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_chat_dark.png -------------------------------------------------------------------------------- /art/discord_chat_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_chat_light.png -------------------------------------------------------------------------------- /art/discord_friends_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_friends_dark.png -------------------------------------------------------------------------------- /art/discord_invite_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_invite_dark.png -------------------------------------------------------------------------------- /art/discord_invite_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_invite_light.png -------------------------------------------------------------------------------- /art/discord_login_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_login_dark.png -------------------------------------------------------------------------------- /art/discord_login_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_login_light.png -------------------------------------------------------------------------------- /art/discord_welcome_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_welcome_dark.png -------------------------------------------------------------------------------- /app/keystore/praxis-debug.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/keystore/praxis-debug.jks -------------------------------------------------------------------------------- /art/discord_chat_list_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_chat_list_dark.png -------------------------------------------------------------------------------- /art/discord_friends_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_friends_light.png -------------------------------------------------------------------------------- /art/discord_register_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_register_dark.png -------------------------------------------------------------------------------- /art/discord_register_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_register_light.png -------------------------------------------------------------------------------- /art/discord_welcome_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_welcome_light.png -------------------------------------------------------------------------------- /app/keystore/praxis-release.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/keystore/praxis-release.jks -------------------------------------------------------------------------------- /art/discord_chat_list_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_chat_list_light.png -------------------------------------------------------------------------------- /art/discord_notification_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_notification_dark.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /art/discord_create_server_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_create_server_dark.png -------------------------------------------------------------------------------- /art/discord_create_server_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_create_server_light.png -------------------------------------------------------------------------------- /art/discord_notification_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_notification_light.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_bold.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_book.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_thin.otf -------------------------------------------------------------------------------- /art/discord_channel_members_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_channel_members_dark.png -------------------------------------------------------------------------------- /art/discord_channel_members_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_channel_members_light.png -------------------------------------------------------------------------------- /.github/codeowners: -------------------------------------------------------------------------------- 1 | # Setting default codeowners to default as reviewers 2 | * @anmol92verma @aditya-bhawsar @pushpalroy @shubhamsinghshubham777 -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_heavy.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_heavy.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_italic.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_light.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_regular.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_semibold.otf -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/recentlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-hdpi/recentlogo.png -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_bold_italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_bold_italic.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_book_italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_book_italic.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_thin_italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_thin_italic.otf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /art/discord_password_manager_dialog_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_password_manager_dialog_dark.png -------------------------------------------------------------------------------- /art/discord_password_manager_dialog_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/art/discord_password_manager_dialog_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/welcomelogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-hdpi/welcomelogo.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/welcomelogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-mdpi/welcomelogo.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/welcomelogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-xhdpi/welcomelogo.webp -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_heavy_italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_heavy_italic.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_light_italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_light_italic.otf -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_regular_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_regular_italic.ttf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/welcomelogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-xxhdpi/welcomelogo.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/welcomelogo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-xxxhdpi/welcomelogo.webp -------------------------------------------------------------------------------- /app/src/main/res/font/unisans_semibold_italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/font/unisans_semibold_italic.otf -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_hide_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-hdpi/ic_hide_password.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_show_password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-hdpi/ic_show_password.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/background_image_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-hdpi/background_image_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/background_image_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-hdpi/background_image_light.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #5664FB 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/discord_welcome_header_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/DiscordJetpackCompose/master/app/src/main/res/drawable-hdpi/discord_welcome_header_light.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | // Root module 2 | include(":app") 3 | 4 | // Other modules 5 | include(":domain") 6 | include(":data") 7 | include(":navigator") 8 | 9 | include(":benchmark") 10 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/NavigationKeys.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.navigator 2 | 3 | object NavigationKeys { 4 | 5 | val navigateChannel = "ChannelCreated" 6 | 7 | } -------------------------------------------------------------------------------- /navigator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/utils/Typealias.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.utils 2 | 3 | import dev.baseio.discordjetpackcompose.R 4 | 5 | typealias Strings = R.string 6 | typealias Drawables = R.drawable -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.utils 2 | 3 | object Constants { 4 | const val MMLogoUrl = "https://upload.wikimedia.org/wikipedia/commons/0/00/Mutual_Mobile_Logo.png" 5 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/mappers/EntityMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.mappers 2 | 3 | interface EntityMapper { 4 | fun mapToDomain(entity: Data): Domain 5 | fun mapToData(model: Domain): Data 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/notifications/models/FrequencyType.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.models 2 | 3 | enum class FrequencyType { 4 | ALL_MESSAGES, 5 | MENTIONS, 6 | NOTHING 7 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/notifications/models/NotificationSettingsType.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.models 2 | 3 | enum class NotificationSettingsType { 4 | SERVER, 5 | CATEGORY, 6 | CHANNEL 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 07 15:58:10 IST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /captures 8 | .externalNativeBuild 9 | .idea 10 | *.aab 11 | .cxx 12 | */build 13 | */.gradle 14 | /buildSrc/build 15 | build 16 | local.properties 17 | /.idea/ 18 | /buildSrc/build/ 19 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/NetworkState.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities 2 | 3 | sealed class NetworkState { 4 | class Success(val data: T): NetworkState() 5 | class Failure(val throwable: Throwable): NetworkState() 6 | } 7 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/repositories/CountryRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.repositories 2 | 3 | import dev.baseio.discordjetpackcompose.entities.CountryEntity 4 | 5 | interface CountryRepo { 6 | suspend fun fetchCountriesFromAssetFile(): List? 7 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /data/src/test/java/dev/baseio/slackclone/data/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.data 2 | 3 | /** 4 | * Example local unit test, which will execute on the development machine (host). 5 | * 6 | * See [testing documentation](http://d.android.com/tools/testing). 7 | */ 8 | class ExampleUnitTest { 9 | } -------------------------------------------------------------------------------- /benchmark/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordUrlMetaEntity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities.message 2 | 3 | data class DiscordUrlMetaEntity( 4 | var title: String? = null, 5 | var desc: String? = null, 6 | var image: String? = null, 7 | var url: String? = null 8 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_image_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/CountryEntity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities 2 | 3 | data class CountryEntity( 4 | val alpha2: String = "", 5 | val currencyCode: String? = null, 6 | val localeForICU: String? = null, 7 | val name: String = "", 8 | val phoneCountryCode: String = "" 9 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/models/SettingsEntity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.models 2 | 3 | data class SettingsEntity( 4 | val title: String, 5 | val icon: Int, 6 | val currentStatus: String? = null, 7 | val statusIcon: String? = null 8 | ) -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/UIState.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities 2 | 3 | sealed class UIState { 4 | object Empty: UIState() 5 | object Loading: UIState() 6 | class Success(val data: T): UIState() 7 | class Failure(val throwable: Throwable): UIState() 8 | } 9 | -------------------------------------------------------------------------------- /data/src/androidTest/java/dev/baseio/slackclone/data/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.data 2 | 3 | 4 | /** 5 | * Instrumented test, which will execute on an Android device. 6 | * 7 | * See [testing documentation](http://d.android.com/tools/testing). 8 | */ 9 | class ExampleInstrumentedTest { 10 | fun useAppContext() { 11 | } 12 | } -------------------------------------------------------------------------------- /team-props/git-hooks/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Running static analysis using ktlint..." 4 | 5 | # ktlintcheck 6 | # ./gradlew ktlintcheck --daemon 7 | 8 | status=$? 9 | 10 | if [ "$status" = 0 ] ; then 11 | echo "Static analysis found no problems." 12 | exit 0 13 | else 14 | echo 1>&2 "Static analysis found violations it could not fix." 15 | exit 1 16 | fi -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/FetchFriendsUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases 2 | 3 | import dev.baseio.discordjetpackcompose.repositories.FriendsRepo 4 | import javax.inject.Inject 5 | 6 | class FetchFriendsUseCase @Inject constructor(private val friendsRepo: FriendsRepo) { 7 | suspend operator fun invoke() = friendsRepo.fetchFriends() 8 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_circle_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chat_bubble.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/repositories/FriendsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.repositories 2 | 3 | import dev.baseio.discordjetpackcompose.entities.ChatUserEntity 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface FriendsRepo { 7 | suspend fun fetchFriendSuggestions(): Flow> 8 | suspend fun fetchFriends(): Flow> 9 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/server/GetServerListUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases.server 2 | 3 | import dev.baseio.discordjetpackcompose.repositories.ServerRepo 4 | import javax.inject.Inject 5 | 6 | class GetServerListUseCase @Inject constructor(private val serverRepo: ServerRepo) { 7 | suspend operator fun invoke() = serverRepo.getServerList() 8 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/FetchCountriesUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases 2 | 3 | import dev.baseio.discordjetpackcompose.repositories.CountryRepo 4 | import javax.inject.Inject 5 | 6 | class FetchCountriesUseCase @Inject constructor(private val countryRepo: CountryRepo) { 7 | suspend operator fun invoke() = countryRepo.fetchCountriesFromAssetFile() 8 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/GetServerUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases 2 | 3 | import dev.baseio.discordjetpackcompose.repositories.ServerRepo 4 | import javax.inject.Inject 5 | 6 | class GetServerUseCase @Inject constructor(private val serverRepo: ServerRepo) { 7 | suspend operator fun invoke(serverId: String) = serverRepo.getServer(serverId = serverId) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/notifications/models/OverrideItem.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.models 2 | 3 | data class OverrideItem( 4 | val title: String, 5 | val subtitle: String?, 6 | val type: OverrideType, 7 | val frequencyType: FrequencyType 8 | ) 9 | 10 | enum class OverrideType { 11 | CHANNEL, 12 | CATEGORY 13 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/di/dispatcher/CoroutineDispatcherProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.di.dispatcher 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | 5 | interface CoroutineDispatcherProvider { 6 | val main: CoroutineDispatcher 7 | val io: CoroutineDispatcher 8 | val default: CoroutineDispatcher 9 | val unconfirmed: CoroutineDispatcher 10 | } 11 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/FetchFriendSuggestionsUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases 2 | 3 | import dev.baseio.discordjetpackcompose.repositories.FriendsRepo 4 | import javax.inject.Inject 5 | 6 | class FetchFriendSuggestionsUseCase @Inject constructor(private val friendsRepo: FriendsRepo) { 7 | suspend operator fun invoke() = friendsRepo.fetchFriendSuggestions() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) 12 | -------------------------------------------------------------------------------- /domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | id("org.jetbrains.kotlin.jvm") 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | } 10 | 11 | dependencies { 12 | api(Lib.Kotlin.KT_STD) 13 | api(Lib.Async.COROUTINES) 14 | implementation("androidx.paging:paging-common-ktx:3.1.0") 15 | implementation(Lib.Di.hiltCore) 16 | } 17 | 18 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/search/GetSearchSheetItemListUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases.search 2 | 3 | import dev.baseio.discordjetpackcompose.repositories.ServerRepo 4 | import javax.inject.Inject 5 | 6 | class GetSearchSheetItemListUseCase @Inject constructor(private val serverRepo: ServerRepo) { 7 | suspend operator fun invoke() = serverRepo.getSearchSheetItemList() 8 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_info_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_laptop_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_image_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_invite.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/serverinfo/components/models/ServerQuickAction.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.serverinfo.components.models 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.ui.graphics.Color 5 | 6 | data class ServerQuickAction( 7 | @DrawableRes val icon: Int, 8 | val iconTint: Color? = null, 9 | val label: String, 10 | val onClick: () -> Unit, 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_security_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/test/java/dev/baseio/slackclone/domain/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.domain 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /navigator/src/test/java/dev/baseio/slackclone/navigator/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.navigator 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/DiscordApp.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | import timber.log.Timber 6 | import timber.log.Timber.DebugTree 7 | 8 | @HiltAndroidApp 9 | class DiscordApp : Application() { 10 | override fun onCreate() { 11 | super.onCreate() 12 | if (BuildConfig.DEBUG) { 13 | Timber.plant(DebugTree()) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_edit_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_subscriptions_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_vpn_key_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notifications.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_accessibility_new_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/serverinfo/components/models/ServerInfoAction.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.serverinfo.components.models 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.Color 5 | 6 | data class ServerInfoAction( 7 | val title: String, 8 | val titleColor: Color, 9 | val subtitle: String?, 10 | val trailingComposable: @Composable () -> Unit = {}, 11 | val onClick: () -> Unit, 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_mic_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_rounded.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_person_add_alt_1_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_cancel.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_boost.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/message/DiscordMessageEntity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities.message 2 | 3 | data class DiscordMessageEntity( 4 | val uuid: String, 5 | val channelId: String, 6 | val message: String, 7 | val userId: String, 8 | val replyTo: String, 9 | val replyToMessage: String, 10 | val createdBy: String, 11 | val createdDate: Long, 12 | val modifiedDate: Long, 13 | val metaTitle: String, 14 | val metaDesc: String, 15 | val metaImageUrl: String, 16 | val metaUrl: String 17 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_notification_important_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/local/database/DiscordDatabase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.local.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import dev.baseio.discordjetpackcompose.local.dao.DiscordMessageDao 6 | import dev.baseio.discordjetpackcompose.local.model.DBDiscordMessage 7 | 8 | @Database( 9 | entities = [DBDiscordMessage::class], 10 | version = 1, 11 | exportSchema = false 12 | ) 13 | abstract class DiscordDatabase : RoomDatabase() { 14 | abstract fun discordMessageDao(): DiscordMessageDao 15 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_account_circle_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_contact_support_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/repositories/ServerRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.repositories 2 | 3 | import dev.baseio.discordjetpackcompose.entities.NetworkState 4 | import dev.baseio.discordjetpackcompose.entities.search.SearchSheetListItemEntity 5 | import dev.baseio.discordjetpackcompose.entities.server.ServerEntity 6 | 7 | interface ServerRepo { 8 | suspend fun getServer(serverId: String): NetworkState 9 | suspend fun getServerList(): NetworkState> 10 | suspend fun getSearchSheetItemList(): NetworkState> 11 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_account_box_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/models/Country.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.models 2 | 3 | 4 | import androidx.annotation.Keep 5 | import com.google.gson.annotations.SerializedName 6 | 7 | @Keep 8 | data class Country( 9 | @SerializedName("alpha2") 10 | val alpha2: String = "", 11 | @SerializedName("currencyCode") 12 | val currencyCode: String? = null, 13 | @SerializedName("localeForICU") 14 | val localeForICU: String? = null, 15 | @SerializedName("name") 16 | val name: String = "", 17 | @SerializedName("phoneCountryCode") 18 | val phoneCountryCode: String = "" 19 | ) -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/repositories/MessagesRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.repositories 2 | 3 | import androidx.paging.PagingData 4 | import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity 5 | import dev.baseio.discordjetpackcompose.entities.message.DiscordUrlMetaEntity 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface MessagesRepo { 9 | fun fetchMessages(params: String?): Flow> 10 | suspend fun sendMessage(params: DiscordMessageEntity): DiscordMessageEntity 11 | suspend fun fetchUrlMetadata(url: String?): DiscordUrlMetaEntity? 12 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/di/dispatcher/RealCoroutineDispatcherProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.di.dispatcher 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | open class RealCoroutineDispatcherProvider : CoroutineDispatcherProvider { 7 | override val main: CoroutineDispatcher by lazy { Dispatchers.Main } 8 | override val io: CoroutineDispatcher by lazy { Dispatchers.IO } 9 | override val default: CoroutineDispatcher by lazy { Dispatchers.Default } 10 | override val unconfirmed: CoroutineDispatcher by lazy { Dispatchers.Unconfined } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/di/NavigationModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.di 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator 8 | import dev.baseio.discordjetpackcompose.navigator.DiscordComposeNavigator 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | abstract class NavigationModule { 14 | 15 | @Binds 16 | @Singleton 17 | abstract fun provideComposeNavigator(praxisComposeNavigator: DiscordComposeNavigator): ComposeNavigator 18 | } 19 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/chat/FetchUrlMetadataUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases.chat 2 | 3 | import dev.baseio.discordjetpackcompose.entities.message.DiscordUrlMetaEntity 4 | import dev.baseio.discordjetpackcompose.repositories.MessagesRepo 5 | import dev.baseio.discordjetpackcompose.usecases.BaseUseCase 6 | import javax.inject.Inject 7 | 8 | class FetchUrlMetadataUseCase @Inject constructor(private val messagesRepo: MessagesRepo) : 9 | BaseUseCase { 10 | override suspend fun perform(params: String): DiscordUrlMetaEntity? { 11 | return messagesRepo.fetchUrlMetadata(params) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/mappers/CountryMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.mappers 2 | 3 | import dev.baseio.discordjetpackcompose.entities.CountryEntity 4 | import dev.baseio.discordjetpackcompose.models.Country 5 | 6 | fun Country.toDomainEntity() = CountryEntity( 7 | alpha2 = alpha2, 8 | currencyCode = currencyCode, 9 | localeForICU = localeForICU, 10 | name = name, 11 | phoneCountryCode = phoneCountryCode 12 | ) 13 | 14 | fun CountryEntity.toDataModel() = Country( 15 | alpha2 = alpha2, 16 | currencyCode = currencyCode, 17 | localeForICU = localeForICU, 18 | name = name, 19 | phoneCountryCode = phoneCountryCode 20 | ) -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/chat/SendMessageUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases.chat 2 | 3 | import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity 4 | import dev.baseio.discordjetpackcompose.repositories.MessagesRepo 5 | import dev.baseio.discordjetpackcompose.usecases.BaseUseCase 6 | import javax.inject.Inject 7 | 8 | class SendMessageUseCase @Inject constructor(private val messagesRepo: MessagesRepo) : 9 | BaseUseCase { 10 | override suspend fun perform(params: DiscordMessageEntity): DiscordMessageEntity { 11 | return messagesRepo.sendMessage(params) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/issue_template/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/utils/ExtensionUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.utils 2 | 3 | import android.content.Context 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | import timber.log.Timber 7 | 8 | inline fun Context.readAssetFile(filePath: String): T? { 9 | return try { 10 | val jsonDataFromFile = this.assets.open(filePath).bufferedReader().use { it.readText() } 11 | val returnType = object : TypeToken() {}.type 12 | Gson().fromJson(jsonDataFromFile, returnType) 13 | } catch (ex: Exception) { 14 | Timber.e(ex, "Some error occurred. Reason:") 15 | null 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Minimal Android CI Workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | apk: 10 | name: Generate APK 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v1 15 | - name: Setup JDK 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | - name: Build APK 20 | run: bash ./gradlew assembleDebug --stacktrace 21 | - name: Upload APK 22 | uses: actions/upload-artifact@v2 23 | with: 24 | name: apk 25 | path: app/build/outputs/apk/debug/app-debug.apk 26 | retention-days: 5 27 | -------------------------------------------------------------------------------- /app/src/androidTest/java/dev/baseio/discordjetpackcompose/ui/utils/ExtensionUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.utils 2 | 3 | import android.content.Context 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | import timber.log.Timber 7 | 8 | inline fun Context.readAssetFile(filePath: String): T? { 9 | return try { 10 | val jsonDataFromFile = this.assets.open(filePath).bufferedReader().use { it.readText() } 11 | val returnType = object : TypeToken() {}.type 12 | Gson().fromJson(jsonDataFromFile, returnType) 13 | } catch (ex: Exception) { 14 | Timber.e(ex, "Some error occurred. Reason:") 15 | null 16 | } 17 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/di/DispatcherModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import dev.baseio.discordjetpackcompose.di.dispatcher.CoroutineDispatcherProvider 8 | import dev.baseio.discordjetpackcompose.di.dispatcher.RealCoroutineDispatcherProvider 9 | import javax.inject.Singleton 10 | 11 | @InstallIn(SingletonComponent::class) 12 | @Module 13 | class DispatcherModule { 14 | @Provides 15 | @Singleton 16 | fun providesCoroutineDispatcher(): CoroutineDispatcherProvider { 17 | return RealCoroutineDispatcherProvider() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/search/SearchSheetListItemEntity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities.search 2 | 3 | enum class SearchFilter(val title: String, val sign: Char) { 4 | Users(title = "Users", sign = '@'), 5 | TextChannels(title = "Text Channels", sign = '#'), 6 | VoiceChannels(title = "Voice Channels", sign = '!'), 7 | Servers(title = "Servers", sign = '*'), 8 | } 9 | 10 | data class SearchSheetListItemEntity( 11 | val id: String, 12 | val itemType: SearchFilter, 13 | val iconUri: Any?, 14 | val title: String, 15 | val subtitle: String? = null, 16 | val serverName: String? = null, 17 | val unreadCount: Int? = null, 18 | ) 19 | -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/NavigationCommand.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.navigator 2 | 3 | import androidx.navigation.NavOptions 4 | 5 | sealed class NavigationCommand { 6 | object NavigateUp : NavigationCommand() 7 | } 8 | 9 | sealed class ComposeNavigationCommand : NavigationCommand() { 10 | data class NavigateToRoute(val route: String, val options: NavOptions? = null) : 11 | ComposeNavigationCommand() 12 | 13 | data class NavigateUpWithResult( 14 | val key: String, 15 | val result: T, 16 | val route: String? = null 17 | ) : ComposeNavigationCommand() 18 | 19 | data class PopUpToRoute(val route: String, val inclusive: Boolean) : ComposeNavigationCommand() 20 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/chat/FetchMessagesUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases.chat 2 | 3 | import androidx.paging.PagingData 4 | import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity 5 | import dev.baseio.discordjetpackcompose.repositories.MessagesRepo 6 | import dev.baseio.discordjetpackcompose.usecases.BaseUseCase 7 | import kotlinx.coroutines.flow.Flow 8 | import javax.inject.Inject 9 | 10 | class FetchMessagesUseCase @Inject constructor(private val messagesRepo: MessagesRepo) : 11 | BaseUseCase, String> { 12 | override fun performStreaming(params: String?): Flow> { 13 | return messagesRepo.fetchMessages(params) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/repositories/CountryRepoImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.repositories 2 | 3 | import android.content.Context 4 | import dev.baseio.discordjetpackcompose.entities.CountryEntity 5 | import dev.baseio.discordjetpackcompose.mappers.toDomainEntity 6 | import dev.baseio.discordjetpackcompose.models.Country 7 | import dev.baseio.discordjetpackcompose.utils.readAssetFile 8 | 9 | class CountryRepoImpl(private val context: Context) : CountryRepo { 10 | companion object { 11 | const val CountriesAssetFilePath = "countries.json" 12 | } 13 | 14 | override suspend fun fetchCountriesFromAssetFile(): List? = 15 | context.readAssetFile>(CountriesAssetFilePath)?.map { it.toDomainEntity() } 16 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/ChatUserEntity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities 2 | 3 | import dev.baseio.discordjetpackcompose.utils.Constants 4 | 5 | /** 6 | * Represents a User in Discord 7 | * @param username Uniquely identifies this user 8 | * @param name Name of the user ([username] is used if no name is given) 9 | * @param currentStatus Current status of the user 10 | * @param isOnline Whether the user is online or offline 11 | * @param profileImage Profile image of the user (optional) 12 | * */ 13 | data class ChatUserEntity( 14 | val username: String, 15 | val name: String = username, 16 | val currentStatus: String? = null, 17 | val isOnline: Boolean, 18 | val profileImage: String = Constants.MMLogoUrl, 19 | ) 20 | -------------------------------------------------------------------------------- /app/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 -------------------------------------------------------------------------------- /data/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.kts. 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 -------------------------------------------------------------------------------- /navigator/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 -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_card_giftcard_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_color_lens_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/usecases/BaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.usecases 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface BaseUseCase { 6 | 7 | /** 8 | * Perform an operation with no input parameters. 9 | * Will throw an exception by default, if not implemented but invoked. 10 | * 11 | * @return 12 | */ 13 | suspend fun perform(): Result = throw NotImplementedError() 14 | 15 | /** 16 | * Perform an operation. 17 | * Will throw an exception by default, if not implemented but invoked. 18 | * 19 | * @param params 20 | * @return 21 | */ 22 | suspend fun perform(params: ExecutableParam): Result? = throw NotImplementedError() 23 | 24 | fun performStreaming(params: ExecutableParam?): Flow = throw NotImplementedError() 25 | } -------------------------------------------------------------------------------- /navigator/src/androidTest/java/dev/baseio/slackclone/navigator/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.navigator 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("dev.baseio.discordjetpackcompose.navigator.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/di/DataMappersModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.di 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity 8 | import dev.baseio.discordjetpackcompose.local.model.DBDiscordMessage 9 | import dev.baseio.discordjetpackcompose.mappers.DiscordMessageMapper 10 | import dev.baseio.discordjetpackcompose.mappers.EntityMapper 11 | import javax.inject.Singleton 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | abstract class DataMappersModule { 16 | 17 | @Binds 18 | @Singleton 19 | abstract fun bindDiscordMessageMapper( 20 | discordMessageMapper: DiscordMessageMapper 21 | ): EntityMapper 22 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/viewmodels/LoginScreenViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.viewmodels 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.hilt.android.lifecycle.HiltViewModel 5 | import kotlinx.coroutines.flow.MutableStateFlow 6 | import kotlinx.coroutines.flow.StateFlow 7 | import kotlinx.coroutines.flow.asStateFlow 8 | import javax.inject.Inject 9 | 10 | @HiltViewModel 11 | class LoginScreenViewModel @Inject constructor() : ViewModel() { 12 | private val _showDialog = MutableStateFlow(false) 13 | val showDialog: StateFlow = _showDialog.asStateFlow() 14 | 15 | fun onOpenDialogClicked() { 16 | _showDialog.value = true 17 | } 18 | 19 | fun onDialogConfirm() { 20 | _showDialog.value = false 21 | } 22 | 23 | fun onDialogDismiss() { 24 | _showDialog.value = false 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/utils/NetworkUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.utils 2 | 3 | import dev.baseio.discordjetpackcompose.data.BuildConfig 4 | import dev.baseio.discordjetpackcompose.di.dispatcher.CoroutineDispatcherProvider 5 | import dev.baseio.discordjetpackcompose.entities.NetworkState 6 | import kotlinx.coroutines.withContext 7 | 8 | suspend fun safeApiCall( 9 | coroutineDispatcherProvider: CoroutineDispatcherProvider, 10 | debugResponse: T, 11 | releaseResponse: suspend () -> T 12 | ): NetworkState { 13 | return withContext(coroutineDispatcherProvider.io) { 14 | try { 15 | NetworkState.Success( 16 | data = if (BuildConfig.DEBUG) debugResponse 17 | else debugResponse // TODO: Use releaseResponse here 18 | ) 19 | } catch (t: Throwable) { 20 | NetworkState.Failure(throwable = t) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/server/ChannelEntity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities.server 2 | 3 | /** 4 | * A ChannelEntity depicts a single channel inside a server. 5 | * @param id Uniquely identifies the channel 6 | * @param name Channel name that would be publicly displayed 7 | * @param type A channel can be of multiple types as defined inside [ChannelType] 8 | * @param category Defines which category does this channel belong to (there can be multiple channels inside a single category) 9 | * @param unreadCount How many unread messages are there inside this channel 10 | * */ 11 | data class ChannelEntity( 12 | val id: String, 13 | val name: String, 14 | val type: ChannelType, 15 | val category: String? = null, 16 | val unreadCount: Int = 0, 17 | val isUnread: Boolean = false, 18 | ) 19 | 20 | enum class ChannelType { 21 | PUBLIC, PRIVATE, BROADCAST, PODCAST, CONVERSATION 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nitro.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/issue_template/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_hashtag_solid.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/local/model/DBDiscordMessage.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.local.model 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "discordMessage") 8 | data class DBDiscordMessage( 9 | @PrimaryKey val uuid: String, 10 | @ColumnInfo(name = "channelId") val channelId: String, 11 | @ColumnInfo(name = "message") val message: String, 12 | @ColumnInfo(name = "from") val userId: String, 13 | @ColumnInfo(name = "replyTo") val replyTo: String, 14 | @ColumnInfo(name = "replyToMessage") val replyToMessage: String, 15 | @ColumnInfo(name = "createdBy") val createdBy: String, 16 | @ColumnInfo(name = "createdDate") val createdDate: Long, 17 | @ColumnInfo(name = "modifiedDate") val modifiedDate: Long, 18 | @ColumnInfo(name = "metaTitle") val metaTitle: String, 19 | @ColumnInfo(name = "metaDesc") val metaDesc: String, 20 | @ColumnInfo(name = "metaImageUrl") val metaImageUrl: String, 21 | @ColumnInfo(name = "metaUrl") val metaUrl: String 22 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_nitro_verified.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/di/DataModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import dev.baseio.discordjetpackcompose.local.dao.DiscordMessageDao 11 | import dev.baseio.discordjetpackcompose.local.database.DiscordDatabase 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object DataModule { 17 | @Provides 18 | @Singleton 19 | fun provideDatabase(@ApplicationContext context: Context): DiscordDatabase { 20 | return Room.inMemoryDatabaseBuilder( 21 | context, 22 | DiscordDatabase::class.java, 23 | ).fallbackToDestructiveMigration().allowMainThreadQueries().build() 24 | } 25 | 26 | @Provides 27 | @Singleton 28 | fun providesDiscordMessageDao(discordDatabase: DiscordDatabase): DiscordMessageDao = 29 | discordDatabase.discordMessageDao() 30 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/local/dao/DiscordMessageDao.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.local.dao 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import dev.baseio.discordjetpackcompose.local.model.DBDiscordMessage 9 | 10 | @Dao 11 | interface DiscordMessageDao { 12 | @Query("SELECT * FROM discordMessage") 13 | fun getAll(): List 14 | 15 | @Insert(onConflict = OnConflictStrategy.REPLACE) 16 | fun insertAll(messages: List) 17 | 18 | @Insert(onConflict = OnConflictStrategy.REPLACE) 19 | fun insert(message: DBDiscordMessage) 20 | 21 | // The Int type parameter tells Room to use a PositionalDataSource object. 22 | @Query("SELECT * FROM discordMessage where channelId = :params ORDER BY createdDate DESC") 23 | fun messagesByDate(params: String?): PagingSource 24 | 25 | @Query("SELECT * from discordMessage where uuid like :uuid") 26 | fun getById(uuid: String): DBDiscordMessage 27 | } -------------------------------------------------------------------------------- /team-props/git-hooks.gradle.kts: -------------------------------------------------------------------------------- 1 | fun isLinuxOrMacOs(): Boolean { 2 | val osName = System.getProperty("os.name") 3 | .toLowerCase() 4 | return osName.contains("linux") || osName.contains("mac os") || osName.contains("macos") 5 | } 6 | 7 | tasks.create("copyGitHooks") { 8 | description = "Copies the git hooks from team-props/git-hooks to the .git folder." 9 | from("$rootDir/team-props/git-hooks/") { 10 | include("**/*.sh") 11 | rename("(.*).sh", "$1") 12 | } 13 | into("$rootDir/.git/hooks") 14 | onlyIf { isLinuxOrMacOs() } 15 | } 16 | 17 | tasks.create("installGitHooks") { 18 | description = "Installs the pre-commit git hooks from team-props/git-hooks." 19 | group = "git hooks" 20 | workingDir(rootDir) 21 | commandLine("chmod") 22 | args("-R", "+x", ".git/hooks/") 23 | dependsOn("copyGitHooks") 24 | onlyIf { isLinuxOrMacOs() } 25 | doLast { 26 | logger.info("Git hook installed successfully.") 27 | } 28 | } 29 | 30 | tasks.getByName("installGitHooks") 31 | .dependsOn(getTasksByName("copyGitHooks", true)) 32 | tasks.getByPath("app:preBuild") 33 | .dependsOn(getTasksByName("installGitHooks", true)) 34 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_language_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_qr_code_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_settings_applications_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 19 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dark_app_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/light_app_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_discord_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /benchmark/src/main/java/dev/baseio/discordjetpackcompose/baselineprof/BaselineProfileGenerator.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.baselineprof 2 | 3 | import androidx.benchmark.macro.ExperimentalBaselineProfilesApi 4 | import androidx.benchmark.macro.junit4.BaselineProfileRule 5 | import androidx.test.ext.junit.runners.AndroidJUnit4 6 | import androidx.test.uiautomator.By 7 | import org.junit.Rule 8 | import org.junit.Test 9 | import org.junit.runner.RunWith 10 | 11 | /** 12 | * Generates a baseline profile which can be copied to `app/src/main/baseline-prof.txt`. 13 | */ 14 | @ExperimentalBaselineProfilesApi 15 | @RunWith(AndroidJUnit4::class) 16 | class BaselineProfileGenerator { 17 | 18 | @get:Rule val baselineProfileRule = BaselineProfileRule() 19 | 20 | @Test 21 | fun startup() = 22 | baselineProfileRule.collectBaselineProfile( 23 | packageName = "dev.baseio.discordjetpackcompose" 24 | ) { 25 | pressHome() 26 | // This block defines the app's critical user journey. Here we are interested in 27 | // optimizing for app startup. But you can also navigate and scroll 28 | // through your most important UI. 29 | startActivityAndWait() 30 | 31 | device.waitForIdle() 32 | device.run { 33 | findObject(By.text("Login")) 34 | .click() 35 | waitForIdle() 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/onboarding/OnboardingRoute.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.onboarding 2 | 3 | 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.compose.composable 6 | import androidx.navigation.navigation 7 | import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator 8 | import dev.baseio.discordjetpackcompose.navigator.DiscordRoute 9 | import dev.baseio.discordjetpackcompose.navigator.DiscordScreen 10 | import dev.baseio.discordjetpackcompose.ui.routes.onboarding.screens.login.LoginScreen 11 | import dev.baseio.discordjetpackcompose.ui.routes.onboarding.screens.welcome.WelcomeScreen 12 | import dev.baseio.discordjetpackcompose.ui.routes.onboarding.screens.register.RegisterScreen 13 | 14 | fun NavGraphBuilder.onBoardingRoute( 15 | composeNavigator: ComposeNavigator 16 | ) { 17 | navigation( 18 | startDestination = DiscordScreen.Welcome.name, 19 | route = DiscordRoute.OnBoarding.name 20 | ) { 21 | composable(DiscordScreen.Welcome.name) { 22 | WelcomeScreen(composeNavigator) 23 | } 24 | composable(DiscordScreen.Login.name) { 25 | LoginScreen(composeNavigator) 26 | } 27 | composable(DiscordScreen.Register.name) { 28 | RegisterScreen(composeNavigator) 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/createServer/ServerTemplates.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.createServer 2 | 3 | import dev.baseio.discordjetpackcompose.R 4 | 5 | data class SampleServerTemplateModel( 6 | val textProvider: Int, 7 | val iconProvider: Int 8 | ) 9 | 10 | object ServerTemplates { 11 | val type = listOf( 12 | SampleServerTemplateModel( 13 | textProvider = R.string.gaming, 14 | iconProvider = R.drawable.dark_app_logo 15 | ), 16 | SampleServerTemplateModel( 17 | textProvider = R.string.school_club, 18 | iconProvider = R.drawable.dark_app_logo 19 | ), 20 | SampleServerTemplateModel( 21 | textProvider = R.string.study_group, 22 | iconProvider = R.drawable.dark_app_logo 23 | ), 24 | SampleServerTemplateModel( 25 | textProvider = R.string.friends, 26 | iconProvider = R.drawable.dark_app_logo 27 | ), 28 | SampleServerTemplateModel( 29 | textProvider = R.string.artists_and_creators, 30 | iconProvider = R.drawable.dark_app_logo 31 | ), 32 | SampleServerTemplateModel( 33 | textProvider = R.string.local_community, 34 | iconProvider = R.drawable.dark_app_logo 35 | ) 36 | ) 37 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/mappers/DiscordMessageMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.mappers 2 | 3 | import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity 4 | import dev.baseio.discordjetpackcompose.local.model.DBDiscordMessage 5 | import javax.inject.Inject 6 | 7 | class DiscordMessageMapper @Inject constructor() : EntityMapper { 8 | 9 | override fun mapToDomain(entity: DBDiscordMessage): DiscordMessageEntity { 10 | return DiscordMessageEntity( 11 | entity.uuid, 12 | entity.channelId, 13 | entity.message, 14 | entity.userId, 15 | entity.replyTo, 16 | entity.replyToMessage, 17 | entity.createdBy, 18 | entity.createdDate, 19 | entity.modifiedDate, 20 | entity.metaTitle, 21 | entity.metaDesc, 22 | entity.metaImageUrl, 23 | entity.metaUrl 24 | ) 25 | } 26 | 27 | override fun mapToData(model: DiscordMessageEntity): DBDiscordMessage { 28 | return DBDiscordMessage( 29 | model.uuid, 30 | model.channelId, 31 | model.message, 32 | model.userId, 33 | model.replyTo, 34 | model.replyToMessage, 35 | model.createdBy, 36 | model.createdDate, 37 | model.modifiedDate, 38 | model.metaTitle, 39 | model.metaDesc, 40 | model.metaImageUrl, 41 | model.metaUrl 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/components/DiscordAppBar.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.components 2 | 3 | import androidx.compose.foundation.layout.RowScope 4 | import androidx.compose.material.AppBarDefaults 5 | import androidx.compose.material.TopAppBar 6 | import androidx.compose.material.contentColorFor 7 | import androidx.compose.material.primarySurface 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.unit.Dp 12 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 13 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordSurface 14 | import dev.baseio.discordjetpackcompose.ui.theme.primarySurface 15 | 16 | @Composable 17 | fun DiscordAppBar( 18 | modifier: Modifier = Modifier, 19 | title: @Composable () -> Unit = {}, 20 | navigationIcon: @Composable (() -> Unit)? = null, 21 | actions: @Composable RowScope.() -> Unit = {}, 22 | backgroundColor: Color = DiscordColorProvider.colors.primarySurface, 23 | contentColor: Color = contentColorFor(backgroundColor), 24 | elevation: Dp = AppBarDefaults.TopAppBarElevation, 25 | ) { 26 | DiscordSurface( 27 | color = backgroundColor, 28 | contentColor = contentColor, 29 | elevation = elevation 30 | ) { 31 | TopAppBar( 32 | title, modifier, navigationIcon, actions, backgroundColor, contentColor, elevation 33 | ) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsTitle.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.text.font.FontWeight 11 | import androidx.compose.ui.text.style.TextAlign 12 | import androidx.compose.ui.unit.dp 13 | import androidx.compose.ui.unit.sp 14 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 15 | import dev.baseio.discordjetpackcompose.ui.theme.Typography 16 | import dev.baseio.discordjetpackcompose.ui.theme.user_settings_text 17 | 18 | @Composable 19 | fun GetSettingsSubtitle(title: String) { 20 | Box( 21 | modifier = Modifier 22 | .background(DiscordColorProvider.colors.settingsBackground) 23 | .fillMaxWidth() 24 | ) { 25 | Text( 26 | text = title, 27 | style = Typography.h3.copy( 28 | fontWeight = FontWeight.SemiBold, 29 | fontSize = 15.sp, 30 | color = user_settings_text 31 | ), 32 | textAlign = TextAlign.Start, 33 | modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 16.dp) 34 | ) 35 | } 36 | } -------------------------------------------------------------------------------- /navigator/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN) 3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN) 4 | id(BuildPlugins.KOTLIN_KAPT) 5 | } 6 | 7 | android { 8 | compileSdk = ProjectProperties.COMPILE_SDK 9 | 10 | defaultConfig { 11 | minSdk = (ProjectProperties.MIN_SDK) 12 | targetSdk = (ProjectProperties.TARGET_SDK) 13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | 16 | buildTypes { 17 | getByName("release") { 18 | isMinifyEnabled = false 19 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 20 | } 21 | } 22 | 23 | compileOptions { 24 | sourceCompatibility = JavaVersion.VERSION_1_8 25 | targetCompatibility = JavaVersion.VERSION_1_8 26 | } 27 | 28 | kotlinOptions { 29 | jvmTarget = "1.8" 30 | } 31 | 32 | composeOptions { 33 | kotlinCompilerExtensionVersion = Lib.Android.COMPOSE_COMPILER_VERSION 34 | } 35 | } 36 | 37 | // Required for annotation processing plugins like Dagger 38 | kapt { 39 | generateStubs = true 40 | correctErrorTypes = true 41 | } 42 | 43 | dependencies { 44 | /*Kotlin*/ 45 | implementation(Lib.Android.APP_COMPAT) 46 | implementation(Lib.Kotlin.KTX_CORE) 47 | api(Lib.Async.COROUTINES) 48 | api(Lib.Async.COROUTINES_ANDROID) 49 | 50 | implementation(Lib.Kotlin.KT_STD) 51 | implementation(Lib.Android.COMPOSE_NAVIGATION) 52 | 53 | implementation(Lib.Android.COMPOSE_NAVIGATION) 54 | implementation(Lib.Di.hiltNavigationCompose) 55 | } -------------------------------------------------------------------------------- /benchmark/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.test") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | compileSdk = 32 8 | 9 | compileOptions { 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | } 13 | 14 | kotlinOptions { 15 | jvmTarget = "1.8" 16 | } 17 | 18 | defaultConfig { 19 | minSdk = 28 20 | targetSdk = 32 21 | 22 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | 25 | buildTypes { 26 | // This benchmark buildType is used for benchmarking, and should function like your 27 | // release build (for example, with minification on). It"s signed with a debug key 28 | // for easy local/CI testing. 29 | create("benchmark") { 30 | isDebuggable = true 31 | signingConfig = getByName("debug").signingConfig 32 | matchingFallbacks += listOf("release") 33 | } 34 | } 35 | 36 | targetProjectPath = ":app" 37 | experimentalProperties["android.experimental.self-instrumenting"] = true 38 | } 39 | 40 | dependencies { 41 | implementation("androidx.test.ext:junit:1.1.3") 42 | implementation("androidx.test.espresso:espresso-core:3.4.0") 43 | implementation("androidx.test.uiautomator:uiautomator:2.2.0") 44 | implementation("androidx.benchmark:benchmark-macro-junit4:1.1.0-beta04") 45 | implementation("androidx.profileinstaller:profileinstaller:1.2.0-rc01") 46 | } 47 | 48 | androidComponents { 49 | beforeVariants(selector().all()) { 50 | it.enabled = it.buildType == "benchmark" 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/notifications/components/SectionItem.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.material.ContentAlpha 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.draw.alpha 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.unit.Dp 16 | import androidx.compose.ui.unit.dp 17 | 18 | @Composable 19 | fun SectionItem( 20 | color: Color? = null, 21 | disabled: Boolean = false, 22 | paddingVertical: Dp = 0.dp, 23 | paddingHorizontal: Dp = 16.dp, 24 | onClick: () -> Unit, 25 | leadingComposable: @Composable () -> Unit = {}, 26 | trailingComposable: @Composable () -> Unit = {}, 27 | ) { 28 | Row( 29 | modifier = Modifier 30 | .fillMaxWidth() 31 | .background(color = color ?: Color.Transparent) 32 | .alpha(if(disabled) ContentAlpha.disabled else 1.0f) 33 | .clickable(onClick = onClick) 34 | .padding(horizontal = paddingHorizontal, vertical = paddingVertical), 35 | horizontalArrangement = Arrangement.SpaceBetween, 36 | verticalAlignment = Alignment.CenterVertically 37 | ) { 38 | leadingComposable() 39 | trailingComposable() 40 | } 41 | } -------------------------------------------------------------------------------- /data/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN) 3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN) 4 | id(BuildPlugins.KOTLIN_KAPT) 5 | id(BuildPlugins.DAGGER_HILT) 6 | } 7 | 8 | android { 9 | compileSdk = ProjectProperties.COMPILE_SDK 10 | 11 | defaultConfig { 12 | minSdk = (ProjectProperties.MIN_SDK) 13 | targetSdk = (ProjectProperties.TARGET_SDK) 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | getByName("release") { 19 | isMinifyEnabled = false 20 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 21 | } 22 | } 23 | } 24 | 25 | // Required for annotation processing plugins like Dagger 26 | kapt { 27 | generateStubs = true 28 | correctErrorTypes = true 29 | } 30 | 31 | dependencies { 32 | 33 | implementation(project(":domain")) 34 | 35 | api(Lib.Android.MATERIAL_EXTENDED_ICONS) // todo: DATA shouldn't deal with presentation deps 36 | 37 | /*Kotlin*/ 38 | api(Lib.Kotlin.KT_STD) 39 | api(Lib.Async.COROUTINES) 40 | 41 | /* Paging */ 42 | implementation(Lib.Paging.PAGING_3) 43 | implementation(Lib.Jsoup.JSOUP) 44 | 45 | /* Room */ 46 | api(Lib.Room.roomRuntime) 47 | kapt(Lib.Room.roomCompiler) 48 | api(Lib.Room.roomKtx) 49 | api(Lib.Room.roomPaging) 50 | 51 | /* Networking */ 52 | api(Lib.Networking.RETROFIT) 53 | api(Lib.Networking.RETROFIT_GSON) 54 | api(Lib.Networking.LOGGING) 55 | 56 | api(Lib.Serialization.GSON) 57 | 58 | /* Dependency Injection */ 59 | api(Lib.Di.hiltAndroid) 60 | kapt(Lib.Di.hiltAndroidCompiler) 61 | 62 | /* Logger */ 63 | implementation(Lib.Logger.TIMBER) 64 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/viewmodels/RegistrationViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.viewmodels 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import androidx.compose.runtime.snapshotFlow 7 | import androidx.lifecycle.ViewModel 8 | import androidx.lifecycle.viewModelScope 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import dev.baseio.discordjetpackcompose.entities.CountryEntity 11 | import dev.baseio.discordjetpackcompose.usecases.FetchCountriesUseCase 12 | import javax.inject.Inject 13 | import kotlinx.coroutines.flow.launchIn 14 | import kotlinx.coroutines.flow.onEach 15 | import kotlinx.coroutines.launch 16 | 17 | @HiltViewModel 18 | class RegistrationViewModel @Inject constructor( 19 | val fetchCountriesUseCase: FetchCountriesUseCase 20 | ) : ViewModel() { 21 | private var countryList: List? by mutableStateOf(null) 22 | var filteredCountryList: List? by mutableStateOf(null) 23 | private set 24 | private var countrySearchQuery: String by mutableStateOf("") 25 | 26 | init { 27 | getCountryList() 28 | snapshotFlow { countrySearchQuery }.onEach { query -> 29 | filteredCountryList = countryList?.filter { country -> 30 | country.name.contains(other = query, ignoreCase = true) 31 | } 32 | }.launchIn(viewModelScope) 33 | } 34 | 35 | private fun getCountryList() { 36 | viewModelScope.launch { 37 | countryList = fetchCountriesUseCase() 38 | } 39 | } 40 | 41 | fun updateCountryQuery(query: String) { 42 | countrySearchQuery = query 43 | } 44 | } -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/Screens.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.navigator 2 | 3 | import androidx.navigation.NamedNavArgument 4 | 5 | sealed class DiscordScreen( 6 | val route: String, 7 | val navArguments: List = emptyList() 8 | ) { 9 | val name: String = route.appendArguments(navArguments) 10 | 11 | object Welcome : DiscordScreen("welcome") 12 | object Register : DiscordScreen("register") 13 | object Login : DiscordScreen("login") 14 | object Dashboard : DiscordScreen("dashboard") 15 | object Friends : DiscordScreen("friends") 16 | object CreateServer : DiscordScreen("createServer") 17 | object NotificationSettings : DiscordScreen("notificationSettings") 18 | object Invite : DiscordScreen("invite") 19 | object UserSettings: DiscordScreen("userSettings") 20 | object Home : DiscordScreen("home") 21 | object Search : DiscordScreen("search") 22 | } 23 | 24 | sealed class DiscordRoute(val name: String) { 25 | object OnBoarding : DiscordRoute("onboardingRoute") 26 | object Dashboard : DiscordRoute("dashboardRoute") 27 | object DashboardBottomNav: DiscordRoute("dashboardBottomNav") 28 | } 29 | 30 | private fun String.appendArguments(navArguments: List): String { 31 | val mandatoryArguments = navArguments.filter { it.argument.defaultValue == null } 32 | .takeIf { it.isNotEmpty() } 33 | ?.joinToString(separator = "/", prefix = "/") { "{${it.name}}" } 34 | .orEmpty() 35 | val optionalArguments = navArguments.filter { it.argument.defaultValue != null } 36 | .takeIf { it.isNotEmpty() } 37 | ?.joinToString(separator = "&", prefix = "?") { "${it.name}={${it.name}}" } 38 | .orEmpty() 39 | return "$this$mandatoryArguments$optionalArguments" 40 | } -------------------------------------------------------------------------------- /.github/issue_template/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: PULL_REQUEST_TEMPLATE.md 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Description 11 | 12 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 13 | 14 | ## Associated Tickets 15 | - [Feature Name](https://github.com/Anmol92verma/DiscordJetpackCompose/issues/{ticket-number}) 16 | 17 | 18 | ## Type of change 19 | 20 | Please delete options that are not relevant. 21 | 22 | - [ ] Bug fix (non-breaking change which fixes an issue) 23 | - [ ] New feature (non-breaking change which adds functionality) 24 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 25 | - [ ] This change requires a documentation update 26 | 27 | # How Has This Been Tested? 28 | 29 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 30 | 31 | - [ ] Test A 32 | - [ ] Test B 33 | 34 | **Test Configuration**: 35 | * Firmware version: 36 | * Hardware: 37 | * Toolchain: 38 | * SDK: 39 | 40 | # Checklist: 41 | 42 | - [ ] My code follows the style guidelines of this project 43 | - [ ] I have performed a self-review of my own code 44 | - [ ] I have commented my code, particularly in hard-to-understand areas 45 | - [ ] I have made corresponding changes to the documentation 46 | - [ ] My changes generate no new warnings 47 | - [ ] I have added tests that prove my fix is effective or that my feature works 48 | - [ ] New and existing unit tests pass locally with my changes 49 | - [ ] Any dependent changes have been merged and published in downstream modules -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/di/UseCaseModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import dev.baseio.discordjetpackcompose.repositories.CountryRepo 8 | import dev.baseio.discordjetpackcompose.repositories.FriendsRepo 9 | import dev.baseio.discordjetpackcompose.repositories.MessagesRepo 10 | import dev.baseio.discordjetpackcompose.repositories.ServerRepo 11 | import dev.baseio.discordjetpackcompose.usecases.FetchCountriesUseCase 12 | import dev.baseio.discordjetpackcompose.usecases.FetchFriendSuggestionsUseCase 13 | import dev.baseio.discordjetpackcompose.usecases.FetchFriendsUseCase 14 | import dev.baseio.discordjetpackcompose.usecases.GetServerUseCase 15 | import dev.baseio.discordjetpackcompose.usecases.chat.FetchMessagesUseCase 16 | import dev.baseio.discordjetpackcompose.usecases.chat.SendMessageUseCase 17 | import javax.inject.Singleton 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | object UseCaseModule { 22 | @Provides 23 | @Singleton 24 | fun provideFetchCountriesUseCase(countryRepo: CountryRepo): FetchCountriesUseCase = 25 | FetchCountriesUseCase(countryRepo) 26 | 27 | @Provides 28 | @Singleton 29 | fun provideGetServerUseCase(serverRepo: ServerRepo) = GetServerUseCase(serverRepo = serverRepo) 30 | 31 | @Provides 32 | @Singleton 33 | fun provideFriendsSuggestionUseCase(friendsRepo: FriendsRepo) = FetchFriendSuggestionsUseCase(friendsRepo) 34 | 35 | @Provides 36 | @Singleton 37 | fun provideFriendsUseCase(friendsRepo: FriendsRepo) = FetchFriendsUseCase(friendsRepo) 38 | 39 | @Provides 40 | fun provideFetchMessageUseCase(messagesRepo: MessagesRepo) = FetchMessagesUseCase(messagesRepo) 41 | 42 | @Provides 43 | fun provideSendMessageUseCase(messagesRepo: MessagesRepo) = SendMessageUseCase(messagesRepo) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/viewmodels/FriendsViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.viewmodels 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.setValue 7 | import androidx.lifecycle.ViewModel 8 | import androidx.lifecycle.viewModelScope 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import dev.baseio.discordjetpackcompose.entities.ChatUserEntity 11 | import dev.baseio.discordjetpackcompose.usecases.FetchCountriesUseCase 12 | import dev.baseio.discordjetpackcompose.usecases.FetchFriendSuggestionsUseCase 13 | import dev.baseio.discordjetpackcompose.usecases.FetchFriendsUseCase 14 | import kotlinx.coroutines.flow.Flow 15 | import kotlinx.coroutines.flow.MutableStateFlow 16 | import kotlinx.coroutines.flow.asStateFlow 17 | import kotlinx.coroutines.flow.emptyFlow 18 | import kotlinx.coroutines.launch 19 | import javax.inject.Inject 20 | 21 | @HiltViewModel 22 | class FriendsViewModel @Inject constructor( 23 | private val fetchFriendsUseCase: FetchFriendsUseCase, 24 | private val fetchFriendSuggestionsUseCase: FetchFriendSuggestionsUseCase, 25 | ) : ViewModel() { 26 | 27 | private val _friendsSuggestionsList = MutableStateFlow>>(emptyFlow()) 28 | val friendsSuggestionsList = _friendsSuggestionsList.asStateFlow() 29 | 30 | private val _friendsList = MutableStateFlow>>(emptyFlow()) 31 | val friendsList = _friendsList.asStateFlow() 32 | 33 | init { 34 | fetchFriendsList() 35 | fetchSuggestionsList() 36 | } 37 | 38 | private fun fetchSuggestionsList(){ 39 | viewModelScope.launch { 40 | _friendsSuggestionsList.value = fetchFriendSuggestionsUseCase() 41 | } 42 | } 43 | 44 | private fun fetchFriendsList(){ 45 | viewModelScope.launch { 46 | _friendsList.value = fetchFriendsUseCase() 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/ComposeNavigator.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.navigator 2 | 3 | import androidx.navigation.NavOptionsBuilder 4 | import androidx.navigation.navOptions 5 | import kotlinx.coroutines.flow.* 6 | import javax.inject.Inject 7 | 8 | class DiscordComposeNavigator @Inject constructor(): ComposeNavigator() { 9 | 10 | override fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)?) { 11 | val options = optionsBuilder?.let { navOptions(it) } 12 | navigationCommands.tryEmit(ComposeNavigationCommand.NavigateToRoute(route, options)) 13 | } 14 | 15 | override fun navigateAndClearBackStack(route: String) { 16 | navigationCommands.tryEmit(ComposeNavigationCommand.NavigateToRoute(route, navOptions { 17 | popUpTo(0) 18 | })) 19 | } 20 | 21 | override fun popUpTo(route: String, inclusive: Boolean) { 22 | navigationCommands.tryEmit(ComposeNavigationCommand.PopUpToRoute(route, inclusive)) 23 | } 24 | 25 | override fun navigateBackWithResult( 26 | key: String, 27 | result: T, 28 | route: String? 29 | ) { 30 | navigationCommands.tryEmit( 31 | ComposeNavigationCommand.NavigateUpWithResult( 32 | key = key, 33 | result = result, 34 | route = route 35 | ) 36 | ) 37 | } 38 | 39 | override fun observeResult(key: String, route: String?): Flow { 40 | return navControllerFlow 41 | .filterNotNull() 42 | .flatMapLatest { navController -> 43 | val backStackEntry = route?.let { navController.getBackStackEntry(it) } 44 | ?: navController.currentBackStackEntry 45 | 46 | backStackEntry?.savedStateHandle?.let { savedStateHandle -> 47 | savedStateHandle.getLiveData(key) 48 | .asFlow() 49 | .filter { it != null } 50 | .onEach { 51 | // Nullify the result to avoid resubmitting it 52 | savedStateHandle.set(key, null) 53 | } 54 | } ?: emptyFlow() 55 | } 56 | } 57 | 58 | 59 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/notifications/components/SubtitleAppBar.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material.Icon 6 | import androidx.compose.material.IconButton 7 | import androidx.compose.material.Text 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.filled.ArrowBack 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.text.TextStyle 13 | import androidx.compose.ui.text.font.FontWeight 14 | import androidx.compose.ui.unit.dp 15 | import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator 16 | import dev.baseio.discordjetpackcompose.ui.components.DiscordAppBar 17 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 18 | import dev.baseio.discordjetpackcompose.ui.theme.Typography 19 | 20 | @Composable 21 | fun SubtitledAppBar( 22 | composeNavigator: ComposeNavigator, 23 | title: String, 24 | subtitle: String? 25 | ) { 26 | DiscordAppBar( 27 | navigationIcon = { 28 | IconButton(onClick = { 29 | composeNavigator.navigateUp() 30 | }) { 31 | Icon( 32 | imageVector = Icons.Filled.ArrowBack, 33 | contentDescription = null, 34 | modifier = Modifier.padding(start = 8.dp), 35 | ) 36 | } 37 | }, 38 | backgroundColor = DiscordColorProvider.colors.discordBackgroundOne.copy(alpha = 0.5f), 39 | elevation = 0.dp, 40 | title = { 41 | Column { 42 | Text( 43 | text = title, 44 | style = TextStyle(fontWeight = FontWeight.Bold, fontSize = Typography.h6.fontSize) 45 | ) 46 | if (!subtitle.isNullOrBlank()) { 47 | Text( 48 | text = subtitle, 49 | style = TextStyle(fontSize = Typography.body2.fontSize) 50 | ) 51 | } 52 | } 53 | } 54 | ) 55 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_emoji_2.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_emoji_3.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/dasboard/DashboardUtil.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.dasboard 2 | 3 | import androidx.navigation.NavGraph.Companion.findStartDestination 4 | import androidx.navigation.NavHostController 5 | import dev.baseio.discordjetpackcompose.entities.ChatUserEntity 6 | import dev.baseio.discordjetpackcompose.entities.server.ServerEntity 7 | import dev.baseio.discordjetpackcompose.navigator.DiscordScreen 8 | import dev.baseio.discordjetpackcompose.utils.getSampleServer 9 | 10 | fun getFakeChatUserList(): MutableList { 11 | return mutableListOf().apply { 12 | add( 13 | ChatUserEntity( 14 | username = "MEE6", 15 | name = "MEE6", 16 | isOnline = false, 17 | ) 18 | ) 19 | repeat(20) { 20 | add( 21 | if (it % 2 == 0) { 22 | ChatUserEntity( 23 | username = "TestUser$it", 24 | name = "Test User $it", 25 | currentStatus = "Studying", 26 | isOnline = false, 27 | ) 28 | } else { 29 | ChatUserEntity( 30 | username = "TestUser$it", 31 | name = "Test User $it", 32 | isOnline = true, 33 | ) 34 | } 35 | ) 36 | } 37 | } 38 | } 39 | 40 | fun getFakeServerList(): List { 41 | return listOf( 42 | getSampleServer(serverId = "1"), 43 | getSampleServer(serverId = "2"), 44 | ) 45 | } 46 | 47 | fun NavHostController.navigateTab(screen: DiscordScreen) { 48 | navigate(screen.route) { 49 | // Pop up to the start destination of the graph to 50 | // avoid building up a large stack of destinations 51 | // on the back stack as users select items 52 | popUpTo(graph.findStartDestination().id) { 53 | saveState = true 54 | } 55 | // Avoid multiple copies of the same destination when 56 | // reselecting the same item 57 | launchSingleTop = true 58 | // Restore state when reselecting a previously selected item 59 | restoreState = true 60 | } 61 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/discordjetpackcompose/entities/server/ServerEntity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.entities.server 2 | 3 | import dev.baseio.discordjetpackcompose.utils.Constants 4 | 5 | /** 6 | * Model class depicting a Discord server. 7 | * @param id Unique identifier for the server 8 | * @param name Name of the server 9 | * @param thumbnailUri URL to the thumbnail image for the server (Optional) 10 | * @param selectedAnimationUri URL of the animation which will be played whenever this server is selected in the list (Optional) 11 | * @param posterUri URL of the large poster/image to be displayed when this server is selected (Optional) 12 | * @param channels List of channels ([ChannelEntity]) this server contains 13 | * @param allChannelsUnreadCount Total unread message count of all the channels (Optional) 14 | * @param hasNitroSubscription Whether the channel has purchased the Discord Nitro subscription (this will lead to a special icon being displayed before the channel name) 15 | * @param totalMembersCount Displays how many members have joined this server so far 16 | * @param onlineMembersCount Displays how many members are online right now 17 | * @param description A short description about the server (Optional) 18 | * @param boostCount Displays how many Discord Boosts does this channel have 19 | * @param serverEmojiUris List of URIs for emojis which are specific to this server by default (Can be used in other servers too with the Discord Nitro Subscription) 20 | * */ 21 | data class ServerEntity( 22 | val id: String, 23 | val name: String, 24 | val thumbnailUri: String = Constants.MMLogoUrl, 25 | val selectedAnimationUri: String? = null, 26 | val posterUri: String? = null, 27 | val channels: List = emptyList(), 28 | val allChannelsUnreadCount: Int = channels.sumOf { it.unreadCount }, 29 | val hasNitroSubscription: Boolean, 30 | val totalMembersCount: Int = 0, 31 | val onlineMembersCount: Int = 0, 32 | val description: String? = null, 33 | val boostCount: Int = 0, 34 | val serverEmojiUris: List = emptyList() 35 | ) 36 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.runtime.LaunchedEffect 7 | import androidx.core.view.WindowCompat 8 | import androidx.navigation.compose.NavHost 9 | import androidx.navigation.compose.rememberNavController 10 | import com.google.accompanist.insets.ProvideWindowInsets 11 | import dagger.hilt.android.AndroidEntryPoint 12 | import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator 13 | import dev.baseio.discordjetpackcompose.navigator.DiscordRoute 14 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.dashboardRoute 15 | import dev.baseio.discordjetpackcompose.ui.routes.onboarding.onBoardingRoute 16 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordJetpackComposeTheme 17 | import javax.inject.Inject 18 | 19 | @AndroidEntryPoint 20 | class MainActivity : ComponentActivity() { 21 | 22 | @Inject 23 | lateinit var composeNavigator: ComposeNavigator 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | setTheme(R.style.Theme_DiscordJetpackCompose) 27 | 28 | super.onCreate(savedInstanceState) 29 | 30 | WindowCompat.setDecorFitsSystemWindows(window, false) 31 | 32 | setContent { 33 | val navController = rememberNavController() 34 | 35 | LaunchedEffect(Unit) { 36 | composeNavigator.handleNavigationCommands(navController) 37 | } 38 | DiscordJetpackComposeTheme { 39 | 40 | ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { 41 | NavHost( 42 | navController = navController, 43 | startDestination = DiscordRoute.OnBoarding.name, 44 | ) { 45 | onBoardingRoute(composeNavigator) 46 | dashboardRoute(composeNavigator) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/repositories/FriendsRepoImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.repositories 2 | 3 | import dev.baseio.discordjetpackcompose.entities.ChatUserEntity 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.flowOf 6 | 7 | class FriendsRepoImpl() : FriendsRepo { 8 | override suspend fun fetchFriendSuggestions(): Flow> { 9 | return flowOf( 10 | listOf( 11 | ChatUserEntity("Eren yeager", name = "Eren", isOnline = false), 12 | ChatUserEntity("Kamado Tanjiro", name = "Monjiro", isOnline = false) 13 | ) 14 | ) 15 | } 16 | 17 | override suspend fun fetchFriends(): Flow> { 18 | return flowOf( 19 | listOf( 20 | ChatUserEntity("Mikasa Ackerman", name = "Mikasa", isOnline = false, currentStatus = "Offline"), 21 | ChatUserEntity("Gojo Satoru", name = "Nanami", isOnline = true, currentStatus = "Online"), 22 | ChatUserEntity("Akagami Shanks", name = "Shanks", isOnline = false, currentStatus = "Offline"), 23 | ChatUserEntity("Roronoa Zoro", name = "Zoro", isOnline = false, currentStatus = "Offline"), 24 | ChatUserEntity("Itachi Uchiha", name = "Itachi", isOnline = true, currentStatus = "Online"), 25 | ChatUserEntity("Monkey D Luffy", name = "Luffy", isOnline = true, currentStatus = "Online"), 26 | ChatUserEntity("Hatake Kakashi", name = "Kakashi", isOnline = false, currentStatus = "Offline"), 27 | ChatUserEntity("Gol D Roger", name = "Roger", isOnline = false, currentStatus = "Offline"), 28 | ChatUserEntity("Portgas D Ace", name = "Ace", isOnline = false, currentStatus = "Offline"), 29 | ChatUserEntity("Nico Robin", name = "Robin", isOnline = false, currentStatus = "Offline"), 30 | ChatUserEntity("Anya Forger", name = "Anya", isOnline = false, currentStatus = "Offline"), 31 | ChatUserEntity("Boa Hancock", name = "Boa", isOnline = false, currentStatus = "Offline"), 32 | ) 33 | ) 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/components/DiscordScaffold.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.components 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material.* 6 | import androidx.compose.material.icons.Icons.Filled 7 | import androidx.compose.material.icons.filled.ArrowBack 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.unit.dp 12 | import com.google.accompanist.insets.statusBarsPadding 13 | import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator 14 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 15 | import dev.baseio.discordjetpackcompose.ui.theme.contentColorFor 16 | 17 | @Composable 18 | fun DiscordScaffold( 19 | modifier: Modifier = Modifier, 20 | scaffoldState: ScaffoldState, 21 | navigator: ComposeNavigator? = null, 22 | backgroundColor: Color = DiscordColorProvider.colors.background, 23 | contentColor: Color = DiscordColorProvider.colors.contentColorFor(DiscordColorProvider.colors.background), 24 | topAppBar: @Composable () -> Unit = { 25 | DiscordAppBar( 26 | navigationIcon = { 27 | IconButton(onClick = { 28 | navigator?.navigateUp() 29 | }) { 30 | Icon( 31 | imageVector = Filled.ArrowBack, 32 | contentDescription = "Back", 33 | modifier = Modifier.padding(start = 8.dp), 34 | ) 35 | } 36 | }, 37 | backgroundColor = Color.Transparent, 38 | elevation = 0.dp 39 | ) 40 | }, 41 | content: @Composable (PaddingValues) -> Unit 42 | ) { 43 | Scaffold( 44 | backgroundColor = backgroundColor, 45 | contentColor = contentColor, 46 | modifier = modifier, 47 | scaffoldState = scaffoldState, 48 | topBar = topAppBar, 49 | snackbarHost = { 50 | scaffoldState.snackbarHostState 51 | }, 52 | content = content 53 | ) 54 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/components/SettingsAppBar.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material.DropdownMenu 6 | import androidx.compose.material.DropdownMenuItem 7 | import androidx.compose.material.Icon 8 | import androidx.compose.material.IconButton 9 | import androidx.compose.material.Text 10 | import androidx.compose.material.icons.Icons 11 | import androidx.compose.material.icons.Icons.Filled 12 | import androidx.compose.material.icons.filled.Logout 13 | import androidx.compose.material.icons.filled.MoreVert 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.runtime.getValue 16 | import androidx.compose.runtime.mutableStateOf 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.runtime.setValue 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.graphics.Color 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.compose.ui.text.style.TextAlign 23 | import androidx.compose.ui.unit.dp 24 | import dev.baseio.discordjetpackcompose.R.string 25 | import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator 26 | import dev.baseio.discordjetpackcompose.navigator.DiscordScreen.Login 27 | 28 | @Composable 29 | fun UserSettingsAppBar(composeNavigator: ComposeNavigator) { 30 | var displayMenu by remember { mutableStateOf(false) } 31 | IconButton( 32 | onClick = { composeNavigator.navigateAndClearBackStack(Login.name) }) { 33 | Icon( 34 | imageVector = Filled.Logout, 35 | contentDescription = stringResource(string.logout), 36 | modifier = Modifier.padding(start = 8.dp), 37 | ) 38 | } 39 | 40 | IconButton(onClick = { displayMenu = !displayMenu }) { 41 | Icon(Icons.Default.MoreVert, null) 42 | } 43 | DropdownMenu( 44 | expanded = displayMenu, 45 | onDismissRequest = { displayMenu = false }, 46 | modifier = Modifier.background(Color.White) 47 | ) { 48 | DropdownMenuItem(onClick = { }) { 49 | Text( 50 | text = stringResource(string.debug), color = Color.Black, 51 | textAlign = TextAlign.Center 52 | ) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/onboarding/commonui/CenteredTitleSubtitle.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.onboarding.commonui 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.res.stringResource 12 | import androidx.compose.ui.text.font.FontWeight 13 | import androidx.compose.ui.text.style.TextAlign 14 | import androidx.compose.ui.unit.TextUnit 15 | import androidx.compose.ui.unit.dp 16 | import androidx.compose.ui.unit.sp 17 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 18 | import dev.baseio.discordjetpackcompose.ui.theme.Typography 19 | 20 | @Composable 21 | fun CenteredTitleSubtitle( 22 | modifier: Modifier = Modifier, 23 | title: Int, 24 | subtitle: Int, 25 | titleFontSize: TextUnit = 24.sp, 26 | subtitleFontSize: TextUnit = 16.sp 27 | ) { 28 | Column(modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { 29 | Title(title = title, titleFontSize = titleFontSize) 30 | Spacer(modifier = Modifier.height(8.dp)) 31 | SubTitle(subtitle = subtitle, subtitleFontSize = subtitleFontSize) 32 | } 33 | } 34 | 35 | @Composable 36 | private fun SubTitle( 37 | subtitle: Int, 38 | subtitleFontSize: TextUnit = 16.sp 39 | ) { 40 | Text( 41 | text = stringResource(id = subtitle), 42 | style = Typography.subtitle1.copy(fontSize = subtitleFontSize), 43 | textAlign = TextAlign.Center, 44 | color = DiscordColorProvider.colors.onSurface 45 | ) 46 | } 47 | 48 | @Composable 49 | private fun Title( 50 | title: Int, 51 | titleFontSize: TextUnit = 24.sp 52 | ) { 53 | Text( 54 | text = stringResource(id = title), 55 | style = Typography.h5.copy( 56 | fontWeight = FontWeight.Bold, 57 | fontSize = titleFontSize 58 | ), 59 | textAlign = TextAlign.Center, 60 | color = DiscordColorProvider.colors.onSurface 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_emoji_5.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 37 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.di 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import dagger.hilt.components.SingletonComponent 9 | import dev.baseio.discordjetpackcompose.di.dispatcher.CoroutineDispatcherProvider 10 | import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity 11 | import dev.baseio.discordjetpackcompose.local.dao.DiscordMessageDao 12 | import dev.baseio.discordjetpackcompose.local.model.DBDiscordMessage 13 | import dev.baseio.discordjetpackcompose.mappers.EntityMapper 14 | import dev.baseio.discordjetpackcompose.repositories.CountryRepo 15 | import dev.baseio.discordjetpackcompose.repositories.CountryRepoImpl 16 | import dev.baseio.discordjetpackcompose.repositories.FriendsRepo 17 | import dev.baseio.discordjetpackcompose.repositories.FriendsRepoImpl 18 | import dev.baseio.discordjetpackcompose.repositories.MessagesRepo 19 | import dev.baseio.discordjetpackcompose.repositories.MessagesRepoImpl 20 | import dev.baseio.discordjetpackcompose.repositories.ServerRepo 21 | import dev.baseio.discordjetpackcompose.repositories.ServerRepoImpl 22 | import javax.inject.Singleton 23 | 24 | @Module 25 | @InstallIn(SingletonComponent::class) 26 | object RepositoryModule { 27 | @Provides 28 | @Singleton 29 | fun provideCountryRepo(@ApplicationContext context: Context): CountryRepo = 30 | CountryRepoImpl(context = context) 31 | 32 | @Provides 33 | @Singleton 34 | fun provideServerRepo( 35 | coroutineDispatcherProvider: CoroutineDispatcherProvider 36 | ): ServerRepo = ServerRepoImpl(coroutineDispatcherProvider = coroutineDispatcherProvider) 37 | 38 | @Provides 39 | @Singleton 40 | fun provideFriendsRepo(): FriendsRepo = FriendsRepoImpl() 41 | 42 | 43 | @Provides 44 | @Singleton 45 | fun provideMessagesRepo( 46 | discordMessageDao: DiscordMessageDao, 47 | entityMapper: EntityMapper, 48 | coroutineDispatcherProvider: CoroutineDispatcherProvider 49 | ): MessagesRepo = MessagesRepoImpl( 50 | discordMessageDao, 51 | entityMapper, 52 | coroutineDispatcherProvider 53 | ) 54 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_emoji_4.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_emoji_1.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/theme/Surface.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.theme 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.border 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.material.LocalContentColor 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.CompositionLocalProvider 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.draw.clip 12 | import androidx.compose.ui.draw.shadow 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.graphics.RectangleShape 15 | import androidx.compose.ui.graphics.Shape 16 | import androidx.compose.ui.graphics.compositeOver 17 | import androidx.compose.ui.unit.Dp 18 | import androidx.compose.ui.unit.dp 19 | import androidx.compose.ui.zIndex 20 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider.colors 21 | import kotlin.math.ln 22 | 23 | /** 24 | * An alternative to [androidx.compose.material.Surface] 25 | */ 26 | @Composable 27 | fun DiscordSurface( 28 | modifier: Modifier = Modifier, 29 | shape: Shape = RectangleShape, 30 | color: Color = colors.surface, 31 | contentColor: Color = colors.secondary, 32 | border: BorderStroke? = null, 33 | elevation: Dp = 0.dp, 34 | content: @Composable () -> Unit 35 | ) { 36 | Box( 37 | modifier = modifier 38 | .shadow(elevation = elevation, shape = shape, clip = false) 39 | .zIndex(elevation.value) 40 | .then(if (border != null) Modifier.border(border, shape) else Modifier) 41 | .background( 42 | color = getBackgroundColorForElevation(color, elevation), 43 | shape = shape 44 | ) 45 | .clip(shape) 46 | ) { 47 | CompositionLocalProvider(LocalContentColor provides contentColor, content = content) 48 | } 49 | } 50 | 51 | @Composable 52 | private fun getBackgroundColorForElevation( 53 | color: Color, 54 | elevation: Dp 55 | ): Color { 56 | return if (elevation > 0.dp 57 | ) { 58 | color.withElevation(elevation) 59 | } else { 60 | color 61 | } 62 | } 63 | 64 | /** 65 | * Applies a [Color.White] overlay to this color based on the [elevation]. This increases visibility 66 | * of elevation for surfaces in a dark theme. 67 | */ 68 | private fun Color.withElevation(elevation: Dp): Color { 69 | val foreground = calculateForeground(elevation) 70 | return foreground.compositeOver(this) 71 | } 72 | 73 | /** 74 | * @return the alpha-modified [Color.White] to overlay on top of the surface color to produce 75 | * the resultant color. 76 | */ 77 | private fun calculateForeground(elevation: Dp): Color { 78 | val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 20f 79 | return Color.White.copy(alpha = alpha) 80 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/repositories/MessagesRepoImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.repositories 2 | 3 | import androidx.paging.Pager 4 | import androidx.paging.PagingConfig 5 | import androidx.paging.PagingData 6 | import androidx.paging.map 7 | import dev.baseio.discordjetpackcompose.di.dispatcher.CoroutineDispatcherProvider 8 | import dev.baseio.discordjetpackcompose.entities.message.DiscordMessageEntity 9 | import dev.baseio.discordjetpackcompose.entities.message.DiscordUrlMetaEntity 10 | import dev.baseio.discordjetpackcompose.local.dao.DiscordMessageDao 11 | import dev.baseio.discordjetpackcompose.local.model.DBDiscordMessage 12 | import dev.baseio.discordjetpackcompose.mappers.EntityMapper 13 | import kotlinx.coroutines.flow.Flow 14 | import kotlinx.coroutines.flow.mapLatest 15 | import kotlinx.coroutines.withContext 16 | import org.jsoup.Jsoup 17 | import javax.inject.Inject 18 | 19 | class MessagesRepoImpl @Inject constructor( 20 | private val discordMessageDao: DiscordMessageDao, 21 | private val entityMapper: EntityMapper, 22 | private val coroutineMainDispatcherProvider: CoroutineDispatcherProvider 23 | ) : MessagesRepo { 24 | 25 | private val cacheUrlMap = hashMapOf() 26 | 27 | override fun fetchMessages(params: String?): Flow> { 28 | return Pager(PagingConfig(pageSize = 20)) { 29 | discordMessageDao.messagesByDate(params) 30 | }.flow.mapLatest { it -> it.map { entityMapper.mapToDomain(it) } } 31 | } 32 | 33 | override suspend fun sendMessage(params: DiscordMessageEntity): DiscordMessageEntity { 34 | return withContext(coroutineMainDispatcherProvider.io) { 35 | discordMessageDao.insert(entityMapper.mapToData(params)) 36 | params 37 | } 38 | } 39 | 40 | override suspend fun fetchUrlMetadata(url: String?): DiscordUrlMetaEntity? { 41 | val discordUrlMetaEntity = DiscordUrlMetaEntity() 42 | if (cacheUrlMap.containsKey(url)) return cacheUrlMap[url!!] 43 | 44 | withContext(coroutineMainDispatcherProvider.io) { 45 | val con = Jsoup.connect(url) 46 | val doc = con.userAgent("Mozilla").get() 47 | val ogTags = doc.select("meta[property^=og:]") 48 | when { 49 | ogTags.size > 0 -> 50 | ogTags.forEachIndexed { index, _ -> 51 | val tag = ogTags[index] 52 | when (tag.attr("property")) { 53 | "og:image" -> discordUrlMetaEntity.image = tag.attr("content") 54 | "og:description" -> discordUrlMetaEntity.desc = tag.attr("content") 55 | "og:url" -> discordUrlMetaEntity.url = tag.attr("content") 56 | "og:title" -> discordUrlMetaEntity.title = tag.attr("content") 57 | } 58 | } 59 | } 60 | cacheUrlMap[url!!] = discordUrlMetaEntity 61 | } 62 | return discordUrlMetaEntity 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/onboarding/commonui/OnboardingScreensButton.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.onboarding.commonui 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.material.Button 10 | import androidx.compose.material.ButtonDefaults 11 | import androidx.compose.material.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.res.colorResource 16 | import androidx.compose.ui.res.stringResource 17 | import androidx.compose.ui.text.font.FontWeight 18 | import androidx.compose.ui.tooling.preview.Preview 19 | import androidx.compose.ui.unit.dp 20 | import dev.baseio.discordjetpackcompose.R 21 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordJetpackComposeTheme 22 | import dev.baseio.discordjetpackcompose.ui.theme.onboarding_button_blue 23 | import dev.baseio.discordjetpackcompose.ui.theme.onboarding_button_grey 24 | import dev.baseio.discordjetpackcompose.ui.theme.white 25 | 26 | @Composable 27 | fun OnboardingScreensButton( 28 | modifier: Modifier = Modifier, 29 | buttonTextProvider: () -> Int, 30 | buttonBackgroundColor: Color = onboarding_button_blue, 31 | buttonWidthFraction: Float = 0.9f, 32 | onClick: () -> Unit 33 | ) { 34 | Button( 35 | onClick = { onClick() }, 36 | colors = ButtonDefaults.buttonColors( 37 | backgroundColor = buttonBackgroundColor, 38 | contentColor = colorResource(id = R.color.white) 39 | ), 40 | modifier = modifier 41 | .fillMaxWidth(buttonWidthFraction) 42 | ) { 43 | Text( 44 | text = stringResource(id = buttonTextProvider()), 45 | fontWeight = FontWeight.Bold, 46 | modifier = Modifier.padding(vertical = 4.dp) 47 | ) 48 | } 49 | } 50 | 51 | 52 | @Preview(showBackground = true, showSystemUi = true) 53 | @Composable 54 | private fun OnboardingScreensButtonPreview() { 55 | DiscordJetpackComposeTheme { 56 | Column( 57 | modifier = Modifier.background(white), 58 | verticalArrangement = Arrangement.Center, 59 | horizontalAlignment = Alignment.CenterHorizontally 60 | ) { 61 | OnboardingScreensButton( 62 | buttonTextProvider = { R.string.register }, 63 | onClick = { } 64 | ) 65 | OnboardingScreensButton( 66 | buttonTextProvider = { R.string.login }, 67 | buttonBackgroundColor = onboarding_button_grey, 68 | onClick = { } 69 | ) 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/notifications/components/MentionsSection.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.components 2 | 3 | import androidx.compose.material.Switch 4 | import androidx.compose.material.Text 5 | import androidx.compose.runtime.* 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.res.stringResource 8 | import androidx.compose.ui.text.AnnotatedString 9 | import androidx.compose.ui.text.SpanStyle 10 | import androidx.compose.ui.text.buildAnnotatedString 11 | import androidx.compose.ui.text.font.FontWeight 12 | import androidx.compose.ui.text.withStyle 13 | import dev.baseio.discordjetpackcompose.R 14 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.serverinfo.DiscordSwitchColors 15 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 16 | 17 | @Composable 18 | fun MentionsSection( 19 | isMute: Boolean 20 | ) { 21 | var suppressEveryoneMentions by remember { mutableStateOf(false) } 22 | var suppressRoleMentions by remember { mutableStateOf(false) } 23 | var suppressPush by remember { mutableStateOf(false) } 24 | 25 | SwitchItem( 26 | onClick = { 27 | suppressEveryoneMentions = !suppressEveryoneMentions 28 | }, 29 | title = buildAnnotatedString { 30 | append(stringResource(id = R.string.suppress)) 31 | withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)){ 32 | append(" "+stringResource(id = R.string.at_everyone)) 33 | } 34 | append(" "+stringResource(id = R.string.and)) 35 | withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)){ 36 | append(" "+stringResource(id = R.string.at_here)) 37 | } 38 | }, 39 | disabled = false, 40 | isChecked = suppressEveryoneMentions 41 | ) 42 | SwitchItem( 43 | onClick = { 44 | suppressRoleMentions = !suppressRoleMentions 45 | }, 46 | title = buildAnnotatedString { append(stringResource(id = R.string.role_mentions)) }, 47 | disabled = false, 48 | isChecked = suppressRoleMentions 49 | ) 50 | SwitchItem( 51 | onClick = { 52 | if (isMute) return@SwitchItem 53 | suppressPush = !suppressPush 54 | }, 55 | title = buildAnnotatedString { append(stringResource(id = R.string.mobile_push)) }, 56 | disabled = isMute, 57 | isChecked = suppressPush 58 | ) 59 | } 60 | 61 | @Composable 62 | fun SwitchItem( 63 | onClick: () -> Unit, 64 | title: AnnotatedString, 65 | disabled: Boolean, 66 | isChecked: Boolean 67 | ) { 68 | SectionItem( 69 | disabled = disabled, 70 | onClick = onClick, 71 | leadingComposable = { 72 | Text( 73 | text = title, 74 | color = DiscordColorProvider.colors.onSurface.copy(alpha = 0.8f) 75 | ) 76 | }, 77 | trailingComposable = { 78 | Switch( 79 | checked = isChecked, 80 | onCheckedChange = { onClick() }, 81 | colors = DiscordSwitchColors 82 | ) 83 | } 84 | ) 85 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettingsListItem.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.size 10 | import androidx.compose.material.Icon 11 | import androidx.compose.material.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.res.painterResource 17 | import androidx.compose.ui.text.style.TextAlign 18 | import androidx.compose.ui.unit.dp 19 | import dev.baseio.discordjetpackcompose.R 20 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.models.SettingsEntity 21 | import dev.baseio.discordjetpackcompose.ui.theme.DirectMessageListTypography 22 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider.colors 23 | import dev.baseio.discordjetpackcompose.ui.theme.discord_settings_icon 24 | import dev.baseio.discordjetpackcompose.ui.theme.user_settings_text 25 | import dev.baseio.discordjetpackcompose.ui.utils.clickableWithRipple 26 | 27 | @Composable 28 | fun UserSettingsListItem( 29 | settingsEntity: SettingsEntity, 30 | onItemClick: () -> Unit 31 | ) { 32 | Row( 33 | modifier = Modifier 34 | .fillMaxWidth() 35 | .background(colors.settingsBackground) 36 | .clickableWithRipple(onClick = onItemClick) 37 | .padding(top = 16.dp, bottom = 16.dp, end = 16.dp), 38 | verticalAlignment = Alignment.CenterVertically, 39 | horizontalArrangement = Arrangement.Start 40 | ) { 41 | Icon( 42 | painter = painterResource(id = settingsEntity.icon), contentDescription = null, 43 | modifier = Modifier.padding(start = 16.dp), 44 | tint = discord_settings_icon, 45 | ) 46 | Text( 47 | text = settingsEntity.title, style = DirectMessageListTypography.h6, 48 | maxLines = 1, 49 | color = user_settings_text, 50 | modifier = Modifier.padding(start = 24.dp) 51 | ) 52 | settingsEntity.currentStatus?.let { status -> 53 | Spacer(modifier = Modifier.weight(1f)) 54 | Icon( 55 | painter = painterResource(id = R.drawable.ic_baseline_circle_24), 56 | contentDescription = null, 57 | modifier = Modifier 58 | .size(14.dp) 59 | .align(Alignment.CenterVertically), 60 | tint = Color(0xFF3DA45C) 61 | ) 62 | Text( 63 | text = status, 64 | textAlign = TextAlign.End, 65 | modifier = Modifier 66 | .padding(start = 14.dp) 67 | .align(Alignment.CenterVertically) 68 | ) 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/components/OnlineIndicator.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.components 2 | 3 | import androidx.compose.animation.animateColorAsState 4 | import androidx.compose.foundation.Canvas 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.BoxScope 7 | import androidx.compose.foundation.layout.Column 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.layout.size 10 | import androidx.compose.foundation.shape.CircleShape 11 | import androidx.compose.material.Surface 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.Color 17 | import androidx.compose.ui.graphics.drawscope.Fill 18 | import androidx.compose.ui.graphics.drawscope.Stroke 19 | import androidx.compose.ui.tooling.preview.Preview 20 | import androidx.compose.ui.unit.Dp 21 | import androidx.compose.ui.unit.dp 22 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 23 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordJetpackComposeTheme 24 | 25 | @Composable 26 | private fun OnlineIndicator( 27 | modifier: Modifier = Modifier, 28 | isOnline: Boolean, 29 | indicatorSize: Dp = 12.dp, 30 | ) { 31 | val color by animateColorAsState(targetValue = if (isOnline) Color(0xFF3da45c) else Color(0xFF6f7c8a)) 32 | Surface(shape = CircleShape, color = DiscordColorProvider.colors.surface) { 33 | Canvas( 34 | modifier = modifier 35 | .padding(1.dp) 36 | .size(indicatorSize), 37 | onDraw = { 38 | drawCircle( 39 | color = color, 40 | radius = if (isOnline) size.width / 2.5f else size.width / 2.75f, 41 | style = if (isOnline) Fill else Stroke(width = size.width.times(0.2f)) 42 | ) 43 | }, 44 | ) 45 | } 46 | } 47 | 48 | @Composable 49 | fun OnlineIndicator( 50 | modifier: Modifier = Modifier, 51 | isOnline: Boolean, 52 | indicatorSize: Dp = 12.dp, 53 | indicatorAlignment: Alignment = Alignment.BottomEnd, 54 | content: @Composable BoxScope.() -> Unit = {}, 55 | ) { 56 | Box(contentAlignment = indicatorAlignment) { 57 | content() 58 | OnlineIndicator( 59 | modifier = modifier, 60 | isOnline = isOnline, 61 | indicatorSize = indicatorSize 62 | ) 63 | } 64 | } 65 | 66 | @Preview(showSystemUi = true) 67 | @Composable 68 | private fun OnlineIndicatorPreview() { 69 | DiscordJetpackComposeTheme { 70 | Column { 71 | OnlineIndicator(isOnline = true) 72 | OnlineIndicator(isOnline = true) { 73 | Surface( 74 | modifier = Modifier.size(48.dp), shape = CircleShape, color = Color(0xFF3da45c) 75 | ) {} 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/onboarding/screens/register/RegistrationTypeSelector.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.onboarding.screens.register 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.fillMaxHeight 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.shape.RoundedCornerShape 9 | import androidx.compose.material.Button 10 | import androidx.compose.material.ButtonDefaults 11 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 12 | import androidx.compose.material.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.res.stringResource 17 | import androidx.compose.ui.unit.dp 18 | import dev.baseio.discordjetpackcompose.ui.utils.Strings 19 | 20 | @Composable 21 | fun RegistrationTypeSelector( 22 | modifier: Modifier, 23 | selectedOption: RegistrationType, 24 | onSelectionChange: (RegistrationType) -> Unit 25 | ) { 26 | Row( 27 | modifier = modifier 28 | .height(32.dp) 29 | .background( 30 | color = DiscordColorProvider.colors.tabsBackgroundColor, 31 | shape = RoundedCornerShape(4.dp) 32 | ) 33 | .fillMaxWidth() 34 | ) { 35 | Button( 36 | modifier = Modifier 37 | .weight(1f) 38 | .fillMaxHeight(), 39 | colors = ButtonDefaults.buttonColors( 40 | backgroundColor = bgColor(selectedOption == RegistrationType.Phone), 41 | contentColor = contentColor(selectedOption == RegistrationType.Phone) 42 | ), 43 | onClick = { onSelectionChange(RegistrationType.Phone) }, 44 | ) { 45 | Text(stringResource(id = Strings.phone)) 46 | } 47 | 48 | Button( 49 | modifier = Modifier 50 | .weight(1f) 51 | .fillMaxHeight(), 52 | colors = ButtonDefaults.buttonColors( 53 | backgroundColor = bgColor(selected = selectedOption == RegistrationType.Email), 54 | contentColor = contentColor(selected = selectedOption == RegistrationType.Email) 55 | ), 56 | onClick = { onSelectionChange(RegistrationType.Email) }, 57 | ) { 58 | Text(stringResource(id = Strings.email)) 59 | } 60 | } 61 | } 62 | 63 | @Composable 64 | private fun contentColor(selected: Boolean) = 65 | if (selected) { 66 | Color(0xFFFFFFFF) 67 | } else { 68 | Color(0xFF55555d) 69 | } 70 | 71 | @Composable 72 | private fun bgColor(selected: Boolean) = 73 | if (selected) { 74 | DiscordColorProvider.colors.tabSelectedColor 75 | } else { 76 | DiscordColorProvider.colors.tabsBackgroundColor 77 | } 78 | 79 | sealed class RegistrationType { 80 | object Phone : RegistrationType() 81 | object Email : RegistrationType() 82 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/search/components/SearchSheetServerSlider.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.search.components 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.foundation.lazy.LazyRow 8 | import androidx.compose.foundation.shape.CircleShape 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.LaunchedEffect 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.draw.clip 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import androidx.hilt.navigation.compose.hiltViewModel 16 | import coil.compose.AsyncImage 17 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.components.CountIndicator 18 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordJetpackComposeTheme 19 | import dev.baseio.discordjetpackcompose.ui.utils.clickableWithRipple 20 | import dev.baseio.discordjetpackcompose.ui.utils.rememberCoilImageRequest 21 | import dev.baseio.discordjetpackcompose.viewmodels.DashboardScreenViewModel 22 | 23 | @Composable 24 | fun SearchSheetServerSlider( 25 | serverIdList: List, 26 | dashboardScreenVM: DashboardScreenViewModel = hiltViewModel(), 27 | onServerSelect: (String) -> Unit 28 | ) { 29 | val serverList = dashboardScreenVM.searchSheetServerList 30 | 31 | LaunchedEffect(serverIdList) { 32 | dashboardScreenVM.getServers(serverIds = serverIdList) 33 | } 34 | 35 | LazyRow( 36 | modifier = Modifier.padding(top = 16.dp), 37 | ) { 38 | item { Spacer(modifier = Modifier.padding(start = 8.dp)) } 39 | items(serverList.size) { index -> 40 | DiscordIndicator( 41 | modifier = Modifier.padding(start = 8.dp), 42 | indicatorPosition = DiscordIndicatorPosition.Bottom, 43 | isEnabled = serverList[index].allChannelsUnreadCount > 0 44 | ) { 45 | CountIndicator(count = serverList[index].allChannelsUnreadCount) { 46 | AsyncImage( 47 | model = rememberCoilImageRequest(data = serverList[index].thumbnailUri), 48 | contentDescription = null, 49 | modifier = Modifier 50 | .size(42.dp) 51 | .clip(CircleShape) 52 | .clickableWithRipple { onServerSelect(serverList[index].id) }, 53 | ) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | @Preview(showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_YES) 61 | @Preview(showSystemUi = true, uiMode = Configuration.UI_MODE_NIGHT_NO) 62 | @Composable 63 | private fun SearchSheetServerSliderPreview() { 64 | DiscordJetpackComposeTheme { 65 | SearchSheetServerSlider( 66 | serverIdList = listOf("1", "2", "3", "4", "5"), 67 | onServerSelect = {} 68 | ) 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/main/chatscreen/ChatScreenContent.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.chatscreen 2 | 3 | import androidx.compose.foundation.layout.BoxWithConstraints 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.imePadding 6 | import androidx.compose.foundation.layout.navigationBarsPadding 7 | import androidx.compose.foundation.layout.statusBarsPadding 8 | import androidx.compose.material.ExperimentalMaterialApi 9 | import androidx.compose.material.ModalBottomSheetState 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.MutableState 12 | import androidx.compose.runtime.State 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.layout.layoutId 15 | import androidx.compose.ui.unit.dp 16 | import androidx.constraintlayout.compose.ConstraintLayout 17 | import androidx.constraintlayout.compose.ConstraintSet 18 | import dev.baseio.discordjetpackcompose.viewmodels.ChatScreenViewModel 19 | 20 | @OptIn(ExperimentalMaterialApi::class) 21 | @Composable 22 | fun ChatScreenContent( 23 | modifier: Modifier = Modifier, 24 | viewModel: ChatScreenViewModel, 25 | sheetState: ModalBottomSheetState, 26 | isReplyMode: MutableState, 27 | userName: State 28 | ) { 29 | BoxWithConstraints( 30 | modifier = modifier 31 | .statusBarsPadding() 32 | .navigationBarsPadding() 33 | .imePadding() 34 | ) { 35 | val constraints = decoupledConstraints() 36 | ConstraintLayout(constraints) { 37 | ChatMessages( 38 | modifier = Modifier 39 | .layoutId("chatMessages") 40 | .fillMaxSize(), 41 | userName = userName, 42 | viewModel = viewModel, 43 | bottomSheetState = sheetState 44 | ) 45 | if (isReplyMode.value) { 46 | ReplyBanner( 47 | modifier = Modifier.layoutId("chatReplyBar"), 48 | userName = userName, 49 | onCloseClicked = { 50 | isReplyMode.value = false 51 | } 52 | ) 53 | } 54 | ChatMessageEditor( 55 | modifier = Modifier.layoutId("chatMessageEditor"), 56 | userName = userName, 57 | isReplyMode = isReplyMode, 58 | viewModel = viewModel 59 | ) 60 | } 61 | } 62 | } 63 | 64 | private fun decoupledConstraints(): ConstraintSet { 65 | return ConstraintSet { 66 | val chatMessages = createRefFor("chatMessages") 67 | val chatReplyBar = createRefFor("chatReplyBar") 68 | val chatMessageEditor = createRefFor("chatMessageEditor") 69 | 70 | constrain(chatMessages) { 71 | start.linkTo(parent.start) 72 | top.linkTo(parent.top) 73 | end.linkTo(parent.end) 74 | bottom.linkTo(chatMessageEditor.top, margin = 64.dp) 75 | } 76 | constrain(chatReplyBar) { 77 | start.linkTo(parent.start) 78 | end.linkTo(parent.end) 79 | bottom.linkTo(chatMessageEditor.top, margin = 0.dp) 80 | } 81 | constrain(chatMessageEditor) { 82 | start.linkTo(parent.start) 83 | end.linkTo(parent.end) 84 | bottom.linkTo(parent.bottom, margin = 8.dp) 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/notifications/components/NotificationFrequencySection.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.components 2 | 3 | import androidx.compose.material.* 4 | import androidx.compose.runtime.* 5 | import androidx.compose.ui.graphics.Color 6 | import androidx.compose.ui.res.stringResource 7 | import androidx.compose.ui.text.AnnotatedString 8 | import androidx.compose.ui.text.SpanStyle 9 | import androidx.compose.ui.text.buildAnnotatedString 10 | import androidx.compose.ui.text.font.FontWeight 11 | import androidx.compose.ui.text.withStyle 12 | import androidx.compose.ui.unit.dp 13 | import dev.baseio.discordjetpackcompose.R 14 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.models.FrequencyType 15 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 16 | 17 | @Composable 18 | fun NotificationFrequencySection( 19 | isMute: Boolean 20 | ) { 21 | var selectionState by remember { mutableStateOf(FrequencyType.ALL_MESSAGES) } 22 | 23 | RadioSelectionItem( 24 | title = AnnotatedString(stringResource(id = R.string.all_messages)), 25 | onClick = { 26 | if(isMute) return@RadioSelectionItem 27 | selectionState = FrequencyType.ALL_MESSAGES 28 | }, 29 | currentSelection = selectionState, 30 | selectionValue = FrequencyType.ALL_MESSAGES, 31 | disabled = isMute 32 | ) 33 | 34 | RadioSelectionItem( 35 | title = buildAnnotatedString { 36 | append(stringResource(id = R.string.only)) 37 | withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)){ 38 | append(" " + stringResource(id = R.string.at_mentions)) 39 | } 40 | }, 41 | onClick = { 42 | if(isMute) return@RadioSelectionItem 43 | selectionState = FrequencyType.MENTIONS 44 | }, 45 | currentSelection = selectionState, 46 | selectionValue = FrequencyType.MENTIONS, 47 | disabled = isMute 48 | ) 49 | 50 | RadioSelectionItem( 51 | title = AnnotatedString(stringResource(id = R.string.nothing)), 52 | onClick = { 53 | if(isMute) return@RadioSelectionItem 54 | selectionState = FrequencyType.NOTHING 55 | }, 56 | currentSelection = selectionState, 57 | selectionValue = FrequencyType.NOTHING, 58 | disabled = isMute 59 | ) 60 | 61 | } 62 | 63 | @Composable 64 | fun RadioSelectionItem( 65 | onClick: () -> Unit, 66 | title: AnnotatedString, 67 | currentSelection: FrequencyType, 68 | selectionValue: FrequencyType, 69 | disabled: Boolean 70 | ) { 71 | SectionItem( 72 | disabled = disabled, 73 | paddingVertical = 4.dp, 74 | onClick = onClick, 75 | leadingComposable = { 76 | Text( 77 | text = title, 78 | color = DiscordColorProvider.colors.onSurface.copy(alpha = 0.8f) 79 | ) 80 | }, 81 | trailingComposable = { 82 | RadioButton( 83 | selected = currentSelection == selectionValue, 84 | onClick = onClick, 85 | colors = DiscordRadioButtonColors 86 | ) 87 | } 88 | ) 89 | } 90 | 91 | val DiscordRadioButtonColors @Composable get() = RadioButtonDefaults.colors( 92 | selectedColor = DiscordColorProvider.colors.primary, 93 | unselectedColor = DiscordColorProvider.colors.onSurface.copy(alpha = 0.5f) 94 | ) 95 | 96 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/userSettings/UserSettingsList.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.lazy.LazyColumn 7 | import androidx.compose.foundation.lazy.rememberLazyListState 8 | import androidx.compose.material.TabRowDefaults.Divider 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.res.stringResource 13 | import androidx.compose.ui.unit.dp 14 | import com.google.accompanist.insets.navigationBarsPadding 15 | import dev.baseio.discordjetpackcompose.R.string 16 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.GetSettingsSubtitle 17 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.GetTopComponent 18 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.components.models.SettingsEntity 19 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider.colors 20 | 21 | @Composable 22 | fun UserSettingsList( 23 | userSettings: List, 24 | nitroSettings: List, 25 | appSettings: List, 26 | appInfo: List, 27 | profileImageUrl: String, 28 | username: String = "mutual", 29 | discordTag: String = "#8976" 30 | ) { 31 | val lazyListState = rememberLazyListState() 32 | 33 | LazyColumn(state = lazyListState, modifier = Modifier.background(colors.settingsBackground)) { 34 | item { 35 | GetTopComponent(lazyListState, profileImageUrl, username, discordTag) 36 | } 37 | 38 | item { 39 | GetSettingsSubtitle(title = stringResource(string.settings_user_settings)) 40 | } 41 | 42 | items(userSettings.size) { index -> 43 | UserSettingsListItem(settingsEntity = userSettings[index], 44 | onItemClick = {}) 45 | } 46 | 47 | item { 48 | Divider(color = Color.DarkGray, thickness = 1.dp, modifier = Modifier.padding(top = 8.dp)) 49 | GetSettingsSubtitle(title = stringResource(string.settings_nitro_settings)) 50 | } 51 | 52 | items(nitroSettings.size) { index -> 53 | UserSettingsListItem(settingsEntity = nitroSettings[index], 54 | onItemClick = {}) 55 | } 56 | 57 | item { 58 | Divider(color = Color.DarkGray, thickness = 1.dp, modifier = Modifier.padding(top = 8.dp)) 59 | GetSettingsSubtitle(title = stringResource(string.settings_app_settings)) 60 | } 61 | 62 | items(appSettings.size) { index -> 63 | UserSettingsListItem(settingsEntity = appSettings[index], 64 | onItemClick = {}) 65 | } 66 | 67 | item { 68 | Divider(color = Color.DarkGray, thickness = 1.dp, modifier = Modifier.padding(top = 8.dp)) 69 | GetSettingsSubtitle(title = stringResource(string.settings_app_info)) 70 | } 71 | 72 | items(appInfo.size) { index -> 73 | UserSettingsListItem(settingsEntity = appInfo[index], 74 | onItemClick = {}) 75 | } 76 | 77 | item { 78 | Spacer( 79 | modifier = Modifier 80 | .navigationBarsPadding() 81 | .padding(vertical = 32.dp) 82 | ) 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/utils/SampleData.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.utils 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.Tag 5 | import dev.baseio.discordjetpackcompose.entities.search.SearchFilter 6 | import dev.baseio.discordjetpackcompose.entities.search.SearchSheetListItemEntity 7 | import dev.baseio.discordjetpackcompose.entities.server.ChannelEntity 8 | import dev.baseio.discordjetpackcompose.entities.server.ChannelType 9 | import dev.baseio.discordjetpackcompose.entities.server.ServerEntity 10 | 11 | fun getSampleServer(serverId: String) = ServerEntity( 12 | id = serverId, 13 | name = "Test Server$serverId", 14 | posterUri = "https://images.pexels.com/photos/12641743/pexels-photo-12641743.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2", 15 | channels = listOf( 16 | ChannelEntity( 17 | id = "1", 18 | name = "announcements", 19 | type = ChannelType.PUBLIC, 20 | category = "INFO", 21 | unreadCount = 92, 22 | isUnread = true, 23 | ), 24 | ChannelEntity( 25 | id = "2", 26 | name = "youtube-feed", 27 | type = ChannelType.PUBLIC, 28 | category = "INFO", 29 | ), 30 | ChannelEntity( 31 | id = "3", 32 | name = "Total Members: 6.71K", 33 | type = ChannelType.PRIVATE, 34 | ), 35 | ChannelEntity( 36 | id = "4", 37 | name = "Online Members: 366", 38 | type = ChannelType.PRIVATE, 39 | ), 40 | ChannelEntity( 41 | id = "5", 42 | name = "android", 43 | type = ChannelType.PUBLIC, 44 | category = "PROGRAMMING", 45 | isUnread = true, 46 | ), 47 | ), 48 | hasNitroSubscription = serverId.toIntOrNull()?.rem(2) == 0, 49 | onlineMembersCount = 415, 50 | totalMembersCount = 1009, 51 | serverEmojiUris = mutableListOf().apply { 52 | repeat(16) { 53 | add("https://cdn.shopify.com/s/files/1/1061/1924/files/Hugging_Face_Emoji_2028ce8b-c213-4d45-94aa-21e1a0842b4d_large.png?15202324258887420558") 54 | } 55 | }, 56 | boostCount = 7, 57 | ) 58 | 59 | fun getSampleSheetListItems() = mutableListOf().apply { 60 | repeat(10) { index -> 61 | add( 62 | SearchSheetListItemEntity( 63 | id = "channel1$index", 64 | itemType = SearchFilter.TextChannels, 65 | iconUri = Icons.Default.Tag, 66 | title = "announcements$index", 67 | subtitle = "!INFO", 68 | serverName = "Coding in Flow", 69 | unreadCount = 92 70 | ) 71 | ) 72 | add( 73 | SearchSheetListItemEntity( 74 | id = "channel2$index", 75 | itemType = SearchFilter.Users, 76 | iconUri = Constants.MMLogoUrl, 77 | title = "YouTube$index", 78 | subtitle = null, 79 | serverName = "Youtube#1316", 80 | unreadCount = null 81 | ) 82 | ) 83 | } 84 | } 85 | 86 | fun getSampleServerList() = listOf( 87 | getSampleServer(serverId = "1"), 88 | getSampleServer(serverId = "2"), 89 | ) -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/serverinfo/components/ServerQuickActions.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.serverinfo.components 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material.ButtonDefaults 7 | import androidx.compose.material.Icon 8 | import androidx.compose.material.Text 9 | import androidx.compose.material.TextButton 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.res.painterResource 15 | import androidx.compose.ui.res.stringResource 16 | import androidx.compose.ui.text.font.FontWeight 17 | import androidx.compose.ui.tooling.preview.Preview 18 | import androidx.compose.ui.unit.dp 19 | import dev.baseio.discordjetpackcompose.R 20 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.serverinfo.components.models.ServerQuickAction 21 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 22 | import dev.baseio.discordjetpackcompose.ui.theme.ServerInfoTypography 23 | import dev.baseio.discordjetpackcompose.ui.theme.contentColorFor 24 | 25 | @Composable 26 | fun ServerQuickActions( 27 | modifier: Modifier = Modifier, actions: List 28 | ) { 29 | Row( 30 | modifier = modifier, verticalAlignment = Alignment.CenterVertically 31 | ) { 32 | actions.forEach { action -> 33 | TextButton(onClick = action.onClick, modifier = Modifier.weight(1f), colors = ButtonDefaults.textButtonColors( 34 | contentColor = DiscordColorProvider.colors.contentColorFor(DiscordColorProvider.colors.primary) 35 | )) { 36 | Column( 37 | horizontalAlignment = Alignment.CenterHorizontally 38 | ) { 39 | Icon( 40 | painter = painterResource(id = action.icon), 41 | contentDescription = null, 42 | tint = action.iconTint 43 | ?: DiscordColorProvider.colors.onSurface.copy(alpha = 0.5f) 44 | ) 45 | Text( 46 | text = action.label, 47 | modifier = Modifier.padding(vertical = 8.dp), 48 | style = ServerInfoTypography.caption.copy(fontWeight = FontWeight.Bold) 49 | ) 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | @Preview(showSystemUi = true) 57 | @Composable 58 | private fun ServerQuickActionsPreview() { 59 | ServerQuickActions( 60 | actions = listOf( 61 | ServerQuickAction( 62 | icon = R.drawable.ic_boost, 63 | iconTint = Color.Magenta, 64 | label = "3 Boosts", 65 | onClick = {}, 66 | ), 67 | ServerQuickAction( 68 | icon = R.drawable.ic_notifications, 69 | label = stringResource(R.string.server_info_notifications_btn_txt), 70 | onClick = {}, 71 | ), 72 | ServerQuickAction( 73 | icon = R.drawable.ic_invite, 74 | label = stringResource(R.string.server_info_invite_btn_txt), 75 | onClick = {}, 76 | ), 77 | ) 78 | ) 79 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/serverchannels/ServerChannelList.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard.serverchannels 2 | 3 | import androidx.compose.animation.AnimatedContent 4 | import androidx.compose.animation.ExperimentalAnimationApi 5 | import androidx.compose.animation.fadeIn 6 | import androidx.compose.animation.fadeOut 7 | import androidx.compose.animation.with 8 | import androidx.compose.foundation.layout.fillMaxHeight 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.material.Surface 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.LaunchedEffect 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.dp 16 | import androidx.hilt.navigation.compose.hiltViewModel 17 | import dev.baseio.discordjetpackcompose.entities.ChatUserEntity 18 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.channels.ChannelList 19 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.components.ServerIconSelector 20 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.directmessages.DirectMessageList 21 | import dev.baseio.discordjetpackcompose.ui.theme.DiscordColorProvider 22 | import dev.baseio.discordjetpackcompose.viewmodels.DashboardScreenViewModel 23 | 24 | @OptIn(ExperimentalAnimationApi::class) 25 | @Composable 26 | fun ServerChannelList( 27 | modifier: Modifier = Modifier, 28 | serverId: String, 29 | chatUserList: List, 30 | dashboardScreenVM: DashboardScreenViewModel = hiltViewModel(), 31 | openServerInfoBottomSheet: () -> Unit, 32 | onItemSelection: () -> Unit 33 | ) { 34 | val serverState = dashboardScreenVM.currentServerEntity 35 | 36 | Surface( 37 | modifier = modifier.fillMaxHeight(), 38 | elevation = 4.dp, 39 | color = DiscordColorProvider.colors.background, 40 | ) { 41 | AnimatedContent( 42 | targetState = serverId, 43 | transitionSpec = { fadeIn() with fadeOut() } 44 | ) { selectedScreenId -> 45 | when (selectedScreenId) { 46 | ServerIconSelector.DMScreenId -> DirectMessageList( 47 | modifier = Modifier.fillMaxSize(), 48 | openNewDMScreen = {} , // todo: Not implemented 49 | openSearchScreen = {}, 50 | onItemSelection = onItemSelection, 51 | chats = chatUserList, 52 | viewModel = dashboardScreenVM 53 | ) 54 | else -> ChannelList( 55 | modifier = Modifier.fillMaxSize(), 56 | serverState = serverState, 57 | onInviteButtonClick = { serverId -> }, // TODO: Not implemented 58 | openServerInfoBottomSheet = openServerInfoBottomSheet, 59 | onItemSelection = onItemSelection 60 | ) 61 | } 62 | } 63 | } 64 | } 65 | 66 | @Preview(showSystemUi = true) 67 | @Composable 68 | private fun ServerChannelListPreview() { 69 | val vm = hiltViewModel() 70 | LaunchedEffect(Unit) { 71 | vm.getServer("testId") 72 | } 73 | ServerChannelList( 74 | serverId = ServerIconSelector.DMScreenId, 75 | chatUserList = emptyList(), 76 | dashboardScreenVM = hiltViewModel(), 77 | openServerInfoBottomSheet = {} 78 | ) {} 79 | } -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/discordjetpackcompose/navigator/Navigator.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.navigator 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.Observer 5 | import androidx.navigation.NavController 6 | import androidx.navigation.NavOptionsBuilder 7 | import kotlinx.coroutines.* 8 | import kotlinx.coroutines.channels.Channel 9 | import kotlinx.coroutines.flow.* 10 | 11 | abstract class Navigator { 12 | val navigationCommands = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE) 13 | 14 | // We use a StateFlow here to allow ViewModels to start observing navigation results before the initial composition, 15 | // and still get the navigation result later 16 | val navControllerFlow = MutableStateFlow(null) 17 | 18 | fun navigateUp() { 19 | navigationCommands.tryEmit(NavigationCommand.NavigateUp) 20 | } 21 | 22 | } 23 | 24 | abstract class ComposeNavigator : Navigator() { 25 | abstract fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)? = null) 26 | abstract fun observeResult(key: String, route: String? = null): Flow 27 | abstract fun navigateBackWithResult(key: String, result: T, route: String?) 28 | 29 | abstract fun popUpTo(route: String, inclusive: Boolean) 30 | abstract fun navigateAndClearBackStack(route: String) 31 | 32 | suspend fun handleNavigationCommands(navController: NavController) { 33 | navigationCommands 34 | .onSubscription { this@ComposeNavigator.navControllerFlow.value = navController } 35 | .onCompletion { this@ComposeNavigator.navControllerFlow.value = null } 36 | .collect { navController.handleComposeNavigationCommand(it) } 37 | } 38 | 39 | private fun NavController.handleComposeNavigationCommand(navigationCommand: NavigationCommand) { 40 | when (navigationCommand) { 41 | is ComposeNavigationCommand.NavigateToRoute -> { 42 | navigate(navigationCommand.route, navigationCommand.options) 43 | } 44 | NavigationCommand.NavigateUp -> navigateUp() 45 | is ComposeNavigationCommand.PopUpToRoute -> popBackStack( 46 | navigationCommand.route, 47 | navigationCommand.inclusive 48 | ) 49 | is ComposeNavigationCommand.NavigateUpWithResult<*> -> { 50 | navUpWithResult(navigationCommand) 51 | } 52 | else -> { 53 | throw RuntimeException("can't handle this with ComposeNavigator") 54 | } 55 | } 56 | } 57 | 58 | private fun NavController.navUpWithResult(navigationCommand: ComposeNavigationCommand.NavigateUpWithResult<*>) { 59 | val backStackEntry = 60 | navigationCommand.route?.let { getBackStackEntry(it) } 61 | ?: previousBackStackEntry 62 | backStackEntry?.savedStateHandle?.set( 63 | navigationCommand.key, 64 | navigationCommand.result 65 | ) 66 | 67 | navigationCommand.route?.let { 68 | popBackStack(it, false) 69 | } ?: run { 70 | navigateUp() 71 | } 72 | } 73 | } 74 | 75 | 76 | @OptIn(DelicateCoroutinesApi::class) 77 | fun LiveData.asFlow(): Flow = flow { 78 | val channel = Channel(Channel.CONFLATED) 79 | val observer = Observer { 80 | channel.trySend(it) 81 | } 82 | withContext(Dispatchers.Main.immediate) { 83 | observeForever(observer) 84 | } 85 | try { 86 | for (value in channel) { 87 | emit(value) 88 | } 89 | } finally { 90 | GlobalScope.launch(Dispatchers.Main.immediate) { 91 | removeObserver(observer) 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /benchmark/src/main/java/dev/baseio/discordjetpackcompose/startup/StartupBenchmark.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.baseio.discordjetpackcompose.startup 18 | 19 | import androidx.benchmark.macro.BaselineProfileMode.Disable 20 | import androidx.benchmark.macro.BaselineProfileMode.Require 21 | import androidx.benchmark.macro.CompilationMode 22 | import androidx.benchmark.macro.StartupMode 23 | import androidx.benchmark.macro.StartupMode.COLD 24 | import androidx.benchmark.macro.StartupMode.HOT 25 | import androidx.benchmark.macro.StartupMode.WARM 26 | import androidx.benchmark.macro.StartupTimingMetric 27 | import androidx.benchmark.macro.junit4.MacrobenchmarkRule 28 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner 29 | import org.junit.Rule 30 | import org.junit.Test 31 | import org.junit.runner.RunWith 32 | 33 | /** 34 | * Run this benchmark from Studio to see startup measurements, and captured system traces 35 | * for investigating your app's performance from a cold state. 36 | */ 37 | @RunWith(AndroidJUnit4ClassRunner::class) 38 | class ColdStartupBenchmark : AbstractStartupBenchmark(COLD) 39 | 40 | /** 41 | * Run this benchmark from Studio to see startup measurements, and captured system traces 42 | * for investigating your app's performance from a warm state. 43 | */ 44 | @RunWith(AndroidJUnit4ClassRunner::class) 45 | class WarmStartupBenchmark : AbstractStartupBenchmark(WARM) 46 | 47 | /** 48 | * Run this benchmark from Studio to see startup measurements, and captured system traces 49 | * for investigating your app's performance from a hot state. 50 | */ 51 | @RunWith(AndroidJUnit4ClassRunner::class) 52 | class HotStartupBenchmark : AbstractStartupBenchmark(HOT) 53 | 54 | /** 55 | * Base class for benchmarks with different startup modes. 56 | * Enables app startups from various states of baseline profile or [CompilationMode]s. 57 | */ 58 | abstract class AbstractStartupBenchmark(private val startupMode: StartupMode) { 59 | @get:Rule 60 | val benchmarkRule = MacrobenchmarkRule() 61 | 62 | @Test 63 | fun startupNoCompilation() = startup(CompilationMode.None()) 64 | 65 | @Test 66 | fun startupBaselineProfileDisabled() = startup( 67 | CompilationMode.Partial(baselineProfileMode = Disable, warmupIterations = 1) 68 | ) 69 | 70 | @Test 71 | fun startupBaselineProfile() = startup(CompilationMode.Partial(baselineProfileMode = Require)) 72 | 73 | @Test 74 | fun startupFullCompilation() = startup(CompilationMode.Full()) 75 | 76 | private fun startup(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( 77 | packageName = "dev.baseio.discordjetpackcompose", 78 | metrics = listOf(StartupTimingMetric()), 79 | compilationMode = compilationMode, 80 | iterations = 10, 81 | startupMode = startupMode, 82 | setupBlock = { 83 | pressHome() 84 | } 85 | ) { 86 | startActivityAndWait() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/discordjetpackcompose/ui/routes/dashboard/DashboardRoute.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.ui.routes.dashboard 2 | 3 | import androidx.compose.material.ExperimentalMaterialApi 4 | import androidx.compose.material.ModalBottomSheetState 5 | import androidx.navigation.NavGraphBuilder 6 | import androidx.navigation.compose.composable 7 | import androidx.navigation.navigation 8 | import dev.baseio.discordjetpackcompose.entities.server.ServerEntity 9 | import dev.baseio.discordjetpackcompose.navigator.ComposeNavigator 10 | import dev.baseio.discordjetpackcompose.navigator.DiscordRoute 11 | import dev.baseio.discordjetpackcompose.navigator.DiscordScreen 12 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.createServer.CreateServer 13 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.friends.FriendsScreen 14 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.invite.InviteScreen 15 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.HomeScreen 16 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.main.dasboard.DashboardScreen 17 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.notifications.NotificationScreen 18 | import dev.baseio.discordjetpackcompose.ui.routes.dashboard.userSettings.UserSettings 19 | 20 | fun NavGraphBuilder.dashboardRoute( 21 | composeNavigator: ComposeNavigator, 22 | ) { 23 | navigation( 24 | startDestination = DiscordScreen.Dashboard.name, 25 | route = DiscordRoute.Dashboard.name 26 | ) { 27 | composable(DiscordScreen.Dashboard.name) { 28 | DashboardScreen(composeNavigator = composeNavigator) 29 | } 30 | composable(DiscordScreen.Friends.name) { 31 | FriendsScreen(composeNavigator = composeNavigator) 32 | } 33 | composable(DiscordScreen.Invite.name) { 34 | InviteScreen(composeNavigator = composeNavigator) 35 | } 36 | composable(DiscordScreen.CreateServer.name) { 37 | CreateServer(composeNavigator) 38 | } 39 | composable(DiscordScreen.NotificationSettings.name) { 40 | NotificationScreen(composeNavigator = composeNavigator) 41 | } 42 | composable(DiscordScreen.UserSettings.name) { 43 | UserSettings(composeNavigator = composeNavigator) 44 | } 45 | } 46 | 47 | } 48 | 49 | @OptIn(ExperimentalMaterialApi::class) 50 | fun NavGraphBuilder.setupDashboardBottomNavScreens( 51 | composeNavigator: ComposeNavigator, 52 | sheetState: ModalBottomSheetState, 53 | onSelectServer: (String) -> Unit, 54 | shouldDisplayBottomBar: (Boolean) -> Unit, 55 | serverList: List // todo: refer a single source of truth for this serverlist without passing it around 56 | ) { 57 | navigation( 58 | startDestination = DiscordScreen.Home.route, 59 | route = DiscordRoute.DashboardBottomNav.name 60 | ) { 61 | composable(DiscordScreen.Home.route) { 62 | HomeScreen( 63 | composeNavigator = composeNavigator, 64 | serverList = serverList, 65 | onSelectServer = onSelectServer, 66 | sheetState = sheetState, 67 | shouldDisplayBottomBar = shouldDisplayBottomBar 68 | ) 69 | } 70 | composable(DiscordScreen.Friends.route) { 71 | FriendsScreen(composeNavigator = composeNavigator) 72 | } 73 | composable(DiscordScreen.UserSettings.name) { 74 | UserSettings(composeNavigator = composeNavigator) 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/discordjetpackcompose/repositories/ServerRepoImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.discordjetpackcompose.repositories 2 | 3 | import dev.baseio.discordjetpackcompose.di.dispatcher.CoroutineDispatcherProvider 4 | import dev.baseio.discordjetpackcompose.entities.NetworkState 5 | import dev.baseio.discordjetpackcompose.entities.search.SearchSheetListItemEntity 6 | import dev.baseio.discordjetpackcompose.entities.server.ChannelEntity 7 | import dev.baseio.discordjetpackcompose.entities.server.ChannelType 8 | import dev.baseio.discordjetpackcompose.entities.server.ServerEntity 9 | import dev.baseio.discordjetpackcompose.utils.getSampleServerList 10 | import dev.baseio.discordjetpackcompose.utils.getSampleSheetListItems 11 | import dev.baseio.discordjetpackcompose.utils.safeApiCall 12 | 13 | class ServerRepoImpl(private val coroutineDispatcherProvider: CoroutineDispatcherProvider) : 14 | ServerRepo { 15 | override suspend fun getServer(serverId: String): NetworkState { 16 | return safeApiCall( 17 | coroutineDispatcherProvider = coroutineDispatcherProvider, 18 | debugResponse = ServerEntity( 19 | id = serverId, 20 | name = "Test Server", 21 | posterUri = "https://images.pexels.com/photos/12641743/pexels-photo-12641743.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2", 22 | channels = listOf( 23 | ChannelEntity( 24 | id = "1", 25 | name = "announcements", 26 | type = ChannelType.PUBLIC, 27 | category = "INFO", 28 | unreadCount = 92, 29 | isUnread = true, 30 | ), 31 | ChannelEntity( 32 | id = "2", 33 | name = "youtube-feed", 34 | type = ChannelType.PUBLIC, 35 | category = "INFO", 36 | ), 37 | ChannelEntity( 38 | id = "3", 39 | name = "Total Members: 6.71K", 40 | type = ChannelType.PRIVATE, 41 | ), 42 | ChannelEntity( 43 | id = "4", 44 | name = "Online Members: 366", 45 | type = ChannelType.PRIVATE, 46 | ), 47 | ChannelEntity( 48 | id = "5", 49 | name = "android", 50 | type = ChannelType.PUBLIC, 51 | category = "PROGRAMMING", 52 | isUnread = true, 53 | ), 54 | ), 55 | hasNitroSubscription = true 56 | ) 57 | ) { 58 | TODO("Not Implemented") 59 | } 60 | } 61 | 62 | override suspend fun getServerList(): NetworkState> { 63 | return safeApiCall( 64 | coroutineDispatcherProvider = coroutineDispatcherProvider, 65 | debugResponse = getSampleServerList() 66 | ) { 67 | TODO("Not Implemented") 68 | } 69 | } 70 | 71 | override suspend fun getSearchSheetItemList(): NetworkState> { 72 | return safeApiCall( 73 | coroutineDispatcherProvider = coroutineDispatcherProvider, 74 | debugResponse = getSampleSheetListItems() 75 | ) { 76 | TODO("Not Implemented") 77 | } 78 | } 79 | } --------------------------------------------------------------------------------