├── .github ├── PULL_REQUEST_TEMPLATE.md ├── codeowners └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── fabric.properties ├── keystore │ ├── praxis-debug.jks │ └── praxis-release.jks ├── proguard-common.txt ├── proguard-rules.pro ├── proguard-specific.txt └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── baseio │ │ │ └── slackclone │ │ │ ├── PraxisApp.kt │ │ │ ├── di │ │ │ └── NavigationModule.kt │ │ │ └── root │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable │ │ ├── ic_circle.xml │ │ ├── slack.png │ │ └── splash_image.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── splash_theme.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── resources │ └── responses │ └── jokes_response.json ├── art ├── animated_start.gif ├── architecture.jpeg ├── art1.png ├── art10.png ├── art11.png ├── art2.png ├── art3.png ├── art4.png ├── art5.png ├── art6.png ├── art8.gif ├── art9.png ├── art_pref1.png ├── art_pref2.png ├── art_pref3.png ├── art_pref4.png ├── clean-architecture.jpg └── gesture_vertical_drag_chatbox.gif ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── Dependencies.kt │ └── ProjectProperties.kt ├── common ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── common │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── baseio │ │ │ └── slackclone │ │ │ └── common │ │ │ ├── extensions │ │ │ ├── PrimitiveExtensions.kt │ │ │ ├── ReactiveExtensions.kt │ │ │ └── ViewExtensions.kt │ │ │ └── injection │ │ │ ├── DispatcherModule.kt │ │ │ └── dispatcher │ │ │ ├── CoroutineDispatcherProvider.kt │ │ │ └── RealCoroutineDispatcherProvider.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── common │ └── ExampleUnitTest.kt ├── commonui ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── commonui │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── baseio │ │ │ └── slackclone │ │ │ └── commonui │ │ │ ├── keyboard │ │ │ └── Keyboard.kt │ │ │ ├── material │ │ │ ├── DefaultSnackbar.kt │ │ │ └── SlackSurfaceAppBar.kt │ │ │ ├── reusable │ │ │ ├── SlackDragComposableView.kt │ │ │ ├── SlackImageBox.kt │ │ │ └── SlackListItem.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── PraxisSurface.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── anim │ │ ├── slide_left_in.xml │ │ ├── slide_left_out.xml │ │ ├── slide_right_in.xml │ │ └── slide_right_out.xml │ │ ├── drawable │ │ ├── ic_email.xml │ │ └── ic_eye.xml │ │ ├── font │ │ ├── lato_bold.ttf │ │ ├── lato_light.ttf │ │ └── lato_regular.ttf │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── integer.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── commonui │ └── ExampleUnitTest.kt ├── data ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── data │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── data │ │ ├── injection │ │ ├── DataMappersModule.kt │ │ ├── DataModule.kt │ │ ├── RepositoryModule.kt │ │ ├── SourcesModule.kt │ │ └── UseCaseModule.kt │ │ ├── local │ │ ├── SlackDatabase.kt │ │ ├── dao │ │ │ ├── SlackChannelDao.kt │ │ │ ├── SlackMessageDao.kt │ │ │ └── SlackUserDao.kt │ │ └── model │ │ │ ├── DBSlackChannel.kt │ │ │ ├── DBSlackMessage.kt │ │ │ ├── DBSlackUser.kt │ │ │ └── SlackPreferences.kt │ │ ├── mapper │ │ ├── EntityMapper.kt │ │ ├── SlackChannelMapper.kt │ │ ├── SlackMessageMapper.kt │ │ ├── SlackUserChannelMapper.kt │ │ └── SlackUserMapper.kt │ │ └── repository │ │ ├── SlackChannelLastMessageRepository.kt │ │ ├── SlackChannelsRepositoryImpl.kt │ │ ├── SlackMessagesRepositoryImpl.kt │ │ └── SlackUserRepository.kt │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── data │ └── ExampleUnitTest.kt ├── domain ├── .gitignore ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── domain │ │ ├── SafeResult.kt │ │ ├── mappers │ │ └── DomainModel.kt │ │ ├── model │ │ ├── channel │ │ │ └── SlackChannel.kt │ │ ├── message │ │ │ └── SlackMessage.kt │ │ └── users │ │ │ └── SlackUser.kt │ │ ├── repository │ │ ├── ChannelLastMessageRepository.kt │ │ ├── ChannelsRepository.kt │ │ ├── MessagesRepository.kt │ │ └── UsersRepository.kt │ │ └── usecases │ │ ├── BaseUseCase.kt │ │ ├── channels │ │ ├── UseCaseCreateChannel.kt │ │ ├── UseCaseCreateChannels.kt │ │ ├── UseCaseFetchChannelCount.kt │ │ ├── UseCaseFetchChannels.kt │ │ ├── UseCaseFetchChannelsWithLastMessage.kt │ │ ├── UseCaseFetchUsers.kt │ │ ├── UseCaseGetChannel.kt │ │ └── UseCaseSearchChannel.kt │ │ └── chat │ │ ├── UseCaseFetchMessages.kt │ │ └── UseCaseSendMessage.kt │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── domain │ └── ExampleUnitTest.kt ├── feat-channels ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── uichannels │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── baseio │ │ │ └── slackclone │ │ │ └── uichannels │ │ │ ├── SlackChannelVM.kt │ │ │ ├── createsearch │ │ │ ├── CreateChannelVM.kt │ │ │ ├── CreateNewChannelUI.kt │ │ │ ├── SearchChannelsVM.kt │ │ │ └── SearchCreateChannelsScreen.kt │ │ │ ├── directmessages │ │ │ ├── DMChannelsList.kt │ │ │ └── DMessageViewModel.kt │ │ │ └── views │ │ │ ├── SKExpandCollapseColumn.kt │ │ │ ├── SlackAllChannels.kt │ │ │ ├── SlackConnections.kt │ │ │ ├── SlackDirectMessages.kt │ │ │ ├── SlackRecentChannels.kt │ │ │ └── SlackStarredChannels.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── uichannels │ └── ExampleUnitTest.kt ├── feat-chat ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── uichat │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── baseio │ │ │ └── slackclone │ │ │ └── uichat │ │ │ ├── chatthread │ │ │ ├── ChatScreenUI.kt │ │ │ ├── ChatScreenVM.kt │ │ │ └── composables │ │ │ │ ├── ChatMessage.kt │ │ │ │ ├── ChatMessageBox.kt │ │ │ │ ├── ChatMessagesUI.kt │ │ │ │ └── ChatScreenContent.kt │ │ │ └── newchat │ │ │ ├── NewChatThreadScreen.kt │ │ │ └── NewChatThreadVM.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── uichat │ └── ExampleUnitTest.kt ├── feat-chatcore ├── .gitignore ├── build.gradle.kts └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── chatcore │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── chatcore │ │ ├── ChannelUIModelMapper.kt │ │ ├── data │ │ ├── ChatPresentation.kt │ │ └── ExpandCollapseModel.kt │ │ ├── injection │ │ ├── UiModelMapperModule.kt │ │ └── UserChannelUiMapper.kt │ │ └── views │ │ └── SlackChannelItem.kt │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── chatcore │ └── ExampleUnitTest.kt ├── feat-onboarding ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── uionboarding │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── baseio │ │ │ └── slackclone │ │ │ └── uionboarding │ │ │ ├── compose │ │ │ ├── CommonInputUI.kt │ │ │ ├── EmailInputView.kt │ │ │ ├── GettingStarted.kt │ │ │ ├── ScreenInputUI.kt │ │ │ ├── SkipTypingScreen.kt │ │ │ ├── SlackAnimation.kt │ │ │ └── WorkspaceInputView.kt │ │ │ └── nav │ │ │ └── OnboardingNavigation.kt │ └── res │ │ ├── drawable-hdpi │ │ └── gettingstarted.png │ │ ├── drawable-xxhdpi │ │ └── gettingstarted.png │ │ ├── drawable-xxxhdpi │ │ └── gettingstarted.png │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── uionboarding │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── navigator ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── navigator │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── navigator │ │ ├── ComposeNavigator.kt │ │ ├── NavigationCommand.kt │ │ ├── NavigationKeys.kt │ │ ├── Navigator.kt │ │ └── Screens.kt │ └── test │ └── java │ └── dev │ └── baseio │ └── slackclone │ └── navigator │ └── ExampleUnitTest.kt ├── settings.gradle.kts ├── team-props ├── git-hooks.gradle.kts └── git-hooks │ └── pre-commit.sh └── ui-dashboard ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── androidTest └── java │ └── dev │ └── baseio │ └── slackclone │ └── uidashboard │ └── ExampleInstrumentedTest.kt ├── main ├── AndroidManifest.xml ├── java │ └── dev │ │ └── baseio │ │ └── slackclone │ │ └── uidashboard │ │ ├── compose │ │ ├── DashboardUI.kt │ │ ├── DashboardVM.kt │ │ └── SideNavigation.kt │ │ ├── home │ │ ├── DirectMessagesUI.kt │ │ ├── HomeScreenUI.kt │ │ ├── MentionsReactionsUI.kt │ │ ├── SearchMessagesUI.kt │ │ ├── UserProfileUI.kt │ │ ├── preferences │ │ │ ├── PreferencesAppBar.kt │ │ │ ├── PreferencesUI.kt │ │ │ ├── PreferencesVM.kt │ │ │ ├── prefitems │ │ │ │ ├── ItemTypePopUp.kt │ │ │ │ └── ItemWithSlider.kt │ │ │ └── uicomponents │ │ │ │ ├── ComponentStates.kt │ │ │ │ ├── DoubleOptionDialog.kt │ │ │ │ ├── EmojiSkinColorDialog.kt │ │ │ │ ├── PickerDialogWithRadioButton.kt │ │ │ │ └── PopUpItemStates.kt │ │ └── search │ │ │ └── SearchCancel.kt │ │ └── nav │ │ └── dashboardNavigation.kt └── res │ ├── drawable │ ├── ic_baseline_close_24.xml │ ├── ic_baseline_front_hand_24.xml │ ├── ic_outline_broken_image_24.xml │ ├── ic_outline_call_24.xml │ ├── ic_outline_feed_24.xml │ ├── ic_outline_feedback_24.xml │ ├── ic_outline_help_outline_24.xml │ ├── ic_outline_hourglass_bottom_24.xml │ ├── ic_outline_image_24.xml │ ├── ic_outline_info_24.xml │ ├── ic_outline_keyboard_alt_24.xml │ ├── ic_outline_language_24.xml │ └── ic_outline_speed_24.xml │ └── values │ └── strings.xml └── test └── java └── dev └── baseio └── slackclone └── uidashboard └── ExampleUnitTest.kt /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Purpose 2 | - [ ] Feature 3 | - [ ] Bug Fix 4 | - [ ] Release 5 | - [ ] Other 6 | 7 | #### Associated Tickets 8 | - [Feature Name](https://dealerware.atlassian.net/browse/RTA-{ticket-number}) 9 | 10 | #### Details 11 | - This is a thing that does stuff for widgets. 12 | 13 | #### Steps to Test 14 | 1. Enumerate steps to test new functionality 15 | 16 | #### Screenshots 17 | * Include screenshots if PR includes UI/UX changes 18 | 19 | #### Checklist 20 | - [ ] Tests -------------------------------------------------------------------------------- /.github/codeowners: -------------------------------------------------------------------------------- 1 | # Setting default codeowners to default as reviewers 2 | * @anmol92verma @aditya-bhawsar @pushpalroy @shubhamsinghshubham777 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/fabric.properties: -------------------------------------------------------------------------------- 1 | #Contains API Secret used to validate your application. Commit to internal source control; avoid making secret public. 2 | #Wed Mar 08 18:34:26 IST 2017 3 | apiSecret=9cb50ff88ceece358871f08a6424267dd88f84ca25ecce34d12d8c1d9ff12367 4 | -------------------------------------------------------------------------------- /app/keystore/praxis-debug.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/app/keystore/praxis-debug.jks -------------------------------------------------------------------------------- /app/keystore/praxis-release.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/app/keystore/praxis-release.jks -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/Development/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -keep class com.github.vatbub.** { *; } 19 | -keepclassmembers class com.github.vatbub.** { *; } 20 | -------------------------------------------------------------------------------- /app/proguard-specific.txt: -------------------------------------------------------------------------------- 1 | #MM Proguard Settings pertaining to this project 2 | # this is an extension to the recommended settings for android 3 | # provide in proguard-android.pro 4 | # 5 | # It is also an extention to the proguard configuration of SlackClone 6 | # 7 | # Add proguard directives to this file if this project requires additional 8 | # configuration 9 | 10 | -keepnames !abstract class com.customername.android.injection.* 11 | 12 | #Keeping the members of that have static vars 13 | -keepclassmembers public class com.customername.android.** { 14 | public static * ; 15 | public *; 16 | } 17 | 18 | # Below will be classes you want to explicity keep AND obfuscate - you shouldn't need to do this unless your class is only referenced at runtime and not compile time (IE injected via annotation or reflection) 19 | #-keep,allowobfuscation class com.customername.android.** { *; } 20 | 21 | #Things you don't want to obfuscate and you don't want to be shrunk usually GSON pojos. Add your domain/JSON below here 22 | -keep class com.customername.android.model.** { *; } 23 | 24 | -dontwarn okio.** 25 | -dontwarn org.simpleframework.** 26 | -keep class com.google.common.** { *; } 27 | 28 | 29 | #Rxjava 30 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { 31 | long producerIndex; 32 | long consumerIndex; 33 | } 34 | 35 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { 36 | rx.internal.util.atomic.LinkedQueueNode producerNode; 37 | } 38 | 39 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { 40 | rx.internal.util.atomic.LinkedQueueNode consumerNode; 41 | } 42 | 43 | 44 | # Retrofit2 45 | -dontwarn retrofit2.** 46 | -keep class retrofit2.** { *; } 47 | -keepattributes Signature 48 | -keepattributes Exceptions 49 | -keepattributes Annotation 50 | 51 | -dontwarn android.databinding.** 52 | -keep class android.databinding.** { *; } 53 | -dontwarn com.google.errorprone.annotations.** 54 | 55 | 56 | -keep class okhttp3.** { *; } 57 | -keep interface okhttp3.** { *; } 58 | -dontwarn okhttp3.** 59 | -dontwarn okio.** 60 | -dontwarn javax.annotation** 61 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 17 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/slackclone/PraxisApp.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | import timber.log.Timber 6 | 7 | @HiltAndroidApp 8 | class SlackCloneApp : Application() { 9 | 10 | override fun onCreate() { 11 | super.onCreate() 12 | 13 | if (BuildConfig.DEBUG) { 14 | Timber.plant(Timber.DebugTree()) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/slackclone/di/NavigationModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.di 2 | 3 | import dev.baseio.slackclone.navigator.ComposeNavigator 4 | import dev.baseio.slackclone.navigator.composenavigator.SlackCloneComposeNavigator 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 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: SlackCloneComposeNavigator): ComposeNavigator 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/dev/baseio/slackclone/root/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.root 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.compose.runtime.LaunchedEffect 7 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 8 | import androidx.core.view.WindowCompat 9 | import androidx.navigation.compose.NavHost 10 | import androidx.navigation.compose.rememberNavController 11 | import dagger.hilt.android.AndroidEntryPoint 12 | import dev.baseio.slackclone.R 13 | import dev.baseio.slackclone.navigator.ComposeNavigator 14 | import dev.baseio.slackclone.navigator.SlackRoute 15 | import dev.baseio.slackclone.uidashboard.nav.dashboardNavigation 16 | import dev.baseio.slackclone.uionboarding.nav.onboardingNavigation 17 | import javax.inject.Inject 18 | 19 | @AndroidEntryPoint 20 | class MainActivity : AppCompatActivity() { 21 | 22 | @Inject 23 | lateinit var composeNavigator: ComposeNavigator 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | setTheme(R.style.Theme_SlackJetpackCompose) 27 | super.onCreate(savedInstanceState) 28 | WindowCompat.setDecorFitsSystemWindows(window, false) 29 | 30 | setContent { 31 | val navController = rememberNavController() 32 | 33 | LaunchedEffect(Unit) { 34 | composeNavigator.handleNavigationCommands(navController) 35 | } 36 | 37 | NavHost( 38 | navController = navController, 39 | startDestination = SlackRoute.OnBoarding.name, 40 | ) { 41 | onboardingNavigation( 42 | composeNavigator = composeNavigator, 43 | ) 44 | dashboardNavigation( 45 | composeNavigator = composeNavigator 46 | ) 47 | } 48 | 49 | 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_circle.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/app/src/main/res/drawable/slack.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/splash_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MainActivity 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /app/src/test/resources/responses/jokes_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "success", 3 | "value": [ 4 | { 5 | "id": 427, 6 | "joke": "Chuck Norris' favorite cereal is Kellogg's Nails 'N' Gravel.", 7 | "categories": [] 8 | }, 9 | { 10 | "id": 75, 11 | "joke": "Chuck Norris can believe it's not butter.", 12 | "categories": [] 13 | }, 14 | { 15 | "id": 302, 16 | "joke": "Chuck Norris doesn't go on the internet, he has every internet site stored in his memory. He refreshes webpages by blinking.", 17 | "categories": [] 18 | }, 19 | { 20 | "id": 275, 21 | "joke": "Little Miss Muffet sat on her tuffet, until Chuck Norris roundhouse kicked her into a glacier.", 22 | "categories": [] 23 | }, 24 | { 25 | "id": 76, 26 | "joke": "If tapped, a Chuck Norris roundhouse kick could power the country of Australia for 44 minutes.", 27 | "categories": [] 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /art/animated_start.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/animated_start.gif -------------------------------------------------------------------------------- /art/architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/architecture.jpeg -------------------------------------------------------------------------------- /art/art1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art1.png -------------------------------------------------------------------------------- /art/art10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art10.png -------------------------------------------------------------------------------- /art/art11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art11.png -------------------------------------------------------------------------------- /art/art2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art2.png -------------------------------------------------------------------------------- /art/art3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art3.png -------------------------------------------------------------------------------- /art/art4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art4.png -------------------------------------------------------------------------------- /art/art5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art5.png -------------------------------------------------------------------------------- /art/art6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art6.png -------------------------------------------------------------------------------- /art/art8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art8.gif -------------------------------------------------------------------------------- /art/art9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art9.png -------------------------------------------------------------------------------- /art/art_pref1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art_pref1.png -------------------------------------------------------------------------------- /art/art_pref2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art_pref2.png -------------------------------------------------------------------------------- /art/art_pref3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art_pref3.png -------------------------------------------------------------------------------- /art/art_pref4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/art_pref4.png -------------------------------------------------------------------------------- /art/clean-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/clean-architecture.jpg -------------------------------------------------------------------------------- /art/gesture_vertical_drag_chatbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/art/gesture_vertical_drag_chatbox.gif -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | maven("https://plugins.gradle.org/m2/") 7 | } 8 | dependencies { 9 | classpath(BuildPlugins.TOOLS_BUILD_GRADLE) 10 | classpath(BuildPlugins.DAGGER_HILT_PLUGIN) 11 | classpath(BuildPlugins.KOTLIN_GRADLE_PLUGIN) 12 | classpath(kotlin("serialization", version = Lib.Kotlin.KOTLIN_VERSION)) 13 | classpath(BuildPlugins.KTLINT_GRADLE_PLUGIN) 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | 23 | tasks.withType().all { 24 | kotlinOptions.freeCompilerArgs += listOf( "-Xopt-in=kotlin.RequiresOptIn", 25 | "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") 26 | kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() 27 | } 28 | } 29 | 30 | tasks.register("clean") 31 | .configure { 32 | delete(rootProject.buildDir) 33 | } 34 | 35 | apply(from = teamPropsFile("git-hooks.gradle.kts")) 36 | 37 | fun teamPropsFile(propsFile: String): File { 38 | val teamPropsDir = file("team-props") 39 | return File(teamPropsDir, propsFile) 40 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | jcenter() 3 | } 4 | 5 | plugins { 6 | `kotlin-dsl` 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/ProjectProperties.kt: -------------------------------------------------------------------------------- 1 | object ProjectProperties { 2 | const val COMPILE_SDK = 32 3 | const val MIN_SDK = 21 4 | const val TARGET_SDK = 32 5 | const val APPLICATION_ID = "dev.baseio.slackclone" 6 | } -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/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 | /*Kotlin*/ 33 | api(Lib.Kotlin.KT_STD) 34 | implementation(Lib.Async.COROUTINES) 35 | implementation(Lib.Async.COROUTINES_ANDROID) 36 | /* Dependency Injection */ 37 | api(Lib.Di.hiltAndroid) 38 | kapt(Lib.Di.hiltCompiler) 39 | kapt(Lib.Di.hiltAndroidCompiler) 40 | } -------------------------------------------------------------------------------- /common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/common/consumer-rules.pro -------------------------------------------------------------------------------- /common/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 -------------------------------------------------------------------------------- /common/src/androidTest/java/dev/baseio/slackclone/common/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.common 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.slackclone.common.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /common/src/main/java/dev/baseio/slackclone/common/extensions/PrimitiveExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.common.extensions 2 | 3 | import android.annotation.SuppressLint 4 | import java.math.RoundingMode 5 | import java.text.SimpleDateFormat 6 | import java.util.* 7 | 8 | 9 | fun String?.isValid(): Boolean { 10 | return !this.isNullOrEmpty() 11 | } 12 | 13 | fun Int.divide(divideBy: Double, decimals: Int = 2): String { 14 | return (this / divideBy).toBigDecimal().setScale(decimals, RoundingMode.UP).toPlainString() 15 | } 16 | 17 | 18 | fun Long.calendar(): Calendar { 19 | return Calendar.getInstance().apply { 20 | this.timeInMillis = this@calendar 21 | } 22 | } 23 | 24 | @SuppressLint("SimpleDateFormat") 25 | fun Calendar.formattedMonthDate(): String { 26 | return SimpleDateFormat("MMM dd").format(this.time) 27 | } 28 | 29 | @SuppressLint("SimpleDateFormat") 30 | fun Calendar.formattedTime(): String { 31 | return SimpleDateFormat("hh:mm a").format(this.time) 32 | } -------------------------------------------------------------------------------- /common/src/main/java/dev/baseio/slackclone/common/extensions/ReactiveExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.common.extensions 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | 5 | fun MutableLiveData.show() = this.postValue(true) 6 | 7 | fun MutableLiveData.hide() = this.postValue(false) 8 | -------------------------------------------------------------------------------- /common/src/main/java/dev/baseio/slackclone/common/extensions/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.common.extensions 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.annotation.LayoutRes 7 | 8 | fun View.visible() { 9 | this.visibility = View.VISIBLE 10 | } 11 | 12 | fun View.gone() { 13 | this.visibility = View.GONE 14 | } 15 | 16 | fun View.isVisible() = this.visibility == View.VISIBLE 17 | 18 | fun View.isGone() = this.visibility == View.GONE 19 | 20 | fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View = 21 | LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot) 22 | 23 | -------------------------------------------------------------------------------- /common/src/main/java/dev/baseio/slackclone/common/injection/DispatcherModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.common.injection 2 | 3 | import dev.baseio.slackclone.common.injection.dispatcher.CoroutineDispatcherProvider 4 | import dev.baseio.slackclone.common.injection.dispatcher.RealCoroutineDispatcherProvider 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 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 | -------------------------------------------------------------------------------- /common/src/main/java/dev/baseio/slackclone/common/injection/dispatcher/CoroutineDispatcherProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.common.injection.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 | -------------------------------------------------------------------------------- /common/src/main/java/dev/baseio/slackclone/common/injection/dispatcher/RealCoroutineDispatcherProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.common.injection.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 | -------------------------------------------------------------------------------- /common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Home 4 | DMs 5 | Mentions 6 | Search 7 | You 8 | Workspaces 9 | Add a workspace 10 | Preferences 11 | Help 12 | Pause Notifications 13 | Set yoursel as away 14 | Saved items 15 | View profile 16 | Notifications 17 | mutualmobile 18 | mutualmobile.slack.com 19 | Threads 20 | prj_jetpack_compose 21 | Recent 22 | Starred 23 | Direct messages 24 | Channels 25 | Connections 26 | Browse People 27 | Browse Channels 28 | Recent Searches 29 | Narrow Your Search 30 | -------------------------------------------------------------------------------- /common/src/test/java/dev/baseio/slackclone/common/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.common 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 | } -------------------------------------------------------------------------------- /commonui/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /commonui/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 | buildFeatures { 23 | compose = true 24 | } 25 | 26 | composeOptions { 27 | kotlinCompilerExtensionVersion = Lib.Android.COMPOSE_COMPILER_VERSION 28 | } 29 | packagingOptions { 30 | resources { 31 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 32 | } 33 | } 34 | } 35 | 36 | // Required for annotation processing plugins like Dagger 37 | kapt { 38 | generateStubs = true 39 | correctErrorTypes = true 40 | } 41 | 42 | dependencies { 43 | /*Kotlin*/ 44 | api(Lib.Kotlin.KT_STD) 45 | api(Lib.Kotlin.KTX_CORE) 46 | /* Android Designing and layout */ 47 | api(Lib.Android.MATERIAL_DESIGN) 48 | api(Lib.Android.COMPOSE_UI) 49 | implementation(Lib.Android.CONSTRAINT_LAYOUT_COMPOSE) 50 | implementation(Lib.Android.ACCOMPANIST_SYSTEM_UI_CONTROLLER) 51 | api(Lib.Android.COIL_COMPOSE) 52 | api(Lib.Android.COMPOSE_MATERIAL) 53 | api(Lib.Android.COMPOSE_TOOLING) 54 | debugApi(Lib.Android.COMPOSE_DEBUG_TOOLING) 55 | api(Lib.Android.ACTIVITY_COMPOSE) 56 | 57 | /* Dependency Injection */ 58 | api(Lib.Di.hiltAndroid) 59 | kapt(Lib.Di.hiltAndroidCompiler) 60 | } -------------------------------------------------------------------------------- /commonui/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/consumer-rules.pro -------------------------------------------------------------------------------- /commonui/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 -------------------------------------------------------------------------------- /commonui/src/androidTest/java/dev/baseio/slackclone/commonui/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui 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.slackclone.commonui.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /commonui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/keyboard/Keyboard.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.keyboard 2 | 3 | import android.graphics.Rect 4 | import android.view.ViewTreeObserver 5 | import androidx.compose.runtime.* 6 | import androidx.compose.ui.platform.LocalView 7 | 8 | sealed class Keyboard { 9 | data class Opened(var height: Int) : Keyboard() 10 | object Closed : Keyboard() 11 | } 12 | 13 | @Composable 14 | fun keyboardAsState(): State { 15 | val keyboardState = remember { mutableStateOf(Keyboard.Closed) } 16 | val view = LocalView.current 17 | DisposableEffect(view) { 18 | val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener { 19 | val rect = Rect() 20 | view.getWindowVisibleDisplayFrame(rect) 21 | val screenHeight = view.rootView.height 22 | val keypadHeight = screenHeight - rect.bottom 23 | keyboardState.value = if (keypadHeight > screenHeight * 0.15) { 24 | Keyboard.Opened(screenHeight - keypadHeight) 25 | } else { 26 | Keyboard.Closed 27 | } 28 | } 29 | view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener) 30 | 31 | onDispose { 32 | view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener) 33 | } 34 | } 35 | 36 | return keyboardState 37 | } -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/material/DefaultSnackbar.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.material 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.foundation.layout.wrapContentHeight 5 | import androidx.compose.material.* 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 11 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 12 | 13 | @Composable 14 | fun DefaultSnackbar( 15 | snackbarHostState: SnackbarHostState, 16 | modifier: Modifier = Modifier, 17 | onDismiss: () -> Unit = { } 18 | ) { 19 | SnackbarHost( 20 | hostState = snackbarHostState, 21 | snackbar = { data -> 22 | Snackbar( 23 | content = { 24 | Text( 25 | text = data.message, 26 | style = SlackCloneTypography.body1, 27 | color = SlackCloneColorProvider.colors.textPrimary, 28 | ) 29 | }, 30 | action = { 31 | data.actionLabel?.let { actionLabel -> 32 | TextButton(onClick = onDismiss) { 33 | Text( 34 | text = actionLabel, 35 | color = SlackCloneColorProvider.colors.textPrimary, 36 | style = SlackCloneTypography.body2 37 | ) 38 | } 39 | } 40 | }, 41 | backgroundColor = Color.White 42 | ) 43 | }, 44 | modifier = modifier 45 | .fillMaxWidth() 46 | .wrapContentHeight(Alignment.Bottom) 47 | ) 48 | } -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/material/SlackSurfaceAppBar.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.material 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.foundation.layout.RowScope 5 | import androidx.compose.material.* 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Color 9 | import androidx.compose.ui.unit.Dp 10 | import dev.baseio.slackclone.commonui.theme.SlackCloneSurface 11 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 12 | 13 | @Composable 14 | fun SlackSurfaceAppBar( 15 | title: @Composable () -> Unit, 16 | modifier: Modifier = Modifier, 17 | navigationIcon: @Composable (() -> Unit)? = null, 18 | actions: @Composable RowScope.() -> Unit = {}, 19 | backgroundColor: Color = MaterialTheme.colors.primarySurface, 20 | contentColor: Color = contentColorFor(backgroundColor), 21 | elevation: Dp = AppBarDefaults.TopAppBarElevation, 22 | ) { 23 | SlackCloneSurface( 24 | color = backgroundColor, 25 | contentColor = contentColor, 26 | elevation = elevation 27 | ) { 28 | TopAppBar( 29 | title, modifier, navigationIcon, actions, backgroundColor, contentColor, elevation 30 | ) 31 | } 32 | } 33 | 34 | @Composable 35 | fun SlackSurfaceAppBar( 36 | modifier: Modifier = Modifier, 37 | backgroundColor: Color = MaterialTheme.colors.primarySurface, 38 | contentColor: Color = contentColorFor(backgroundColor), 39 | elevation: Dp = AppBarDefaults.TopAppBarElevation, 40 | contentPadding: PaddingValues = AppBarDefaults.ContentPadding, 41 | content: @Composable RowScope.() -> Unit 42 | ) { 43 | SlackCloneSurface( 44 | color = backgroundColor, 45 | contentColor = contentColor, 46 | elevation = elevation 47 | ) { 48 | TopAppBar( 49 | modifier, backgroundColor, contentColor, elevation, contentPadding, content 50 | ) 51 | } 52 | } -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/reusable/SlackImageBox.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.reusable 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.foundation.shape.CircleShape 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.draw.clip 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import androidx.constraintlayout.compose.ConstraintLayout 16 | import coil.compose.rememberImagePainter 17 | import coil.transform.RoundedCornersTransformation 18 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 19 | import dev.baseio.slackclone.commonui.theme.SlackCloneSurface 20 | 21 | @Composable 22 | fun SlackImageBox(modifier: Modifier, imageUrl: String) { 23 | Image( 24 | painter = rememberImagePainter( 25 | data = imageUrl, 26 | builder = { 27 | transformations(RoundedCornersTransformation(12.0f, 12.0f, 12.0f, 12.0f)) 28 | } 29 | ), 30 | contentDescription = null, 31 | modifier = modifier 32 | ) 33 | } 34 | 35 | @Composable 36 | fun SlackOnlineBox(imageUrl: String, 37 | parentModifier: Modifier = Modifier.size(34.dp), 38 | imageModifier:Modifier = Modifier.size(28.dp)) { 39 | ConstraintLayout(parentModifier) { 40 | val (image, indicator) = createRefs() 41 | SlackImageBox( 42 | imageModifier 43 | .constrainAs(image) { 44 | top.linkTo(parent.top) 45 | bottom.linkTo(parent.bottom) 46 | start.linkTo(parent.start) 47 | end.linkTo(parent.end) 48 | }, imageUrl 49 | ) 50 | SlackCloneSurface(shape = CircleShape, 51 | border = BorderStroke(3.dp, color = SlackCloneColorProvider.colors.uiBackground), 52 | modifier = Modifier 53 | .constrainAs(indicator) { 54 | bottom.linkTo(parent.bottom) 55 | end.linkTo(parent.end) 56 | } 57 | .size(14.dp)){ 58 | Box( 59 | modifier = Modifier 60 | .size(12.dp) 61 | .clip(CircleShape) 62 | .background(Color.Green) 63 | 64 | ) 65 | } 66 | 67 | } 68 | } 69 | 70 | 71 | @Preview 72 | @Composable 73 | fun PrevSlackOnlineBox() { 74 | SlackOnlineBox( 75 | imageUrl = "https://avatars.slack-edge.com/2018-07-20/401750958992_1b07bb3c946bc863bfc6_88.png" 76 | ) 77 | } -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/reusable/SlackListItem.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.reusable 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.material.ExperimentalMaterialApi 8 | import androidx.compose.material.Icon 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.draw.shadow 14 | import androidx.compose.ui.graphics.vector.ImageVector 15 | import androidx.compose.ui.unit.dp 16 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 17 | import dev.baseio.slackclone.commonui.theme.SlackCloneShapes 18 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 19 | 20 | @OptIn(ExperimentalMaterialApi::class) 21 | @Composable 22 | fun SlackListItem( 23 | icon: ImageVector, 24 | title: String, 25 | trailingItem: ImageVector? = null, 26 | onItemClick: () -> Unit = {} 27 | ) { 28 | Row( 29 | modifier = Modifier 30 | .padding(8.dp) 31 | .clickable { 32 | onItemClick() 33 | }, verticalAlignment = Alignment.CenterVertically 34 | ) { 35 | Icon( 36 | imageVector = icon, 37 | contentDescription = null, 38 | tint = SlackCloneColorProvider.colors.textPrimary.copy(alpha = 0.4f), 39 | modifier = Modifier 40 | .size(28.dp) 41 | .padding(4.dp) 42 | ) 43 | Text( 44 | text = title, 45 | style = SlackCloneTypography.subtitle1.copy( 46 | color = SlackCloneColorProvider.colors.textPrimary.copy( 47 | alpha = 0.8f 48 | ) 49 | ), modifier = Modifier 50 | .weight(1f) 51 | .padding(8.dp) 52 | ) 53 | trailingItem?.let { safeIcon -> 54 | Icon( 55 | imageVector = safeIcon, 56 | contentDescription = null, 57 | tint = SlackCloneColorProvider.colors.textPrimary.copy(alpha = 0.4f), 58 | modifier = Modifier 59 | .size(24.dp) 60 | .padding(4.dp) 61 | ) 62 | } 63 | } 64 | } 65 | 66 | @OptIn(ExperimentalMaterialApi::class) 67 | @Composable 68 | fun SlackListItem( 69 | icon: @Composable () -> Unit, 70 | center: @Composable (Modifier) -> Unit, 71 | trailingItem: @Composable () -> Unit ? = {}, 72 | onItemClick: () -> Unit = {} 73 | ) { 74 | Row( 75 | modifier = Modifier 76 | .padding(8.dp) 77 | .clickable { 78 | onItemClick() 79 | }, verticalAlignment = Alignment.CenterVertically 80 | ) { 81 | icon() 82 | 83 | center(Modifier 84 | .weight(1f) 85 | .padding(8.dp)) 86 | 87 | trailingItem() 88 | } 89 | } -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val SlackCloneColor = Color(0xff411540) 6 | val DarkAppBarColor = Color(0xff1a1b1e) 7 | val DarkBackground = Color(0xff1b1d21) 8 | val FunctionalRed = Color(0xffd00036) 9 | val FunctionalRedDark = Color(0xffea6d7e) 10 | val SlackLogoYellow = Color(0xffECB22E) 11 | val DarkBlue = Color(0xFF01579B) 12 | val LightBlue = Color(0xFF81D4FA) 13 | val DisabledSwitchThumbColor = Color(0xFF9E9E9E) 14 | val DisabledSwitchTrackColor = Color(0xFFBDBDBD) 15 | val LineColorLight = Color.Black.copy(alpha = 0.4f) 16 | val LineColorDark = Color.White.copy(alpha = 0.3f) 17 | const val AlphaNearOpaque = 0.95f 18 | const val AlphaNearTransparent = 0.15f 19 | -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/theme/PraxisSurface.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.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 kotlin.math.ln 21 | 22 | /** 23 | * An alternative to [androidx.compose.material.Surface] 24 | */ 25 | @Composable 26 | fun SlackCloneSurface( 27 | modifier: Modifier = Modifier, 28 | shape: Shape = RectangleShape, 29 | color: Color = SlackCloneColorProvider.colors.uiBackground, 30 | contentColor: Color = SlackCloneColorProvider.colors.textSecondary, 31 | border: BorderStroke? = null, 32 | elevation: Dp = 0.dp, 33 | content: @Composable () -> Unit 34 | ) { 35 | Box( 36 | modifier = modifier 37 | .shadow(elevation = elevation, shape = shape, clip = false) 38 | .zIndex(elevation.value) 39 | .then(if (border != null) Modifier.border(border, shape) else Modifier) 40 | .background( 41 | color = getBackgroundColorForElevation(color, elevation), 42 | shape = shape 43 | ) 44 | .clip(shape) 45 | ) { 46 | CompositionLocalProvider(LocalContentColor provides contentColor, content = content) 47 | } 48 | } 49 | 50 | @Composable 51 | private fun getBackgroundColorForElevation( 52 | color: Color, 53 | elevation: Dp 54 | ): Color { 55 | return if (elevation > 0.dp 56 | ) { 57 | color.withElevation(elevation) 58 | } else { 59 | color 60 | } 61 | } 62 | 63 | /** 64 | * Applies a [Color.White] overlay to this color based on the [elevation]. This increases visibility 65 | * of elevation for surfaces in a dark theme. 66 | */ 67 | private fun Color.withElevation(elevation: Dp): Color { 68 | val foreground = calculateForeground(elevation) 69 | return foreground.compositeOver(this) 70 | } 71 | 72 | /** 73 | * @return the alpha-modified [Color.White] to overlay on top of the surface color to produce 74 | * the resultant color. 75 | */ 76 | private fun calculateForeground(elevation: Dp): Color { 77 | val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 20f 78 | return Color.White.copy(alpha = alpha) 79 | } -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.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 SlackCloneShapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(6.dp), 10 | large = RoundedCornerShape(10.dp) 11 | ) -------------------------------------------------------------------------------- /commonui/src/main/java/dev/baseio/slackclone/commonui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.Font 6 | import androidx.compose.ui.text.font.FontFamily 7 | import androidx.compose.ui.text.font.FontWeight 8 | import androidx.compose.ui.unit.sp 9 | import dev.baseio.slackclone.commonui.R 10 | 11 | // Set of Material typography styles to start with 12 | 13 | val slackFontFamily = 14 | FontFamily( 15 | Font(R.font.lato_bold, weight = FontWeight.Bold), 16 | Font(R.font.lato_light, weight = FontWeight.Light), 17 | Font(R.font.lato_regular) 18 | ) 19 | 20 | val SlackCloneTypography = Typography( 21 | defaultFontFamily = slackFontFamily, 22 | body1 = TextStyle( 23 | fontFamily = slackFontFamily, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 16.sp 26 | ), 27 | button = TextStyle( 28 | fontFamily = slackFontFamily, 29 | fontWeight = FontWeight.W500, 30 | fontSize = 14.sp 31 | ), 32 | caption = TextStyle( 33 | fontFamily = slackFontFamily, 34 | fontWeight = FontWeight.Normal, 35 | fontSize = 12.sp 36 | ) 37 | 38 | 39 | ) -------------------------------------------------------------------------------- /commonui/src/main/res/anim/slide_left_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /commonui/src/main/res/anim/slide_left_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /commonui/src/main/res/anim/slide_right_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /commonui/src/main/res/anim/slide_right_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /commonui/src/main/res/drawable/ic_email.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /commonui/src/main/res/drawable/ic_eye.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /commonui/src/main/res/font/lato_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/src/main/res/font/lato_bold.ttf -------------------------------------------------------------------------------- /commonui/src/main/res/font/lato_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/src/main/res/font/lato_light.ttf -------------------------------------------------------------------------------- /commonui/src/main/res/font/lato_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/src/main/res/font/lato_regular.ttf -------------------------------------------------------------------------------- /commonui/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /commonui/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /commonui/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /commonui/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /commonui/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/commonui/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /commonui/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /commonui/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #ffffff 7 | #009688 8 | #0645AD 9 | #000 10 | #411540 11 | #e4e4e2 12 | #838381 13 | 14 | 15 | -------------------------------------------------------------------------------- /commonui/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20dp 4 | 16sp 5 | 10sp 6 | 26sp 7 | 8 | 5dp 9 | 8dp 10 | 10dp 11 | 16dp 12 | 65dp 13 | 120dp 14 | 15 | -------------------------------------------------------------------------------- /commonui/src/main/res/values/integer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 300 4 | -------------------------------------------------------------------------------- /commonui/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SlackClone 3 | 4 | -------------------------------------------------------------------------------- /commonui/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 17 | 18 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /commonui/src/test/java/dev/baseio/slackclone/commonui/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.commonui 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 | } -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /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("com.github.vatbub:randomusers:1.3"){ 34 | exclude("com.google.guava") 35 | } 36 | implementation(project(":common")) 37 | implementation(project(":domain")) 38 | /*Kotlin*/ 39 | api(Lib.Kotlin.KT_STD) 40 | api(Lib.Async.COROUTINES) 41 | 42 | /* Paging */ 43 | implementation(Lib.Paging.PAGING_3) 44 | /* Room */ 45 | api(Lib.Room.roomRuntime) 46 | kapt(Lib.Room.roomCompiler) 47 | api(Lib.Room.roomKtx) 48 | api(Lib.Room.roomPaging) 49 | 50 | /* Networking */ 51 | api(Lib.Networking.RETROFIT) 52 | api(Lib.Networking.RETROFIT_GSON) 53 | api(Lib.Networking.LOGGING) 54 | 55 | api(Lib.Serialization.GSON) 56 | 57 | /* Dependency Injection */ 58 | api(Lib.Di.hiltAndroid) 59 | kapt(Lib.Di.hiltAndroidCompiler) 60 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /data/src/androidTest/java/dev/baseio/slackclone/data/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.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 | } -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/injection/DataMappersModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.injection 2 | 3 | import com.github.vatbub.randomusers.result.RandomUser 4 | import dagger.Binds 5 | import dagger.Module 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | import dev.baseio.slackclone.data.local.model.DBSlackChannel 9 | import dev.baseio.slackclone.data.local.model.DBSlackMessage 10 | import dev.baseio.slackclone.data.mapper.* 11 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 12 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 13 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 14 | import javax.inject.Singleton 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | abstract class DataMappersModule { 19 | 20 | @Binds 21 | @Singleton 22 | abstract fun bindSlackUserChannelMapper(slackUserChannelMapper: SlackUserChannelMapper): EntityMapper 23 | 24 | @Binds 25 | @Singleton 26 | abstract fun bindSlackUserDataDomainMapper(slackUserMapper: SlackUserMapper): EntityMapper 27 | 28 | @Binds 29 | @Singleton 30 | abstract fun bindSlackChannelDataDomainMapper(slackChannelMapper: SlackChannelMapper): EntityMapper 31 | 32 | @Binds 33 | @Singleton 34 | abstract fun bindSlackMessageDataDomMapper(slackMessageMapper: SlackMessageMapper): EntityMapper 35 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/injection/DataModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.injection 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.slackclone.data.local.SlackDatabase 11 | import dev.baseio.slackclone.data.local.dao.SlackChannelDao 12 | import dev.baseio.slackclone.data.local.dao.SlackMessageDao 13 | import dev.baseio.slackclone.data.local.dao.SlackUserDao 14 | import kotlinx.coroutines.Dispatchers 15 | import javax.inject.Singleton 16 | import kotlin.coroutines.CoroutineContext 17 | 18 | @Module 19 | @InstallIn(SingletonComponent::class) 20 | object DataModule { 21 | @Provides 22 | @Singleton 23 | fun provideDatabase(@ApplicationContext context: Context): SlackDatabase { 24 | return Room.inMemoryDatabaseBuilder( 25 | context, 26 | SlackDatabase::class.java, 27 | ).fallbackToDestructiveMigration().allowMainThreadQueries().build() 28 | } 29 | 30 | @Provides 31 | @Singleton 32 | fun providesSlackMessageDao(slackDatabase: SlackDatabase): SlackMessageDao = 33 | slackDatabase.slackMessageDao() 34 | 35 | @Provides 36 | @Singleton 37 | fun provideChannelDao(slackDatabase: SlackDatabase): SlackChannelDao = 38 | slackDatabase.slackChannelDao() 39 | 40 | @Provides 41 | @Singleton 42 | fun provideUserDao(slackDatabase: SlackDatabase): SlackUserDao = slackDatabase.slackUserDao() 43 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/injection/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.injection 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import dev.baseio.slackclone.data.repository.SlackChannelLastMessageRepository 8 | import dev.baseio.slackclone.data.repository.SlackChannelsRepositoryImpl 9 | import dev.baseio.slackclone.data.repository.SlackMessagesRepositoryImpl 10 | import dev.baseio.slackclone.data.repository.SlackUserRepository 11 | import dev.baseio.slackclone.domain.repository.ChannelLastMessageRepository 12 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 13 | import dev.baseio.slackclone.domain.repository.MessagesRepository 14 | import dev.baseio.slackclone.domain.repository.UsersRepository 15 | import javax.inject.Singleton 16 | 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | abstract class RepositoryModule { 20 | @Binds 21 | @Singleton 22 | abstract fun bindLocalChannelsRepository(slackLocalChannelsRepositoryImpl: SlackChannelsRepositoryImpl): ChannelsRepository 23 | 24 | @Binds 25 | @Singleton 26 | abstract fun bindSlackUserRepository(slackUserRepository: SlackUserRepository): UsersRepository 27 | 28 | @Binds 29 | @Singleton 30 | abstract fun bindMessagesRepository(slackMessagesRepositoryImpl: SlackMessagesRepositoryImpl): MessagesRepository 31 | 32 | @Binds 33 | @Singleton 34 | abstract fun bindChannelLastMessageRepository(slackChannelLastMessageRepository: SlackChannelLastMessageRepository): ChannelLastMessageRepository 35 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/injection/SourcesModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.injection 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | 8 | @Module 9 | @InstallIn(SingletonComponent::class) 10 | object SourcesModule { 11 | 12 | 13 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/injection/UseCaseModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.injection 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.android.components.ViewModelComponent 7 | import dagger.hilt.android.scopes.ViewModelScoped 8 | import dev.baseio.slackclone.domain.repository.ChannelLastMessageRepository 9 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 10 | import dev.baseio.slackclone.domain.repository.MessagesRepository 11 | import dev.baseio.slackclone.domain.repository.UsersRepository 12 | import dev.baseio.slackclone.domain.usecases.channels.* 13 | import dev.baseio.slackclone.domain.usecases.chat.UseCaseSendMessage 14 | import dev.baseio.slackclone.domain.usecases.chat.UseCaseFetchMessages 15 | 16 | @Module 17 | @InstallIn(ViewModelComponent::class) 18 | object UseCaseModule { 19 | 20 | @Provides 21 | @ViewModelScoped 22 | fun provideUseCaseFetchChannels(channelsRepository: ChannelsRepository) = 23 | UseCaseFetchChannels(channelsRepository) 24 | 25 | @Provides 26 | @ViewModelScoped 27 | fun provideUseCaseFetchChannelWithLastMessage(channelsRepository: ChannelLastMessageRepository) = 28 | UseCaseFetchChannelsWithLastMessage(channelsRepository) 29 | 30 | @Provides 31 | @ViewModelScoped 32 | fun provideUseCaseFetchMessages(messagesRepository: MessagesRepository) = 33 | UseCaseFetchMessages(messagesRepository) 34 | 35 | @Provides 36 | @ViewModelScoped 37 | fun provideUseCaseSendMessage(messagesRepository: MessagesRepository) = 38 | UseCaseSendMessage(messagesRepository) 39 | 40 | @Provides 41 | @ViewModelScoped 42 | fun provideUseCaseCreateChannel(channelsRepository: ChannelsRepository) = 43 | UseCaseCreateChannel(channelsRepository) 44 | 45 | @Provides 46 | @ViewModelScoped 47 | fun provideUseCaseCreateChannels(channelsRepository: ChannelsRepository) = 48 | UseCaseCreateChannels(channelsRepository) 49 | 50 | @Provides 51 | @ViewModelScoped 52 | fun provideUseCaseGetChannel(channelsRepository: ChannelsRepository) = 53 | UseCaseGetChannel(channelsRepository) 54 | 55 | @Provides 56 | @ViewModelScoped 57 | fun provideUseCaseFetchChannelCount(channelsRepository: ChannelsRepository) = 58 | UseCaseFetchChannelCount(channelsRepository) 59 | 60 | @Provides 61 | @ViewModelScoped 62 | fun provideUseCaseSearchChannel(channelsRepository: ChannelsRepository) = 63 | UseCaseSearchChannel(channelsRepository) 64 | 65 | @Provides 66 | @ViewModelScoped 67 | fun provideUseCaseFetchUsers(slackUsersRepository: UsersRepository) = 68 | UseCaseFetchUsers(slackUsersRepository) 69 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/local/SlackDatabase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.local 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import dev.baseio.slackclone.data.local.dao.SlackChannelDao 6 | import dev.baseio.slackclone.data.local.dao.SlackMessageDao 7 | import dev.baseio.slackclone.data.local.dao.SlackUserDao 8 | import dev.baseio.slackclone.data.local.model.DBSlackChannel 9 | import dev.baseio.slackclone.data.local.model.DBSlackMessage 10 | import dev.baseio.slackclone.data.local.model.DBSlackUser 11 | 12 | 13 | @Database( 14 | entities = [DBSlackUser::class, DBSlackChannel::class, DBSlackMessage::class], 15 | version = 1, 16 | exportSchema = false 17 | ) 18 | abstract class SlackDatabase : RoomDatabase() { 19 | abstract fun slackUserDao(): SlackUserDao 20 | abstract fun slackChannelDao(): SlackChannelDao 21 | abstract fun slackMessageDao(): SlackMessageDao 22 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/local/dao/SlackChannelDao.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.local.dao 2 | 3 | import androidx.paging.PagingSource 4 | import androidx.room.* 5 | import dev.baseio.slackclone.data.local.model.ChannelWithLastMessage 6 | import dev.baseio.slackclone.data.local.model.DBSlackChannel 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | @Dao 10 | interface SlackChannelDao { 11 | 12 | @Query("SELECT COUNT(*) from slackChannel ") 13 | fun count(): Int 14 | 15 | @Query("SELECT * FROM slackChannel") 16 | fun getAll(): List 17 | 18 | @Query("SELECT * FROM slackChannel") 19 | fun getAllAsFlow(): Flow> 20 | 21 | @Query("SELECT * FROM slackChannel WHERE uuid IN (:groupIds)") 22 | fun loadAllByIds(groupIds: Array): List 23 | 24 | @Query( 25 | "SELECT * FROM slackChannel WHERE name LIKE :name" 26 | ) 27 | fun findByName(name: String): List 28 | 29 | @Insert(onConflict = OnConflictStrategy.REPLACE) 30 | fun insertAll(channelDB: List) 31 | 32 | @Insert(onConflict = OnConflictStrategy.REPLACE) 33 | suspend fun insert(channel: DBSlackChannel) 34 | 35 | @Delete 36 | fun delete(channelDB: DBSlackChannel) 37 | 38 | @Query("SELECT * from slackChannel where uuid like :uuid") 39 | fun getById(uuid: String): DBSlackChannel? 40 | 41 | // The Int type parameter tells Room to use a PositionalDataSource object. 42 | @Query("SELECT * FROM slackChannel where name like '%' || :params || '%' ORDER BY name ASC") 43 | fun channelsByName(params: String?): PagingSource 44 | 45 | // The Int type parameter tells Room to use a PositionalDataSource object. 46 | @Query("SELECT * FROM slackChannel ORDER BY name ASC") 47 | fun channelsByName(): PagingSource 48 | 49 | @Query("SELECT * FROM slackmessage AS channelMessage " + 50 | "JOIN (SELECT channelId, max(modifiedDate) AS received_at FROM slackMessage GROUP BY channelId) AS channelMessage_last " + 51 | "ON channelMessage_last.channelId = channelMessage.channelId AND channelMessage_last.received_at = channelMessage.modifiedDate") 52 | fun getChannelsWithLastMessage(): PagingSource 53 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/local/dao/SlackMessageDao.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.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.slackclone.data.local.model.DBSlackMessage 9 | 10 | @Dao 11 | interface SlackMessageDao { 12 | @Query("SELECT * FROM slackMessage") 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: DBSlackMessage) 20 | 21 | // The Int type parameter tells Room to use a PositionalDataSource object. 22 | @Query("SELECT * FROM slackMessage where channelId = :params ORDER BY createdDate DESC") 23 | fun messagesByDate(params: String?): PagingSource 24 | 25 | @Query("SELECT * from slackMessage where uuid like :uuid") 26 | fun getById(uuid: String): DBSlackMessage 27 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/local/dao/SlackUserDao.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Delete 5 | import androidx.room.Insert 6 | import androidx.room.Query 7 | import dev.baseio.slackclone.data.local.model.DBSlackUser 8 | 9 | @Dao 10 | interface SlackUserDao { 11 | @Query("SELECT * FROM slackuser") 12 | fun getAll(): List 13 | 14 | @Query("SELECT * FROM slackuser WHERE uuid IN (:slackuserIds)") 15 | fun loadAllByIds(slackuserIds: IntArray): List 16 | 17 | @Query( 18 | "SELECT * FROM slackuser WHERE first_name LIKE :first AND " + 19 | "last_name LIKE :last LIMIT 1" 20 | ) 21 | fun findByName(first: String, last: String): DBSlackUser 22 | 23 | @Insert 24 | fun insertAll( slackUsers: List) 25 | 26 | @Delete 27 | fun delete(slackUser: DBSlackUser) 28 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/local/model/DBSlackChannel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.local.model 2 | 3 | import androidx.room.* 4 | 5 | @Entity(tableName = "slackChannel") 6 | data class DBSlackChannel( 7 | @PrimaryKey val uuid: String, 8 | @ColumnInfo(name = "name") val name: String?, 9 | @ColumnInfo(name = "createdDate") val createdDate: Long? = System.currentTimeMillis(), 10 | @ColumnInfo(name = "modifiedDate") val modifiedDate: Long? = System.currentTimeMillis(), 11 | @ColumnInfo(name = "isMuted") val isMuted: Boolean? = false, 12 | @ColumnInfo(name = "isStarred") val isStarred: Boolean? = false, 13 | @ColumnInfo(name = "isPrivate") val isPrivate: Boolean? = false, 14 | @ColumnInfo(name = "isShareOutSide") val isShareOutSide: Boolean? = false, 15 | @ColumnInfo(name = "photo") val avatarUrl: String? = null, 16 | @ColumnInfo(name = "isOneToOne") val isOneToOne: Boolean? = null 17 | ) 18 | 19 | data class ChannelWithLastMessage( 20 | @Embedded 21 | val message: DBSlackMessage, 22 | @Relation(parentColumn = "channelId", entityColumn = "uuid") val dbSlackChannel: DBSlackChannel 23 | ) -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/local/model/DBSlackMessage.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.local.model 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "slackMessage") 8 | data class DBSlackMessage( 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 = "createdBy") val createdBy: String, 14 | @ColumnInfo(name = "createdDate") val createdDate: Long, 15 | @ColumnInfo(name = "modifiedDate") val modifiedDate: Long, 16 | ) -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/local/model/DBSlackUser.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.local.model 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "slackUser") 8 | data class DBSlackUser( 9 | @PrimaryKey val uuid: String, 10 | @ColumnInfo(name = "first_name") val firstName: String?, 11 | @ColumnInfo(name = "last_name") val lastName: String?, 12 | @ColumnInfo(name = "photo") val photo: String? 13 | ) 14 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/local/model/SlackPreferences.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.local.model 2 | 3 | import java.util.Date 4 | 5 | data class SlackPreferences( 6 | var id: Int, 7 | var name: String, 8 | var value: String, 9 | var lastModified: Long = Date().time, 10 | var description: String, 11 | ) -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/mapper/EntityMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.mapper 2 | 3 | interface EntityMapper { 4 | fun mapToDomain(entity: Data): Domain 5 | 6 | fun mapToData(model: Domain): Data 7 | } 8 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/mapper/SlackChannelMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.mapper 2 | 3 | import dev.baseio.slackclone.data.local.model.DBSlackChannel 4 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 5 | import javax.inject.Inject 6 | 7 | class SlackChannelMapper @Inject constructor() : 8 | EntityMapper { 9 | override fun mapToDomain(entity: DBSlackChannel): DomainLayerChannels.SlackChannel { 10 | return DomainLayerChannels.SlackChannel( 11 | isStarred = entity.isStarred, 12 | isPrivate = entity.isPrivate, 13 | uuid = entity.uuid, 14 | name = entity.name, 15 | isMuted = entity.isMuted, 16 | createdDate = entity.createdDate, 17 | modifiedDate = entity.modifiedDate, 18 | isShareOutSide = entity.isShareOutSide, 19 | isOneToOne = entity.isOneToOne, 20 | avatarUrl = entity.avatarUrl 21 | ) 22 | } 23 | 24 | override fun mapToData(model: DomainLayerChannels.SlackChannel): DBSlackChannel { 25 | return DBSlackChannel( 26 | model.uuid ?: model.name!!, 27 | model.name, 28 | isStarred = model.isStarred, 29 | createdDate = model.createdDate, 30 | modifiedDate = model.modifiedDate, 31 | isPrivate = model.isPrivate, 32 | isShareOutSide = model.isShareOutSide, 33 | isOneToOne = model.isOneToOne, avatarUrl = model.avatarUrl 34 | ) 35 | } 36 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/mapper/SlackMessageMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.mapper 2 | 3 | import dev.baseio.slackclone.data.local.model.DBSlackMessage 4 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 5 | import java.util.* 6 | import java.util.concurrent.TimeUnit 7 | import javax.inject.Inject 8 | 9 | class SlackMessageMapper @Inject constructor() : EntityMapper { 10 | override fun mapToDomain(entity: DBSlackMessage): DomainLayerMessages.SlackMessage { 11 | return DomainLayerMessages.SlackMessage( 12 | entity.uuid, 13 | entity.channelId, 14 | entity.message, 15 | entity.userId, 16 | entity.createdBy, 17 | entity.createdDate, 18 | entity.modifiedDate 19 | ) 20 | } 21 | 22 | override fun mapToData(model: DomainLayerMessages.SlackMessage): DBSlackMessage { 23 | return DBSlackMessage( 24 | model.uuid, 25 | model.channelId, 26 | model.message, 27 | model.userId, 28 | model.createdBy, 29 | model.createdDate, 30 | model.modifiedDate, 31 | ) 32 | } 33 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/mapper/SlackUserChannelMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.mapper 2 | 3 | import dev.baseio.slackclone.data.local.model.DBSlackChannel 4 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 5 | import javax.inject.Inject 6 | 7 | class SlackUserChannelMapper @Inject constructor() : 8 | EntityMapper { 9 | override fun mapToDomain(entity: DBSlackChannel): DomainLayerUsers.SlackUser { 10 | TODO("Not yet implemented") 11 | } 12 | 13 | override fun mapToData(model: DomainLayerUsers.SlackUser): DBSlackChannel { 14 | return DBSlackChannel(model.login, model.name, avatarUrl = model.picture, isOneToOne = true) 15 | } 16 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/mapper/SlackUserMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.mapper 2 | 3 | import com.github.vatbub.randomusers.result.Name 4 | import com.github.vatbub.randomusers.result.RandomUser 5 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 6 | import javax.inject.Inject 7 | 8 | class SlackUserMapper @Inject constructor() : EntityMapper { 9 | override fun mapToDomain(entity: RandomUser): DomainLayerUsers.SlackUser { 10 | return DomainLayerUsers.SlackUser( 11 | entity.gender.genderText, 12 | entity.name.fullName(), 13 | entity.location.city, 14 | entity.email, 15 | entity.login.username, 16 | entity.dateOfBirth.time, 17 | entity.registrationDate.time, 18 | entity.phone, 19 | entity.cell, 20 | entity.picture.mediumPicture.toURI().toString(), 21 | entity.nationality.shortCode 22 | ) 23 | } 24 | 25 | override fun mapToData(model: DomainLayerUsers.SlackUser): RandomUser { 26 | TODO("not needed!") 27 | } 28 | } 29 | 30 | private fun Name.fullName(): String { 31 | return "${this.firstName} ${this.lastName}" 32 | } 33 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/repository/SlackChannelLastMessageRepository.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.repository 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.slackclone.data.local.dao.SlackChannelDao 8 | import dev.baseio.slackclone.data.local.model.DBSlackChannel 9 | import dev.baseio.slackclone.data.local.model.DBSlackMessage 10 | import dev.baseio.slackclone.data.mapper.EntityMapper 11 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 12 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 13 | import dev.baseio.slackclone.domain.repository.ChannelLastMessageRepository 14 | import kotlinx.coroutines.flow.Flow 15 | import kotlinx.coroutines.flow.map 16 | import javax.inject.Inject 17 | 18 | class SlackChannelLastMessageRepository @Inject constructor( 19 | private val slackChannelDao: SlackChannelDao, 20 | private val messagesMapper: EntityMapper, 21 | private val slackChannelMapper: EntityMapper 22 | ) : ChannelLastMessageRepository { 23 | override fun fetchChannels(): Flow> { 24 | val chatPager = Pager(PagingConfig(pageSize = 20)) { 25 | slackChannelDao.getChannelsWithLastMessage() 26 | } 27 | return chatPager.flow.map { 28 | it.map { 29 | DomainLayerMessages.LastMessage( 30 | slackChannelMapper.mapToDomain(it.dbSlackChannel), 31 | messagesMapper.mapToDomain(it.message) 32 | ) 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/repository/SlackChannelsRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.repository 2 | 3 | import androidx.paging.* 4 | import dev.baseio.slackclone.common.injection.dispatcher.CoroutineDispatcherProvider 5 | import dev.baseio.slackclone.data.local.dao.SlackChannelDao 6 | import dev.baseio.slackclone.data.local.model.DBSlackChannel 7 | import dev.baseio.slackclone.data.mapper.EntityMapper 8 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 9 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 10 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.map 13 | import kotlinx.coroutines.withContext 14 | import javax.inject.Inject 15 | 16 | class SlackChannelsRepositoryImpl @Inject constructor( 17 | private val slackChannelDao: SlackChannelDao, 18 | private val slackUserChannelMapper: EntityMapper, 19 | private val slackChannelMapper: EntityMapper, 20 | private val coroutineMainDispatcherProvider: CoroutineDispatcherProvider 21 | ) : 22 | ChannelsRepository { 23 | 24 | override fun fetchChannelsPaged(params: String?): Flow> { 25 | val chatPager = Pager(PagingConfig(pageSize = 20)) { 26 | params?.takeIf { it.isNotEmpty() }?.let { 27 | slackChannelDao.channelsByName(params) 28 | } ?: run { 29 | slackChannelDao.channelsByName() 30 | } 31 | } 32 | return chatPager.flow.map { 33 | it.map {message-> 34 | slackChannelMapper.mapToDomain(message) 35 | } 36 | } 37 | } 38 | 39 | override suspend fun channelCount(): Int { 40 | return withContext(coroutineMainDispatcherProvider.io) { 41 | slackChannelDao.count() 42 | } 43 | } 44 | 45 | override fun fetchChannels(): Flow> { 46 | return slackChannelDao.getAllAsFlow() 47 | .map { list -> dbToDomList(list) } 48 | } 49 | 50 | private fun dbToDomList(list: List) = 51 | list.map { channel -> slackChannelMapper.mapToDomain(channel) } 52 | 53 | override suspend fun getChannel(uuid: String): DomainLayerChannels.SlackChannel? { 54 | val dbSlack = slackChannelDao.getById(uuid) 55 | return dbSlack?.let { slackChannelMapper.mapToDomain(it) } 56 | } 57 | 58 | override suspend fun saveOneToOneChannels(params: List) { 59 | return withContext(coroutineMainDispatcherProvider.io) { 60 | slackChannelDao.insertAll(params.map { 61 | slackUserChannelMapper.mapToData(it) 62 | }) 63 | } 64 | } 65 | 66 | override suspend fun saveChannel(params: DomainLayerChannels.SlackChannel): DomainLayerChannels.SlackChannel? { 67 | return withContext(coroutineMainDispatcherProvider.io) { 68 | slackChannelDao.insert(slackChannelMapper.mapToData(params)) 69 | slackChannelDao.getById(params.uuid!!)?.let { slackChannelMapper.mapToDomain(it) } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/repository/SlackMessagesRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.repository 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.slackclone.common.injection.dispatcher.CoroutineDispatcherProvider 8 | import dev.baseio.slackclone.data.local.dao.SlackMessageDao 9 | import dev.baseio.slackclone.data.local.model.DBSlackMessage 10 | import dev.baseio.slackclone.data.mapper.EntityMapper 11 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 12 | import dev.baseio.slackclone.domain.repository.MessagesRepository 13 | import kotlinx.coroutines.flow.* 14 | import kotlinx.coroutines.withContext 15 | import javax.inject.Inject 16 | 17 | class SlackMessagesRepositoryImpl @Inject constructor( 18 | private val slackMessageDao: SlackMessageDao, 19 | private val entityMapper: EntityMapper, 20 | private val coroutineMainDispatcherProvider: CoroutineDispatcherProvider 21 | ) : MessagesRepository { 22 | override fun fetchMessages(params: String?): Flow> { 23 | return Pager(PagingConfig(pageSize = 20)) { 24 | slackMessageDao.messagesByDate(params) 25 | }.flow.mapLatest { it -> it.map { entityMapper.mapToDomain(it) } } 26 | } 27 | 28 | override suspend fun sendMessage(params: DomainLayerMessages.SlackMessage): DomainLayerMessages.SlackMessage { 29 | return withContext(coroutineMainDispatcherProvider.io) { 30 | slackMessageDao.insert(entityMapper.mapToData(params)) 31 | params 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /data/src/main/java/dev/baseio/slackclone/data/repository/SlackUserRepository.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.data.repository 2 | 3 | import com.github.vatbub.randomusers.Generator 4 | import com.github.vatbub.randomusers.result.RandomUser 5 | import dev.baseio.slackclone.common.injection.dispatcher.CoroutineDispatcherProvider 6 | import dev.baseio.slackclone.data.mapper.EntityMapper 7 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 8 | import dev.baseio.slackclone.domain.repository.UsersRepository 9 | import kotlinx.coroutines.withContext 10 | import javax.inject.Inject 11 | 12 | class SlackUserRepository @Inject constructor( 13 | private val mapper: EntityMapper, 14 | private val coroutineMainDispatcherProvider: CoroutineDispatcherProvider 15 | ) : 16 | UsersRepository { 17 | override suspend fun getUsers(count: Int): List { 18 | return withContext(coroutineMainDispatcherProvider.io) { 19 | Generator.generateRandomUsers( 20 | RandomUser.RandomUserSpec(), 21 | count 22 | ).map { 23 | mapper.mapToDomain(it) 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /data/src/test/java/dev/baseio/slackclone/data/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.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 | } -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /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 | } 16 | 17 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/SafeResult.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain 2 | 3 | /** 4 | * A generic class that holds a value with its loading status. 5 | * @param 6 | */ 7 | sealed class SafeResult { 8 | 9 | data class Success(val data: T) : SafeResult() 10 | data class Failure( 11 | val exception: Exception? = Exception("Unknown Error"), 12 | val message: String = exception?.localizedMessage ?: "" 13 | ) : SafeResult() 14 | 15 | object NetworkError : SafeResult() 16 | 17 | override fun toString(): String { 18 | return when (this) { 19 | is Success -> "Success[data=$data]" 20 | is Failure -> "Failure[exception=$exception]" 21 | is NetworkError -> "NetworkError" 22 | } 23 | } 24 | } 25 | 26 | /** 27 | * `true` if [SafeResult] is of type [Success] & holds non-null [Success.data]. 28 | */ 29 | val SafeResult<*>.succeeded 30 | get() = this is SafeResult.Success && data != null 31 | 32 | fun SafeResult.getSuccessOrNull(): T? { 33 | return when (this) { 34 | is SafeResult.Success -> this.data 35 | else -> null 36 | } 37 | } 38 | 39 | fun SafeResult.getErrorOrNull(): SafeResult.Failure? { 40 | return when (this) { 41 | is SafeResult.Failure -> this 42 | else -> null 43 | } 44 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/mappers/DomainModel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.mappers 2 | 3 | 4 | interface UiModelMapper { 5 | fun mapToPresentation(model: DomainModel): UiModel 6 | 7 | fun mapToDomain(modelItem: UiModel): DomainModel 8 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/model/channel/SlackChannel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.model.channel 2 | 3 | 4 | interface DomainLayerChannels { 5 | data class SlackChannel( 6 | val uuid: String? = null, 7 | val name: String? = null, 8 | val createdDate: Long? = null, 9 | val modifiedDate: Long? = null, 10 | val isMuted: Boolean? = null, 11 | val isPrivate: Boolean? = null, 12 | val isStarred: Boolean? = false, 13 | val isShareOutSide: Boolean? = false, 14 | val isOneToOne: Boolean?, 15 | val avatarUrl: String? 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/model/message/SlackMessage.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.model.message 2 | 3 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 4 | 5 | 6 | interface DomainLayerMessages { 7 | data class SlackMessage( 8 | val uuid: String, 9 | val channelId: String, 10 | val message: String, 11 | val userId: String, 12 | val createdBy: String, 13 | val createdDate: Long, 14 | val modifiedDate: Long, 15 | ) 16 | 17 | data class LastMessage( 18 | val channel: DomainLayerChannels.SlackChannel, 19 | val message: DomainLayerMessages.SlackMessage 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/model/users/SlackUser.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.model.users 2 | 3 | interface DomainLayerUsers { 4 | data class SlackUser( 5 | val gender: String, 6 | val name: String, 7 | val location: String, 8 | val email: String, 9 | val login: String, 10 | val dateOfBirth: Long, 11 | val registrationDate: Long, 12 | val phone: String, 13 | val cell: String, 14 | val picture: String, 15 | val nationality: String, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/repository/ChannelLastMessageRepository.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.repository 2 | 3 | import androidx.paging.PagingData 4 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 5 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface ChannelLastMessageRepository { 9 | fun fetchChannels(): Flow> 10 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/repository/ChannelsRepository.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.repository 2 | 3 | import androidx.paging.PagingData 4 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 5 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface ChannelsRepository { 9 | fun fetchChannels(): Flow> 10 | fun fetchChannelsPaged(params: String?): Flow> 11 | suspend fun saveChannel(params: DomainLayerChannels.SlackChannel): DomainLayerChannels.SlackChannel? 12 | suspend fun getChannel(uuid: String): DomainLayerChannels.SlackChannel? 13 | suspend fun channelCount(): Int 14 | suspend fun saveOneToOneChannels(params: List) 15 | } 16 | 17 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/repository/MessagesRepository.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.repository 2 | 3 | import androidx.paging.PagingData 4 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface MessagesRepository { 8 | fun fetchMessages(params: String?): Flow> 9 | suspend fun sendMessage(params: DomainLayerMessages.SlackMessage): DomainLayerMessages.SlackMessage 10 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/repository/UsersRepository.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.repository 2 | 3 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 4 | 5 | interface UsersRepository { 6 | suspend fun getUsers(count: Int): List 7 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/BaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.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 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/channels/UseCaseCreateChannel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.channels 2 | 3 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 4 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 5 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 6 | 7 | class UseCaseCreateChannel(private val channelsRepository: ChannelsRepository) : BaseUseCase { 8 | override suspend fun perform(params: DomainLayerChannels.SlackChannel): DomainLayerChannels.SlackChannel? { 9 | return channelsRepository.saveChannel(params) 10 | } 11 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/channels/UseCaseCreateChannels.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.channels 2 | 3 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 4 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 5 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 6 | 7 | class UseCaseCreateChannels(private val channelsRepository: ChannelsRepository) : 8 | BaseUseCase> { 9 | override suspend fun perform(params: List) { 10 | return channelsRepository.saveOneToOneChannels(params) 11 | } 12 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/channels/UseCaseFetchChannelCount.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.channels 2 | 3 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 4 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 5 | 6 | class UseCaseFetchChannelCount(private val channelsRepository: ChannelsRepository) : BaseUseCase { 7 | 8 | override suspend fun perform(): Int { 9 | return channelsRepository.channelCount() 10 | } 11 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/channels/UseCaseFetchChannels.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.channels 2 | 3 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 4 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 5 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | class UseCaseFetchChannels( 9 | private val channelsRepository: ChannelsRepository, 10 | ) : BaseUseCase, Unit> { 11 | 12 | override fun performStreaming(params: Unit?): Flow> { 13 | return channelsRepository.fetchChannels() 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/channels/UseCaseFetchChannelsWithLastMessage.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.channels 2 | 3 | import androidx.paging.PagingData 4 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 5 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 6 | import dev.baseio.slackclone.domain.repository.ChannelLastMessageRepository 7 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | class UseCaseFetchChannelsWithLastMessage(private val channelLastMessageRepository: ChannelLastMessageRepository) : 11 | BaseUseCase, Unit> { 12 | 13 | override fun performStreaming(params: Unit?): Flow> { 14 | return channelLastMessageRepository.fetchChannels() 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/channels/UseCaseFetchUsers.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.channels 2 | 3 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 4 | import dev.baseio.slackclone.domain.repository.UsersRepository 5 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 6 | 7 | class UseCaseFetchUsers(private val usersRepository: UsersRepository) : 8 | BaseUseCase, Int> { 9 | override suspend fun perform(params: Int): List { 10 | return usersRepository.getUsers(params) 11 | } 12 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/channels/UseCaseGetChannel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.channels 2 | 3 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 4 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 5 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 6 | 7 | class UseCaseGetChannel(private val channelsRepository: ChannelsRepository) : 8 | BaseUseCase { 9 | override suspend fun perform(params: String): DomainLayerChannels.SlackChannel? { 10 | return channelsRepository.getChannel(uuid = params) 11 | } 12 | } -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/channels/UseCaseSearchChannel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.channels 2 | 3 | import androidx.paging.PagingData 4 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 5 | import dev.baseio.slackclone.domain.repository.ChannelsRepository 6 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | class UseCaseSearchChannel(private val channelsRepository: ChannelsRepository) : 10 | BaseUseCase, String> { 11 | override fun performStreaming(params: String?): Flow> { 12 | return channelsRepository.fetchChannelsPaged(params) 13 | } 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/chat/UseCaseFetchMessages.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.chat 2 | 3 | import androidx.paging.PagingData 4 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 5 | import dev.baseio.slackclone.domain.repository.MessagesRepository 6 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | class UseCaseFetchMessages(private val messagesRepository: MessagesRepository) : 10 | BaseUseCase, String> { 11 | override fun performStreaming(params: String?): Flow> { 12 | return messagesRepository.fetchMessages(params) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/dev/baseio/slackclone/domain/usecases/chat/UseCaseSendMessage.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.domain.usecases.chat 2 | 3 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 4 | import dev.baseio.slackclone.domain.repository.MessagesRepository 5 | import dev.baseio.slackclone.domain.usecases.BaseUseCase 6 | 7 | class UseCaseSendMessage(private val messagesRepository: MessagesRepository) :BaseUseCase{ 8 | override suspend fun perform(params: DomainLayerMessages.SlackMessage): DomainLayerMessages.SlackMessage { 9 | return messagesRepository.sendMessage(params) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/test/java/dev/baseio/slackclone/domain/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.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 | } -------------------------------------------------------------------------------- /feat-channels/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feat-channels/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/feat-channels/consumer-rules.pro -------------------------------------------------------------------------------- /feat-channels/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 -------------------------------------------------------------------------------- /feat-channels/src/androidTest/java/dev/baseio/slackclone/uichannels/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels 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.slackclone.uichannels.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feat-channels/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/SlackChannelVM.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.hilt.android.lifecycle.HiltViewModel 5 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 6 | import dev.baseio.slackclone.domain.mappers.UiModelMapper 7 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 8 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseFetchChannels 9 | import kotlinx.coroutines.flow.* 10 | import javax.inject.Inject 11 | 12 | @HiltViewModel 13 | class SlackChannelVM @Inject constructor( 14 | private val ucFetchChannels: UseCaseFetchChannels, 15 | private val chatPresentationMapper: UiModelMapper 16 | ) : ViewModel() { 17 | 18 | val channels = MutableStateFlow>>(emptyFlow()) 19 | 20 | fun allChannels() { 21 | channels.value = ucFetchChannels.performStreaming(null).map { channels -> 22 | domSlackToPresentation(channels) 23 | } 24 | } 25 | 26 | fun loadDirectMessageChannels() { 27 | channels.value = ucFetchChannels.performStreaming(null).map { channels -> 28 | domSlackToPresentation(channels,) 29 | } 30 | } 31 | 32 | fun loadStarredChannels() { 33 | channels.value = ucFetchChannels.performStreaming(null).map { channels -> 34 | domSlackToPresentation(channels) 35 | } 36 | } 37 | 38 | private fun domSlackToPresentation(channels: List) = 39 | channels.map { channel -> 40 | chatPresentationMapper.mapToPresentation(channel) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/createsearch/CreateChannelVM.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.createsearch 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 7 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseCreateChannel 8 | import dev.baseio.slackclone.navigator.ComposeNavigator 9 | import dev.baseio.slackclone.navigator.NavigationKeys 10 | import dev.baseio.slackclone.navigator.SlackScreen 11 | import kotlinx.coroutines.flow.MutableStateFlow 12 | import kotlinx.coroutines.launch 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class CreateChannelVM @Inject constructor( 17 | private val composeNavigator: ComposeNavigator, 18 | private val useCaseCreateChannel: UseCaseCreateChannel 19 | ) : 20 | ViewModel() { 21 | 22 | var channel = 23 | MutableStateFlow(DomainLayerChannels.SlackChannel(isOneToOne = false, avatarUrl = null)) 24 | 25 | fun createChannel() { 26 | viewModelScope.launch { 27 | if (channel.value.name?.isNotEmpty() == true) { 28 | val channel = useCaseCreateChannel.perform(channel.value) 29 | composeNavigator.navigateBackWithResult( 30 | NavigationKeys.navigateChannel, 31 | channel?.uuid!!, 32 | SlackScreen.Dashboard.name 33 | ) 34 | } 35 | 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/createsearch/SearchChannelsVM.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.createsearch 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import androidx.paging.map 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 8 | import dev.baseio.slackclone.domain.mappers.UiModelMapper 9 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 10 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseFetchChannelCount 11 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseSearchChannel 12 | import dev.baseio.slackclone.navigator.ComposeNavigator 13 | import dev.baseio.slackclone.navigator.NavigationKeys 14 | import dev.baseio.slackclone.navigator.SlackScreen 15 | import kotlinx.coroutines.flow.MutableStateFlow 16 | import kotlinx.coroutines.flow.map 17 | import kotlinx.coroutines.launch 18 | import javax.inject.Inject 19 | 20 | @HiltViewModel 21 | class SearchChannelsVM @Inject constructor( 22 | private val composeNavigator: ComposeNavigator, 23 | private val ucFetchChannels: UseCaseSearchChannel, 24 | private val useCaseFetchChannelCount: UseCaseFetchChannelCount, 25 | private val chatPresentationMapper: UiModelMapper 26 | ) : ViewModel() { 27 | 28 | val search = MutableStateFlow("") 29 | val channelCount = MutableStateFlow(0) 30 | 31 | var channels = MutableStateFlow(flow("")) 32 | 33 | init { 34 | viewModelScope.launch { 35 | val count = useCaseFetchChannelCount.perform() 36 | channelCount.value = count 37 | } 38 | } 39 | 40 | private fun flow(search: String) = ucFetchChannels.performStreaming(search).map { channels -> 41 | channels.map { channel -> 42 | chatPresentationMapper.mapToPresentation(channel) 43 | } 44 | } 45 | 46 | fun search(newValue: String) { 47 | search.value = newValue 48 | channels.value = flow(newValue) 49 | } 50 | 51 | fun navigate(channel: UiLayerChannels.SlackChannel) { 52 | composeNavigator.navigateBackWithResult( 53 | NavigationKeys.navigateChannel, 54 | channel.uuid!!, 55 | SlackScreen.Dashboard.name 56 | ) 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/directmessages/DMChannelsList.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.directmessages 2 | 3 | import androidx.compose.foundation.lazy.LazyColumn 4 | import androidx.compose.foundation.lazy.rememberLazyListState 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.LaunchedEffect 7 | import androidx.compose.runtime.collectAsState 8 | import androidx.compose.runtime.getValue 9 | import androidx.hilt.navigation.compose.hiltViewModel 10 | import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi 11 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 12 | import androidx.paging.compose.collectAsLazyPagingItems 13 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 14 | import dev.baseio.slackclone.chatcore.views.DMLastMessageItem 15 | 16 | @OptIn(ExperimentalLifecycleComposeApi::class) 17 | @Composable 18 | fun DMChannelsList( 19 | onItemClick: (UiLayerChannels.SlackChannel) -> Unit, 20 | channelVM: DMessageViewModel = hiltViewModel() 21 | ) { 22 | 23 | val channels by channelVM.channels.collectAsStateWithLifecycle() 24 | val channelsFlow = channels.collectAsLazyPagingItems() 25 | val listState = rememberLazyListState() 26 | 27 | LaunchedEffect(key1 = Unit) { 28 | channelVM.refresh() 29 | } 30 | 31 | LazyColumn(state = listState) { 32 | for (channelIndex in 0 until channelsFlow.itemCount) { 33 | val channel = channelsFlow.peek(channelIndex)!! 34 | 35 | item { 36 | DMLastMessageItem({ 37 | onItemClick(it) 38 | }, channelVM.mapToUI(channel.channel), channel.message) 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/directmessages/DMessageViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.directmessages 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.paging.PagingData 5 | import androidx.paging.map 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 8 | import dev.baseio.slackclone.domain.mappers.UiModelMapper 9 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 10 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 11 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseFetchChannelsWithLastMessage 12 | import kotlinx.coroutines.flow.* 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class DMessageViewModel @Inject constructor( 17 | private val useCaseFetchChannels: UseCaseFetchChannelsWithLastMessage, 18 | private val channelPresentationMapper: UiModelMapper, 19 | ) : ViewModel() { 20 | 21 | 22 | val channels = MutableStateFlow(fetchFlow()) 23 | 24 | fun refresh() { 25 | channels.value = useCaseFetchChannels.performStreaming(null) 26 | } 27 | 28 | fun fetchFlow(): Flow> { 29 | return useCaseFetchChannels.performStreaming(null) 30 | } 31 | 32 | fun mapToUI(channel: DomainLayerChannels.SlackChannel): UiLayerChannels.SlackChannel { 33 | return channelPresentationMapper.mapToPresentation(channel) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/views/SlackAllChannels.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.views 2 | 3 | import androidx.compose.ui.res.stringResource 4 | import androidx.hilt.navigation.compose.hiltViewModel 5 | import dev.baseio.slackclone.chatcore.data.ExpandCollapseModel 6 | import dev.baseio.slackclone.uichannels.R 7 | import dev.baseio.slackclone.uichannels.SlackChannelVM 8 | import androidx.compose.runtime.* 9 | import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi 10 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 11 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 12 | 13 | @OptIn(ExperimentalLifecycleComposeApi::class) 14 | @Composable 15 | fun SlackAllChannels( 16 | onItemClick: (UiLayerChannels.SlackChannel) -> Unit = {}, 17 | channelVM: SlackChannelVM = hiltViewModel(), 18 | onClickAdd: () -> Unit 19 | ) { 20 | val recent = stringResource(R.string.channels) 21 | val channelsFlow = channelVM.channels.collectAsStateWithLifecycle() 22 | val channels by channelsFlow.value.collectAsStateWithLifecycle(initialValue = emptyList()) 23 | 24 | LaunchedEffect(key1 = Unit) { 25 | channelVM.allChannels() 26 | } 27 | 28 | var expandCollapseModel by remember { 29 | mutableStateOf( 30 | ExpandCollapseModel( 31 | 1, recent, 32 | needsPlusButton = true, 33 | isOpen = false 34 | ) 35 | ) 36 | } 37 | SKExpandCollapseColumn(expandCollapseModel = expandCollapseModel, onItemClick = onItemClick, { 38 | expandCollapseModel = expandCollapseModel.copy(isOpen = it) 39 | }, channels, onClickAdd) 40 | } -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/views/SlackConnections.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.views 2 | 3 | import androidx.compose.ui.res.stringResource 4 | import androidx.hilt.navigation.compose.hiltViewModel 5 | import dev.baseio.slackclone.uichannels.SlackChannelVM 6 | import dev.baseio.slackclone.chatcore.data.ExpandCollapseModel 7 | import dev.baseio.slackclone.uichannels.R 8 | import androidx.compose.runtime.* 9 | import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi 10 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 11 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 12 | 13 | @OptIn(ExperimentalLifecycleComposeApi::class) 14 | @Composable 15 | fun SlackConnections( 16 | onItemClick: (UiLayerChannels.SlackChannel) -> Unit = {}, 17 | channelVM: SlackChannelVM = hiltViewModel(), 18 | onClickAdd: () -> Unit 19 | 20 | ) { 21 | val recent = stringResource(R.string.connections) 22 | val channelsFlow = channelVM.channels.collectAsStateWithLifecycle() 23 | val channels by channelsFlow.value.collectAsStateWithLifecycle(initialValue = emptyList()) 24 | 25 | LaunchedEffect(key1 = Unit) { 26 | channelVM.allChannels() 27 | } 28 | 29 | var expandCollapseModel by remember { 30 | mutableStateOf( 31 | ExpandCollapseModel( 32 | 1, recent, 33 | needsPlusButton = false, 34 | isOpen = false 35 | ) 36 | ) 37 | } 38 | SKExpandCollapseColumn(expandCollapseModel, onItemClick, { 39 | expandCollapseModel = expandCollapseModel.copy(isOpen = it) 40 | }, channels, onClickAdd) 41 | } 42 | -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/views/SlackDirectMessages.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.views 2 | 3 | import androidx.compose.ui.res.stringResource 4 | import androidx.hilt.navigation.compose.hiltViewModel 5 | import dev.baseio.slackclone.chatcore.data.ExpandCollapseModel 6 | import dev.baseio.slackclone.uichannels.R 7 | import dev.baseio.slackclone.uichannels.SlackChannelVM 8 | import androidx.compose.runtime.* 9 | import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi 10 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 11 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 12 | 13 | @OptIn(ExperimentalLifecycleComposeApi::class) 14 | @Composable 15 | fun SlackDirectMessages( 16 | onItemClick: (UiLayerChannels.SlackChannel) -> Unit = {}, 17 | channelVM: SlackChannelVM = hiltViewModel(), 18 | onClickAdd: () -> Unit 19 | ) { 20 | val recent = stringResource(R.string.direct_messages) 21 | val channelsFlow = channelVM.channels.collectAsStateWithLifecycle() 22 | val channels by channelsFlow.value.collectAsStateWithLifecycle(initialValue = emptyList()) 23 | 24 | LaunchedEffect(key1 = Unit) { 25 | channelVM.loadDirectMessageChannels() 26 | } 27 | var expandCollapseModel by remember { 28 | mutableStateOf( 29 | ExpandCollapseModel( 30 | 1, recent, 31 | needsPlusButton = true, 32 | isOpen = false 33 | ) 34 | ) 35 | } 36 | SKExpandCollapseColumn(expandCollapseModel, onItemClick, { 37 | expandCollapseModel = expandCollapseModel.copy(isOpen = it) 38 | }, channels, onClickAdd) 39 | } 40 | -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/views/SlackRecentChannels.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.views 2 | 3 | import androidx.compose.ui.res.stringResource 4 | import androidx.hilt.navigation.compose.hiltViewModel 5 | import dev.baseio.slackclone.uichannels.SlackChannelVM 6 | import dev.baseio.slackclone.chatcore.data.ExpandCollapseModel 7 | import dev.baseio.slackclone.uichannels.R 8 | import androidx.compose.runtime.* 9 | import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi 10 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 11 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 12 | 13 | @OptIn(ExperimentalLifecycleComposeApi::class) 14 | @Composable 15 | fun SlackRecentChannels( 16 | onItemClick: (UiLayerChannels.SlackChannel) -> Unit = {}, 17 | channelVM: SlackChannelVM = hiltViewModel(), 18 | onClickAdd: () -> Unit 19 | ) { 20 | val recent = stringResource(R.string.Recent) 21 | val channelsFlow = channelVM.channels.collectAsStateWithLifecycle() 22 | val channels by channelsFlow.value.collectAsStateWithLifecycle(initialValue = emptyList()) 23 | 24 | LaunchedEffect(key1 = Unit) { 25 | channelVM.allChannels() 26 | } 27 | 28 | var expandCollapseModel by remember { 29 | mutableStateOf( 30 | ExpandCollapseModel( 31 | 1, recent, 32 | needsPlusButton = false, 33 | isOpen = true 34 | ) 35 | ) 36 | } 37 | SKExpandCollapseColumn(expandCollapseModel, onItemClick, { 38 | expandCollapseModel = expandCollapseModel.copy(isOpen = it) 39 | }, channels, onClickAdd) 40 | } -------------------------------------------------------------------------------- /feat-channels/src/main/java/dev/baseio/slackclone/uichannels/views/SlackStarredChannels.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels.views 2 | 3 | import androidx.compose.ui.res.stringResource 4 | import androidx.hilt.navigation.compose.hiltViewModel 5 | import dev.baseio.slackclone.uichannels.SlackChannelVM 6 | import dev.baseio.slackclone.chatcore.data.ExpandCollapseModel 7 | import dev.baseio.slackclone.uichannels.R 8 | import androidx.compose.runtime.* 9 | import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi 10 | import androidx.lifecycle.compose.collectAsStateWithLifecycle 11 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 12 | 13 | @OptIn(ExperimentalLifecycleComposeApi::class) 14 | @Composable 15 | fun SlackStarredChannels( 16 | onItemClick: (UiLayerChannels.SlackChannel) -> Unit = {}, 17 | channelVM: SlackChannelVM = hiltViewModel(), 18 | onClickAdd: () -> Unit 19 | ) { 20 | val recent = stringResource(R.string.starred) 21 | val channelsFlow = channelVM.channels.collectAsStateWithLifecycle() 22 | val channels by channelsFlow.value.collectAsStateWithLifecycle(initialValue = emptyList()) 23 | 24 | LaunchedEffect(key1 = Unit) { 25 | channelVM.allChannels() 26 | } 27 | 28 | 29 | LaunchedEffect(key1 = Unit) { 30 | channelVM.loadStarredChannels() 31 | } 32 | var expandCollapseModel by remember { 33 | mutableStateOf( 34 | ExpandCollapseModel( 35 | 1, recent, 36 | needsPlusButton = false, 37 | isOpen = false 38 | ) 39 | ) 40 | } 41 | SKExpandCollapseColumn(expandCollapseModel, onItemClick, { 42 | expandCollapseModel = expandCollapseModel.copy(isOpen = it) 43 | }, channels, onClickAdd) 44 | } -------------------------------------------------------------------------------- /feat-channels/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Create 4 | Name 5 | Make Private 6 | When a channel is set to private, it can only be viewed or joined by invitation. 7 | Share outside organization 8 | Share this channel with people from other companies or teams, and work together right in Slack. 9 | This can\'t be undone. A private channel cannot be made public later on. 10 | -------------------------------------------------------------------------------- /feat-channels/src/test/java/dev/baseio/slackclone/uichannels/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichannels 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 | } -------------------------------------------------------------------------------- /feat-chat/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feat-chat/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/feat-chat/consumer-rules.pro -------------------------------------------------------------------------------- /feat-chat/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 -------------------------------------------------------------------------------- /feat-chat/src/androidTest/java/dev/baseio/slackclone/uichat/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichat 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.slackclone.uichat.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feat-chat/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /feat-chat/src/main/java/dev/baseio/slackclone/uichat/chatthread/ChatScreenUI.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichat.chatthread 2 | 3 | import androidx.compose.animation.* 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material.* 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.* 8 | import androidx.compose.runtime.* 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.ExperimentalComposeUiApi 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.unit.dp 13 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 14 | import dev.baseio.slackclone.chatcore.views.SlackChannelItem 15 | import dev.baseio.slackclone.commonui.material.SlackSurfaceAppBar 16 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 17 | import dev.baseio.slackclone.commonui.theme.SlackCloneTheme 18 | import dev.baseio.slackclone.uichat.chatthread.composables.ChatScreenContent 19 | 20 | @OptIn( 21 | androidx.constraintlayout.compose.ExperimentalMotionApi::class 22 | ) 23 | @Composable 24 | fun ChatScreenUI( 25 | modifier: Modifier, 26 | slackChannel: UiLayerChannels.SlackChannel, 27 | onBackClick: () -> Unit, 28 | viewModel: ChatScreenVM 29 | ) { 30 | val scaffoldState = rememberScaffoldState() 31 | SideEffect { 32 | viewModel.requestFetch(slackChannel) 33 | } 34 | SlackCloneTheme { 35 | Scaffold( 36 | backgroundColor = SlackCloneColorProvider.colors.uiBackground, 37 | contentColor = SlackCloneColorProvider.colors.textSecondary, 38 | modifier = modifier 39 | .statusBarsPadding(), 40 | scaffoldState = scaffoldState, 41 | snackbarHost = { 42 | scaffoldState.snackbarHostState 43 | }, 44 | topBar = { 45 | ChatAppBar(onBackClick, slackChannel) 46 | } 47 | ) { innerPadding -> 48 | Box( 49 | modifier = Modifier 50 | .padding(innerPadding) 51 | ) { 52 | ChatScreenContent(viewModel) 53 | } 54 | } 55 | } 56 | 57 | } 58 | 59 | 60 | @Composable 61 | private fun ChatAppBar(onBackClick: () -> Unit, slackChannel: UiLayerChannels.SlackChannel) { 62 | SlackSurfaceAppBar(backgroundColor = SlackCloneColorProvider.colors.appBarColor) { 63 | IconButton(onClick = { onBackClick() }) { 64 | Icon( 65 | imageVector = Icons.Default.ArrowBack, 66 | contentDescription = null, 67 | tint = SlackCloneColorProvider.colors.appBarIconColor, 68 | modifier = Modifier.size(24.dp) 69 | ) 70 | } 71 | Column( 72 | Modifier.weight(1f), 73 | verticalArrangement = Arrangement.Center, 74 | horizontalAlignment = Alignment.CenterHorizontally 75 | ) { 76 | SlackChannelItem( 77 | slackChannel = slackChannel, 78 | textColor = SlackCloneColorProvider.colors.appBarTextTitleColor 79 | ) {} 80 | } 81 | IconButton(onClick = { }) { 82 | Icon( 83 | imageVector = Icons.Default.Call, 84 | contentDescription = null, 85 | tint = SlackCloneColorProvider.colors.appBarIconColor, 86 | modifier = Modifier 87 | .size(24.dp) 88 | ) 89 | } 90 | } 91 | } 92 | 93 | fun lock() = "\uD83D\uDD12" 94 | 95 | enum class BoxState { Collapsed, Expanded } -------------------------------------------------------------------------------- /feat-chat/src/main/java/dev/baseio/slackclone/uichat/chatthread/ChatScreenVM.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichat.chatthread 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import androidx.paging.PagingData 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 8 | import dev.baseio.slackclone.domain.usecases.chat.UseCaseFetchMessages 9 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 10 | import dev.baseio.slackclone.domain.usecases.chat.UseCaseSendMessage 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.MutableStateFlow 13 | import kotlinx.coroutines.launch 14 | import java.util.* 15 | import javax.inject.Inject 16 | 17 | @HiltViewModel 18 | class ChatScreenVM @Inject constructor( 19 | private val useCaseFetchMessages: UseCaseFetchMessages, 20 | private val useCaseSendMessage: UseCaseSendMessage 21 | ) : ViewModel() { 22 | var channel: UiLayerChannels.SlackChannel? = null 23 | var chatMessagesFlow = MutableStateFlow>?>(null) 24 | var message = MutableStateFlow("") 25 | var chatBoxState = MutableStateFlow(BoxState.Collapsed) 26 | 27 | fun requestFetch(slackChannel: UiLayerChannels.SlackChannel) { 28 | this.channel = slackChannel 29 | chatMessagesFlow.value = useCaseFetchMessages.performStreaming(slackChannel.uuid) 30 | } 31 | 32 | fun sendMessage(search: String) { 33 | if (search.isNotEmpty() && channel?.uuid != null) { 34 | viewModelScope.launch { 35 | val message = DomainLayerMessages.SlackMessage( 36 | UUID.randomUUID().toString(), 37 | channel!!.uuid!!, 38 | search, 39 | UUID.randomUUID().toString(), 40 | "Anmol Verma", 41 | System.currentTimeMillis(), 42 | System.currentTimeMillis(), 43 | ) 44 | useCaseSendMessage.perform(message) 45 | } 46 | message.value = "" 47 | chatBoxState.value = BoxState.Collapsed 48 | } 49 | } 50 | 51 | fun switchChatBoxState() { 52 | chatBoxState.value = 53 | if (chatBoxState.value == BoxState.Collapsed) BoxState.Expanded else BoxState.Collapsed 54 | 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /feat-chat/src/main/java/dev/baseio/slackclone/uichat/chatthread/composables/ChatMessage.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichat.chatthread.composables 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material.ExperimentalMaterialApi 6 | import androidx.compose.material.ListItem 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.text.font.FontWeight 12 | import androidx.compose.ui.unit.dp 13 | import dev.baseio.slackclone.common.extensions.calendar 14 | import dev.baseio.slackclone.common.extensions.formattedTime 15 | import dev.baseio.slackclone.commonui.reusable.SlackImageBox 16 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 17 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 18 | import dev.baseio.slackclone.domain.model.message.DomainLayerMessages 19 | import java.text.SimpleDateFormat 20 | import java.util.* 21 | 22 | @OptIn(ExperimentalMaterialApi::class) 23 | @Composable 24 | fun ChatMessage(message: DomainLayerMessages.SlackMessage) { 25 | ListItem(icon = { 26 | SlackImageBox(Modifier.size(48.dp), imageUrl = "http://placekitten.com/200/300") 27 | }, modifier = Modifier.padding(2.dp), secondaryText = { 28 | ChatMedia(message) 29 | }, text = { 30 | ChatUserDateTime(message) 31 | }) 32 | } 33 | 34 | @Composable 35 | fun ChatMedia(message: DomainLayerMessages.SlackMessage) { 36 | Column { 37 | Text( 38 | message.message, 39 | style = SlackCloneTypography.subtitle2.copy( 40 | color = SlackCloneColorProvider.colors.textSecondary 41 | ), modifier = Modifier.padding(4.dp) 42 | ) 43 | } 44 | 45 | } 46 | 47 | @Composable 48 | fun ChatUserDateTime(message: DomainLayerMessages.SlackMessage) { 49 | Row(verticalAlignment = Alignment.CenterVertically) { 50 | Text( 51 | message.createdBy + " \uD83C\uDF34", 52 | style = SlackCloneTypography.subtitle1.copy( 53 | fontWeight = FontWeight.Bold, 54 | color = SlackCloneColorProvider.colors.textPrimary 55 | ), modifier = Modifier.padding(4.dp) 56 | ) 57 | Text( 58 | message.createdDate.calendar().formattedTime(), 59 | style = SlackCloneTypography.overline.copy( 60 | color = SlackCloneColorProvider.colors.textSecondary.copy(alpha = 0.8f) 61 | ), modifier = Modifier.padding(4.dp) 62 | ) 63 | } 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /feat-chat/src/main/java/dev/baseio/slackclone/uichat/newchat/NewChatThreadVM.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichat.newchat 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.paging.map 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 7 | import dev.baseio.slackclone.domain.mappers.UiModelMapper 8 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 9 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseSearchChannel 10 | import dev.baseio.slackclone.navigator.ComposeNavigator 11 | import dev.baseio.slackclone.navigator.NavigationKeys 12 | import dev.baseio.slackclone.navigator.SlackScreen 13 | import kotlinx.coroutines.flow.MutableStateFlow 14 | import kotlinx.coroutines.flow.map 15 | import javax.inject.Inject 16 | 17 | @HiltViewModel 18 | class NewChatThreadVM @Inject constructor( 19 | private val composeNavigator: ComposeNavigator, 20 | private val ucFetchChannels: UseCaseSearchChannel, 21 | private val chatPresentationMapper: UiModelMapper 22 | ) : 23 | ViewModel() { 24 | 25 | val search = MutableStateFlow("") 26 | var users = MutableStateFlow(flow("")) 27 | 28 | private fun flow(search: String) = ucFetchChannels.performStreaming(search).map { channels -> 29 | channels.map { channel -> 30 | chatPresentationMapper.mapToPresentation(channel) 31 | } 32 | } 33 | 34 | fun search(newValue: String) { 35 | search.value = newValue 36 | users.value = flow(newValue) 37 | } 38 | 39 | fun navigate(channel: UiLayerChannels.SlackChannel) { 40 | composeNavigator.navigateBackWithResult( 41 | NavigationKeys.navigateChannel, 42 | channel.uuid!!, 43 | SlackScreen.Dashboard.name 44 | ) 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /feat-chat/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Search for a channel or conversation 4 | New Message 5 | Clear 6 | -------------------------------------------------------------------------------- /feat-chat/src/test/java/dev/baseio/slackclone/uichat/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uichat 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 | } -------------------------------------------------------------------------------- /feat-chatcore/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feat-chatcore/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 | id(BuildPlugins.KOTLIN_PARCELABLE_PLUGIN) 7 | id("org.jlleitschuh.gradle.ktlint") 8 | } 9 | 10 | android { 11 | compileSdk = ProjectProperties.COMPILE_SDK 12 | 13 | defaultConfig { 14 | minSdk = (ProjectProperties.MIN_SDK) 15 | targetSdk = (ProjectProperties.TARGET_SDK) 16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | getByName("release") { 21 | isMinifyEnabled = false 22 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 23 | } 24 | } 25 | 26 | buildFeatures { 27 | compose = true 28 | } 29 | 30 | composeOptions { 31 | kotlinCompilerExtensionVersion = Lib.Android.COMPOSE_COMPILER_VERSION 32 | } 33 | packagingOptions { 34 | resources.excludes.add("META-INF/LICENSE.txt") 35 | resources.excludes.add("META-INF/NOTICE.txt") 36 | resources.excludes.add("LICENSE.txt") 37 | resources.excludes.add("/META-INF/{AL2.0,LGPL2.1}") 38 | } 39 | 40 | compileOptions { 41 | sourceCompatibility = JavaVersion.VERSION_1_8 42 | targetCompatibility = JavaVersion.VERSION_1_8 43 | } 44 | 45 | kotlinOptions { 46 | jvmTarget = "1.8" 47 | } 48 | 49 | } 50 | 51 | // Required for annotation processing plugins like Dagger 52 | kapt { 53 | generateStubs = true 54 | correctErrorTypes = true 55 | } 56 | 57 | dependencies { 58 | /*Kotlin*/ 59 | implementation(project(":data")) 60 | implementation(project(":domain")) 61 | implementation(project(":common")) 62 | implementation(project(":commonui")) 63 | 64 | api(Lib.Android.COMPOSE_UI) 65 | api(Lib.Android.COIL_COMPOSE) 66 | api(Lib.Android.COMPOSE_MATERIAL) 67 | implementation(Lib.Android.RUNTIME_COMPOSE) 68 | implementation(Lib.Android.ACCOMPANIST_SYSTEM_UI_CONTROLLER) 69 | api(Lib.Android.COMPOSE_UI) 70 | api(Lib.Android.COMPOSE_TOOLING) 71 | implementation(Lib.Android.COIL_COMPOSE) 72 | debugApi(Lib.Android.COMPOSE_DEBUG_TOOLING) 73 | api(Lib.Android.ACTIVITY_COMPOSE) 74 | api(Lib.Android.CONSTRAINT_LAYOUT_COMPOSE) 75 | implementation(Lib.Paging.PAGING_3) 76 | implementation(Lib.Paging.PAGING_COMPOSE) 77 | 78 | api(Lib.Android.APP_COMPAT) 79 | api(Lib.Kotlin.KTX_CORE) 80 | 81 | /*DI*/ 82 | api(Lib.Di.hiltAndroid) 83 | api(Lib.Di.hiltNavigationCompose) 84 | 85 | kapt(Lib.Di.hiltCompiler) 86 | kapt(Lib.Di.hiltAndroidCompiler) 87 | 88 | /* Logger */ 89 | api(Lib.Logger.TIMBER) 90 | /* Async */ 91 | api(Lib.Async.COROUTINES) 92 | api(Lib.Async.COROUTINES_ANDROID) 93 | 94 | testImplementation(TestLib.JUNIT) 95 | testImplementation(TestLib.CORE_TEST) 96 | testImplementation(TestLib.ANDROID_JUNIT) 97 | testImplementation(TestLib.ARCH_CORE) 98 | testImplementation(TestLib.MOCK_WEB_SERVER) 99 | testImplementation(TestLib.ROBO_ELECTRIC) 100 | testImplementation(TestLib.COROUTINES) 101 | testImplementation(TestLib.MOCKK) 102 | testImplementation(TestLib.TURBINE) 103 | } -------------------------------------------------------------------------------- /feat-chatcore/src/androidTest/java/dev/baseio/slackclone/chatcore/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.chatcore 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.slackclone.chatcore.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feat-chatcore/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /feat-chatcore/src/main/java/dev/baseio/slackclone/chatcore/ChannelUIModelMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.chatcore 2 | 3 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 4 | import dev.baseio.slackclone.domain.mappers.UiModelMapper 5 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 6 | import javax.inject.Inject 7 | 8 | class ChannelUIModelMapper @Inject constructor() : 9 | UiModelMapper { 10 | override fun mapToPresentation(model: DomainLayerChannels.SlackChannel): UiLayerChannels.SlackChannel { 11 | return UiLayerChannels.SlackChannel( 12 | model.name, 13 | model.isPrivate, 14 | model.uuid, 15 | model.createdDate, 16 | model.modifiedDate, 17 | model.isMuted, 18 | model.isOneToOne, 19 | model.avatarUrl 20 | ) 21 | } 22 | 23 | override fun mapToDomain(modelItem: UiLayerChannels.SlackChannel): DomainLayerChannels.SlackChannel { 24 | TODO("Not yet implemented") 25 | } 26 | } -------------------------------------------------------------------------------- /feat-chatcore/src/main/java/dev/baseio/slackclone/chatcore/data/ChatPresentation.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.chatcore.data 2 | 3 | interface UiLayerChannels { 4 | data class SlackChannel( 5 | val name: String?, 6 | val isPrivate: Boolean?, 7 | val uuid: String?, 8 | val createdDate: Long?, 9 | val modifiedDate: Long?, 10 | val isMuted: Boolean?, 11 | val isOneToOne: Boolean?, 12 | val pictureUrl: String? 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /feat-chatcore/src/main/java/dev/baseio/slackclone/chatcore/data/ExpandCollapseModel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.chatcore.data 2 | 3 | data class ExpandCollapseModel( 4 | val id: Int, val title: String, val needsPlusButton: Boolean, 5 | var isOpen: Boolean 6 | ) 7 | -------------------------------------------------------------------------------- /feat-chatcore/src/main/java/dev/baseio/slackclone/chatcore/injection/UiModelMapperModule.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.chatcore.injection 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import dev.baseio.slackclone.chatcore.ChannelUIModelMapper 8 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 9 | import dev.baseio.slackclone.domain.mappers.UiModelMapper 10 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 11 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | abstract class UiModelMapperModule { 17 | 18 | @Binds 19 | @Singleton 20 | abstract fun bindSlackUserChannelMapper(userChannelUiMapper: UserChannelUiMapper): UiModelMapper 21 | 22 | @Binds 23 | @Singleton 24 | abstract fun bindChannelUIModelMapper(channelUIModelMapper: ChannelUIModelMapper): UiModelMapper 25 | } 26 | -------------------------------------------------------------------------------- /feat-chatcore/src/main/java/dev/baseio/slackclone/chatcore/injection/UserChannelUiMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.chatcore.injection 2 | 3 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 4 | import dev.baseio.slackclone.domain.mappers.UiModelMapper 5 | import dev.baseio.slackclone.domain.model.users.DomainLayerUsers 6 | import javax.inject.Inject 7 | 8 | class UserChannelUiMapper @Inject constructor(): 9 | UiModelMapper { 10 | override fun mapToPresentation(model: DomainLayerUsers.SlackUser): UiLayerChannels.SlackChannel { 11 | TODO("Not yet implemented") 12 | } 13 | 14 | override fun mapToDomain(modelItem: UiLayerChannels.SlackChannel): DomainLayerUsers.SlackUser { 15 | TODO("Not yet implemented") 16 | } 17 | } -------------------------------------------------------------------------------- /feat-chatcore/src/test/java/dev/baseio/slackclone/chatcore/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.chatcore 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 | } -------------------------------------------------------------------------------- /feat-onboarding/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feat-onboarding/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/feat-onboarding/consumer-rules.pro -------------------------------------------------------------------------------- /feat-onboarding/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 -------------------------------------------------------------------------------- /feat-onboarding/src/androidTest/java/dev/baseio/slackclone/uionboarding/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uionboarding 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.slackclone.uionboarding.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feat-onboarding/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /feat-onboarding/src/main/java/dev/baseio/slackclone/uionboarding/compose/ScreenInputUI.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uionboarding.compose 2 | 3 | import androidx.compose.runtime.* 4 | import androidx.compose.ui.res.stringResource 5 | import dev.baseio.slackclone.commonui.theme.SlackCloneTheme 6 | import dev.baseio.slackclone.navigator.ComposeNavigator 7 | import dev.baseio.slackclone.uionboarding.R 8 | 9 | @Composable 10 | fun EmailAddressInputUI(composeNavigator: ComposeNavigator) { 11 | SlackCloneTheme() { 12 | CommonInputUI( 13 | composeNavigator, 14 | { modifier -> 15 | EmailInputView(modifier) 16 | }, 17 | stringResource(id = R.string.subtitle_this_email_slack) 18 | ) 19 | } 20 | } 21 | 22 | @Composable 23 | fun WorkspaceInputUI(composeNavigator: ComposeNavigator) { 24 | SlackCloneTheme() { 25 | CommonInputUI( 26 | composeNavigator, 27 | { 28 | WorkspaceInputView(it) 29 | }, 30 | stringResource(id = R.string.subtitle_this_address_slack) 31 | ) 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /feat-onboarding/src/main/java/dev/baseio/slackclone/uionboarding/nav/OnboardingNavigation.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uionboarding.nav 2 | 3 | 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.compose.composable 6 | import androidx.navigation.navigation 7 | import dev.baseio.slackclone.navigator.ComposeNavigator 8 | import dev.baseio.slackclone.navigator.SlackRoute 9 | import dev.baseio.slackclone.navigator.SlackScreen 10 | import dev.baseio.slackclone.uionboarding.compose.EmailAddressInputUI 11 | import dev.baseio.slackclone.uionboarding.compose.GettingStartedUI 12 | import dev.baseio.slackclone.uionboarding.compose.SkipTypingUI 13 | import dev.baseio.slackclone.uionboarding.compose.WorkspaceInputUI 14 | 15 | fun NavGraphBuilder.onboardingNavigation( 16 | composeNavigator: ComposeNavigator, 17 | ) { 18 | navigation( 19 | startDestination = SlackScreen.GettingStarted.name, 20 | route = SlackRoute.OnBoarding.name 21 | ) { 22 | composable(SlackScreen.GettingStarted.name) { 23 | GettingStartedUI(composeNavigator) 24 | } 25 | composable(SlackScreen.SkipTypingScreen.name) { 26 | SkipTypingUI(composeNavigator) 27 | } 28 | composable(SlackScreen.WorkspaceInputUI.name) { 29 | WorkspaceInputUI(composeNavigator) 30 | } 31 | composable(SlackScreen.EmailAddressInputUI.name) { 32 | EmailAddressInputUI(composeNavigator) 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /feat-onboarding/src/main/res/drawable-hdpi/gettingstarted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/feat-onboarding/src/main/res/drawable-hdpi/gettingstarted.png -------------------------------------------------------------------------------- /feat-onboarding/src/main/res/drawable-xxhdpi/gettingstarted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/feat-onboarding/src/main/res/drawable-xxhdpi/gettingstarted.png -------------------------------------------------------------------------------- /feat-onboarding/src/main/res/drawable-xxxhdpi/gettingstarted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/feat-onboarding/src/main/res/drawable-xxxhdpi/gettingstarted.png -------------------------------------------------------------------------------- /feat-onboarding/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is the address you use to sign in to Slack 4 | We\'ll send you an email that\'ll instantly sign you in. 5 | -------------------------------------------------------------------------------- /feat-onboarding/src/test/java/dev/baseio/slackclone/uionboarding/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uionboarding 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 | } -------------------------------------------------------------------------------- /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 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 24 21:36:02 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 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /navigator/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /navigator/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/navigator/consumer-rules.pro -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /navigator/src/androidTest/java/dev/baseio/slackclone/navigator/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.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.slackclone.navigator.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /navigator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/slackclone/navigator/ComposeNavigator.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.navigator.composenavigator 2 | 3 | import androidx.navigation.NavOptionsBuilder 4 | import androidx.navigation.navOptions 5 | import dev.baseio.slackclone.navigator.ComposeNavigationCommand 6 | import dev.baseio.slackclone.navigator.ComposeNavigator 7 | import dev.baseio.slackclone.navigator.asFlow 8 | import kotlinx.coroutines.ExperimentalCoroutinesApi 9 | import kotlinx.coroutines.flow.* 10 | import javax.inject.Inject 11 | 12 | class SlackCloneComposeNavigator @Inject constructor(): ComposeNavigator() { 13 | 14 | override fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)?) { 15 | val options = optionsBuilder?.let { navOptions(it) } 16 | navigationCommands.tryEmit(ComposeNavigationCommand.NavigateToRoute(route, options)) 17 | } 18 | 19 | override fun navigateAndClearBackStack(route: String) { 20 | navigationCommands.tryEmit(ComposeNavigationCommand.NavigateToRoute(route, navOptions { 21 | popUpTo(0) 22 | })) 23 | } 24 | 25 | override fun popUpTo(route: String, inclusive: Boolean) { 26 | navigationCommands.tryEmit(ComposeNavigationCommand.PopUpToRoute(route, inclusive)) 27 | } 28 | 29 | override fun navigateBackWithResult( 30 | key: String, 31 | result: T, 32 | route: String? 33 | ) { 34 | navigationCommands.tryEmit( 35 | ComposeNavigationCommand.NavigateUpWithResult( 36 | key = key, 37 | result = result, 38 | route = route 39 | ) 40 | ) 41 | } 42 | 43 | override fun observeResult(key: String, route: String?): Flow { 44 | return navControllerFlow 45 | .filterNotNull() 46 | .flatMapLatest { navController -> 47 | val backStackEntry = route?.let { navController.getBackStackEntry(it) } 48 | ?: navController.currentBackStackEntry 49 | 50 | backStackEntry?.savedStateHandle?.let { savedStateHandle -> 51 | savedStateHandle.getLiveData(key) 52 | .asFlow() 53 | .filter { it != null } 54 | .onEach { 55 | // Nullify the result to avoid resubmitting it 56 | savedStateHandle.set(key, null) 57 | } 58 | } ?: emptyFlow() 59 | } 60 | } 61 | 62 | 63 | } -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/slackclone/navigator/NavigationCommand.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.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 | } -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/slackclone/navigator/NavigationKeys.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.navigator 2 | 3 | object NavigationKeys { 4 | 5 | val navigateChannel = "ChannelCreated" 6 | 7 | } -------------------------------------------------------------------------------- /navigator/src/main/java/dev/baseio/slackclone/navigator/Screens.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.navigator 2 | 3 | import androidx.navigation.NamedNavArgument 4 | import androidx.navigation.NavType 5 | import androidx.navigation.navArgument 6 | 7 | sealed class SlackScreen( 8 | val route: String, 9 | val navArguments: List = emptyList() 10 | ) { 11 | val name: String = route.appendArguments(navArguments) 12 | 13 | // onboarding 14 | object GettingStarted : SlackScreen("gettingStarted") 15 | object SkipTypingScreen : SlackScreen("SkipTypingUI") 16 | object EmailAddressInputUI : SlackScreen("EmailAddressInputUI") 17 | object WorkspaceInputUI : SlackScreen("WorkspaceInputUI") 18 | 19 | // dashboard 20 | object Dashboard : SlackScreen( 21 | "Dashboard", 22 | navArguments = listOf(navArgument("channelId") { type = NavType.StringType }) 23 | ) { 24 | fun createRoute(channelId: String) = 25 | route.replace("{${navArguments.first().name}}", channelId) 26 | } 27 | 28 | object CreateChannelsScreen : SlackScreen("CreateChannelsScreen") 29 | object CreateNewChannel : SlackScreen("CreateNewChannel") 30 | object CreateNewDM : SlackScreen("CreateNewDM") 31 | object SlackPreferences : SlackScreen("SlackPreferences") 32 | 33 | } 34 | 35 | sealed class SlackRoute(val name: String) { 36 | object OnBoarding : SlackRoute("onboarding") 37 | object Dashboard : SlackRoute("dashboard") 38 | } 39 | 40 | private fun String.appendArguments(navArguments: List): String { 41 | val mandatoryArguments = navArguments.filter { it.argument.defaultValue == null } 42 | .takeIf { it.isNotEmpty() } 43 | ?.joinToString(separator = "/", prefix = "/") { "{${it.name}}" } 44 | .orEmpty() 45 | val optionalArguments = navArguments.filter { it.argument.defaultValue != null } 46 | .takeIf { it.isNotEmpty() } 47 | ?.joinToString(separator = "&", prefix = "?") { "${it.name}={${it.name}}" } 48 | .orEmpty() 49 | return "$this$mandatoryArguments$optionalArguments" 50 | } -------------------------------------------------------------------------------- /navigator/src/test/java/dev/baseio/slackclone/navigator/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.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 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | // Root module 2 | include(":app") 3 | 4 | // Feature modules 5 | include(":feat-onboarding") 6 | include(":ui-dashboard") 7 | // Other modules 8 | include(":domain") 9 | include(":data") 10 | include(":common") 11 | include(":commonui") 12 | include(":navigator") 13 | 14 | include(":feat-chat") 15 | include(":feat-channels") 16 | include(":feat-chatcore") 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /ui-dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui-dashboard/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oianmol/SlackAndroidClone/d8dbf67e4446463a88ee81038d5624bb2ddef853/ui-dashboard/consumer-rules.pro -------------------------------------------------------------------------------- /ui-dashboard/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 -------------------------------------------------------------------------------- /ui-dashboard/src/androidTest/java/dev/baseio/slackclone/uidashboard/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard 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.slackclone.uidashboard.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /ui-dashboard/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/compose/DashboardVM.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.compose 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 8 | import dev.baseio.slackclone.domain.mappers.UiModelMapper 9 | import dev.baseio.slackclone.domain.model.channel.DomainLayerChannels 10 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseCreateChannels 11 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseFetchUsers 12 | import dev.baseio.slackclone.domain.usecases.channels.UseCaseGetChannel 13 | import dev.baseio.slackclone.navigator.ComposeNavigator 14 | import dev.baseio.slackclone.navigator.NavigationKeys 15 | import kotlinx.coroutines.flow.* 16 | import kotlinx.coroutines.launch 17 | import javax.inject.Inject 18 | 19 | @HiltViewModel 20 | class DashboardVM @Inject constructor( 21 | private val savedStateHandle: SavedStateHandle, 22 | private val composeNavigator: ComposeNavigator, 23 | private val useCaseGetChannel: UseCaseGetChannel, 24 | private val useCaseFetchUsers: UseCaseFetchUsers, 25 | private val useCaseSaveChannel: UseCaseCreateChannels, 26 | private val channelMapper: UiModelMapper 27 | ) : ViewModel() { 28 | 29 | val selectedChatChannel = MutableStateFlow(null) 30 | val isChatViewClosed = MutableStateFlow(true) 31 | 32 | init { 33 | observeChannelCreated() 34 | preloadUsers() 35 | } 36 | 37 | private fun observeChannelCreated() { 38 | composeNavigator.observeResult( 39 | NavigationKeys.navigateChannel, 40 | ).onStart { 41 | val message = savedStateHandle.get(NavigationKeys.navigateChannel) 42 | message?.let { 43 | emit(it) 44 | } 45 | }.map { 46 | useCaseGetChannel.perform(it) 47 | }.onEach { slackChannel -> 48 | navigateChatThreadForChannel(slackChannel) 49 | } 50 | .launchIn(viewModelScope) 51 | 52 | selectedChatChannel.onEach { 53 | savedStateHandle.set(NavigationKeys.navigateChannel, it?.uuid) 54 | }.launchIn(viewModelScope) 55 | } 56 | 57 | private fun navigateChatThreadForChannel(slackChannel: DomainLayerChannels.SlackChannel?) { 58 | slackChannel?.let { 59 | selectedChatChannel.value = channelMapper.mapToPresentation(it) 60 | isChatViewClosed.value = false 61 | } 62 | } 63 | 64 | private fun preloadUsers() { 65 | viewModelScope.launch { 66 | val users = useCaseFetchUsers.perform(10) 67 | useCaseSaveChannel.perform(users) 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/DirectMessagesUI.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.text.font.FontWeight 12 | import androidx.compose.ui.unit.dp 13 | import dev.baseio.slackclone.chatcore.data.UiLayerChannels 14 | import dev.baseio.slackclone.commonui.material.SlackSurfaceAppBar 15 | import dev.baseio.slackclone.commonui.theme.SlackCloneSurface 16 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 17 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 18 | import dev.baseio.slackclone.uichannels.directmessages.DMChannelsList 19 | 20 | @Composable 21 | fun DirectMessagesUI(onItemClick: (UiLayerChannels.SlackChannel) -> Unit) { 22 | SlackCloneSurface( 23 | color = SlackCloneColorProvider.colors.uiBackground, 24 | modifier = Modifier.fillMaxSize() 25 | ) { 26 | Column() { 27 | DMTopAppBar() 28 | Spacer(modifier = Modifier.height(8.dp)) 29 | JumpToText() 30 | Spacer(modifier = Modifier.height(12.dp)) 31 | DMChannelsList(onItemClick) 32 | } 33 | } 34 | } 35 | 36 | 37 | @Composable 38 | fun DMTopAppBar() { 39 | SlackSurfaceAppBar( 40 | title = { 41 | Text( 42 | text = "Direct Messages", 43 | style = SlackCloneTypography.h5.copy(color = Color.White, fontWeight = FontWeight.Bold) 44 | ) 45 | }, 46 | backgroundColor = SlackCloneColorProvider.colors.appBarColor, 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/MentionsReactionsUI.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Color 9 | import androidx.compose.ui.text.font.FontWeight 10 | import dev.baseio.slackclone.commonui.material.SlackSurfaceAppBar 11 | import dev.baseio.slackclone.commonui.theme.DarkBackground 12 | import dev.baseio.slackclone.commonui.theme.SlackCloneSurface 13 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 14 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 15 | 16 | @Composable 17 | fun MentionsReactionsUI() { 18 | SlackCloneSurface(color = SlackCloneColorProvider.colors.uiBackground, modifier = Modifier.fillMaxSize()){ 19 | Column() { 20 | MRTopAppBar() 21 | } 22 | } 23 | } 24 | 25 | @Composable 26 | private fun MRTopAppBar() { 27 | SlackSurfaceAppBar( 28 | title = { 29 | Text(text = "Mentions & Reactions", style = SlackCloneTypography.h5.copy(color = Color.White, fontWeight = FontWeight.Bold)) 30 | }, 31 | backgroundColor = SlackCloneColorProvider.colors.appBarColor, 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/SearchMessagesUI.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home 2 | 3 | import androidx.compose.foundation.layout.* 4 | import androidx.compose.foundation.rememberScrollState 5 | import androidx.compose.foundation.verticalScroll 6 | import androidx.compose.material.Divider 7 | import androidx.compose.material.Text 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.filled.Clear 10 | import androidx.compose.material.icons.filled.Favorite 11 | import androidx.compose.material.icons.filled.Search 12 | import androidx.compose.material.icons.filled.ShoppingCart 13 | import androidx.compose.runtime.* 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.res.stringResource 16 | import androidx.compose.ui.text.font.FontWeight 17 | import androidx.compose.ui.unit.dp 18 | import dev.baseio.slackclone.commonui.material.SlackSurfaceAppBar 19 | import dev.baseio.slackclone.commonui.reusable.SlackListItem 20 | import dev.baseio.slackclone.commonui.theme.SlackCloneSurface 21 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 22 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 23 | import dev.baseio.slackclone.uidashboard.R 24 | import dev.baseio.slackclone.uidashboard.home.search.SearchCancel 25 | 26 | @Composable 27 | fun SearchMessagesUI() { 28 | SlackCloneSurface( 29 | color = SlackCloneColorProvider.colors.uiBackground, 30 | modifier = Modifier.fillMaxSize() 31 | ) { 32 | Column() { 33 | SearchTopAppBar() 34 | Content() 35 | } 36 | } 37 | } 38 | 39 | @Composable 40 | private fun SearchTopAppBar() { 41 | SlackSurfaceAppBar( 42 | backgroundColor = SlackCloneColorProvider.colors.appBarColor, 43 | contentPadding = PaddingValues(8.dp) 44 | ) { 45 | SearchCancel() 46 | } 47 | 48 | } 49 | 50 | @Composable 51 | private fun Content() { 52 | Column(Modifier.verticalScroll(rememberScrollState())) { 53 | SlackListItem( 54 | icon = Icons.Default.ShoppingCart, 55 | title = stringResource(R.string.browse_people) 56 | ) 57 | SlackListItem(icon = Icons.Default.Search, title = stringResource(R.string.browse_channels)) 58 | SlackListDivider() 59 | // Recent Searches 60 | SearchText(stringResource(R.string.recent_searches)) 61 | repeat(5) { 62 | SlackListItem( 63 | icon = Icons.Default.Favorite, 64 | title = "in:#android_india", 65 | trailingItem = Icons.Default.Clear 66 | ) 67 | } 68 | SlackListDivider() 69 | // Narrow Your Search 70 | SearchText(stringResource(R.string.narrow_your_search)) 71 | repeat(5) { 72 | SlackListItemTrailingView( 73 | icon = Icons.Default.Favorite, 74 | title = "from:", 75 | trailingView = { 76 | Text(text = "Ex: @zoemaxwell") 77 | } 78 | ) 79 | } 80 | } 81 | } 82 | 83 | @Composable 84 | private fun SearchText(title: String) { 85 | Text( 86 | text = title, 87 | style = SlackCloneTypography.caption.copy(fontWeight = FontWeight.SemiBold), 88 | modifier = Modifier.padding(16.dp) 89 | ) 90 | } 91 | 92 | @Composable 93 | fun SlackListDivider() { 94 | Divider(color = SlackCloneColorProvider.colors.lineColor, thickness = 0.5.dp) 95 | } 96 | 97 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/preferences/PreferencesAppBar.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home.preferences 2 | 3 | import androidx.compose.foundation.layout.padding 4 | import androidx.compose.material.Icon 5 | import androidx.compose.material.IconButton 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.painterResource 10 | import androidx.compose.ui.res.stringResource 11 | import androidx.compose.ui.unit.dp 12 | import androidx.compose.ui.unit.sp 13 | import dev.baseio.slackclone.commonui.material.SlackSurfaceAppBar 14 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 15 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 16 | import dev.baseio.slackclone.navigator.ComposeNavigator 17 | import dev.baseio.slackclone.uidashboard.R.drawable 18 | import dev.baseio.slackclone.uidashboard.R.string 19 | 20 | @Composable 21 | fun PreferencesAppBar(composeNavigator: ComposeNavigator) { 22 | SlackSurfaceAppBar( 23 | title = { 24 | Text( 25 | text = stringResource(string.preferences), 26 | style = SlackCloneTypography.subtitle1.copy( 27 | color = SlackCloneColorProvider.colors.appBarTextTitleColor 28 | ), fontSize = 16.sp 29 | ) 30 | }, 31 | navigationIcon = { 32 | IconButton( 33 | onClick = { 34 | composeNavigator.navigateUp() 35 | } 36 | ) { 37 | Icon( 38 | painterResource(id = drawable.ic_baseline_close_24), 39 | contentDescription = "close preferences", 40 | tint = SlackCloneColorProvider.colors.buttonTextColor, 41 | modifier = Modifier.padding(start = 8.dp) 42 | ) 43 | } 44 | }, 45 | backgroundColor = SlackCloneColorProvider.colors.appBarColor, 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/preferences/prefitems/ItemTypePopUp.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home.preferences.prefitems 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.interaction.MutableInteractionSource 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.material.Icon 11 | import androidx.compose.material.IconButton 12 | import androidx.compose.material.Text 13 | import androidx.compose.material.ripple.rememberRipple 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.res.painterResource 17 | import androidx.compose.ui.unit.dp 18 | import androidx.compose.ui.unit.sp 19 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 20 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 21 | import dev.baseio.slackclone.data.local.model.SlackPreferences 22 | 23 | @Composable fun ItemTypePopUp( 24 | prefItem: SlackPreferences, 25 | @DrawableRes icon: Int, 26 | onOptionsClick: (SlackPreferences) -> Unit = {}, 27 | ) { 28 | Row( 29 | modifier = Modifier 30 | .fillMaxWidth() 31 | .clickable(interactionSource = MutableInteractionSource(), indication = rememberRipple(), 32 | onClick = { 33 | onOptionsClick(prefItem) 34 | }) 35 | ) { 36 | IconButton(onClick = { 37 | }) { 38 | Icon(painter = painterResource(id = icon), contentDescription = prefItem.description) 39 | } 40 | Column( 41 | modifier = Modifier 42 | .fillMaxWidth() 43 | ) { 44 | Text( 45 | modifier = Modifier.padding(bottom = 8.dp), 46 | text = prefItem.name, style = SlackCloneTypography.body1.copy( 47 | color = SlackCloneColorProvider.colors.textPrimary 48 | ) 49 | ) 50 | Text( 51 | text = prefItem.value, maxLines = 3, 52 | style = SlackCloneTypography.body1.copy( 53 | color = SlackCloneColorProvider.colors.textPrimary, 54 | fontSize = 14.sp, 55 | ) 56 | ) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/preferences/uicomponents/ComponentStates.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home.preferences.uicomponents 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | 6 | class SwitchesStates( 7 | var timeZone: MutableState = mutableStateOf(false), 8 | var allowAnimation: MutableState = mutableStateOf(false), 9 | var underlineLinks: MutableState = mutableStateOf(false), 10 | var displayTypingIndicators: MutableState = mutableStateOf(false), 11 | var openWebPageInSlack: MutableState = mutableStateOf(false), 12 | var optimizeImageUploads: MutableState = mutableStateOf(false), 13 | var optimizeVideoUploads: MutableState = mutableStateOf(false), 14 | var showImagePreviews: MutableState = mutableStateOf(false), 15 | var callDebugging: MutableState = mutableStateOf(false) 16 | ) 17 | 18 | class ItemClickStates( 19 | var language: MutableState = mutableStateOf(false), 20 | var defaultEmoji: MutableState = mutableStateOf(false), 21 | var darkMode: MutableState = mutableStateOf(false), 22 | var resetCache: MutableState = mutableStateOf(false), 23 | var forceStop: MutableState = mutableStateOf(false), 24 | var sendFeeback: MutableState = mutableStateOf(false), 25 | var helpCenter: MutableState = mutableStateOf(false), 26 | var privacyPolicy: MutableState = mutableStateOf(false), 27 | var openSourceLicenses: MutableState = mutableStateOf(false), 28 | var version: MutableState = mutableStateOf(false) 29 | ) -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/preferences/uicomponents/DoubleOptionDialog.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home.preferences.uicomponents 2 | 3 | import androidx.compose.material.AlertDialog 4 | import androidx.compose.material.Text 5 | import androidx.compose.material.TextButton 6 | import androidx.compose.runtime.Composable 7 | import dev.baseio.slackclone.commonui.theme.DarkBlue 8 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 9 | import dev.baseio.slackclone.commonui.theme.SlackCloneTheme 10 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 11 | 12 | @Composable 13 | fun DoubleOptionDialog( 14 | confirmButtonText: String, 15 | cancelButtonText: String, 16 | title: String, 17 | messsage: String, 18 | onDismiss: () -> Unit = {}, 19 | onConfirm: () -> Unit = {}, 20 | ) { 21 | SlackCloneTheme { 22 | AlertDialog( 23 | backgroundColor = SlackCloneColorProvider.colors.uiBackground, 24 | onDismissRequest = { 25 | }, title = { 26 | Text( 27 | text = title, 28 | style = SlackCloneTypography.body1.copy( 29 | color = SlackCloneColorProvider.colors.textPrimary, 30 | ) 31 | ) 32 | }, confirmButton = { 33 | TextButton(onClick = { onConfirm() }) { 34 | Text( 35 | confirmButtonText, style = SlackCloneTypography.body1.copy( 36 | color = DarkBlue 37 | ) 38 | ) 39 | } 40 | }, dismissButton = { 41 | TextButton(onClick = { onDismiss() }) { 42 | Text( 43 | text = cancelButtonText, style = SlackCloneTypography.body1.copy( 44 | color = DarkBlue 45 | ) 46 | ) 47 | } 48 | }, text = { 49 | Text( 50 | text = messsage, style = SlackCloneTypography.body1.copy( 51 | color = SlackCloneColorProvider.colors.textPrimary 52 | ) 53 | ) 54 | } 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/preferences/uicomponents/EmojiSkinColorDialog.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home.preferences.uicomponents 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.Row 8 | import androidx.compose.foundation.layout.Spacer 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.material.AlertDialog 12 | import androidx.compose.material.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.MutableState 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.res.painterResource 17 | import androidx.compose.ui.unit.dp 18 | import androidx.compose.ui.window.DialogProperties 19 | import dev.baseio.slackclone.commonui.theme.SlackCloneColorProvider 20 | import dev.baseio.slackclone.commonui.theme.SlackCloneTheme 21 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 22 | import dev.baseio.slackclone.uidashboard.R 23 | 24 | @Composable 25 | fun EmojiSkinColorDialog( 26 | emojisList: List, 27 | selectedEmoji: MutableState, 28 | onDismiss: () -> Unit = {}, 29 | onConfirm: () -> Unit = {}, 30 | ) { 31 | SlackCloneTheme { 32 | AlertDialog( 33 | backgroundColor = SlackCloneColorProvider.colors.uiBackground, 34 | onDismissRequest = { 35 | onDismiss() 36 | }, title = { 37 | Text( 38 | text = "Default skin tone", style = SlackCloneTypography.body1.copy( 39 | color = SlackCloneColorProvider.colors.textPrimary 40 | ) 41 | ) 42 | }, text = { 43 | Column( 44 | modifier = Modifier 45 | .fillMaxWidth() 46 | .background( 47 | color = SlackCloneColorProvider.colors.uiBackground 48 | ), 49 | verticalArrangement = Arrangement.SpaceBetween 50 | ) { 51 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { 52 | repeat(3) { 53 | Image( 54 | painterResource(id = R.drawable.ic_baseline_front_hand_24), 55 | contentDescription = "hand skin color" 56 | ) 57 | } 58 | } 59 | Spacer( 60 | modifier = Modifier 61 | .fillMaxWidth() 62 | .height(16.dp) 63 | ) 64 | Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) { 65 | repeat(3) { 66 | Image( 67 | painterResource(id = R.drawable.ic_baseline_front_hand_24), 68 | contentDescription = "hand skin color" 69 | ) 70 | } 71 | } 72 | } 73 | }, buttons = { 74 | 75 | }, properties = DialogProperties( 76 | dismissOnBackPress = true, 77 | dismissOnClickOutside = true 78 | ) 79 | ) 80 | } 81 | } -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/preferences/uicomponents/PopUpItemStates.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home.preferences.uicomponents 2 | 3 | import androidx.compose.runtime.mutableStateOf 4 | 5 | class PopUpItemStates { 6 | var resetCache = mutableStateOf("") 7 | var darkMode = mutableStateOf("") 8 | var emojiSkinColor = mutableStateOf("") 9 | var forceStopState = mutableStateOf("") 10 | } -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/home/search/SearchCancel.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.home.search 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.shape.RoundedCornerShape 7 | import androidx.compose.foundation.text.BasicTextField 8 | import androidx.compose.material.* 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.Search 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.ExperimentalComposeUiApi 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.graphics.SolidColor 17 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController 18 | import androidx.compose.ui.unit.dp 19 | import dev.baseio.slackclone.commonui.keyboard.Keyboard 20 | import dev.baseio.slackclone.commonui.keyboard.keyboardAsState 21 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography 22 | 23 | @OptIn(ExperimentalComposeUiApi::class) 24 | @Composable 25 | fun SearchCancel() { 26 | val keyboardController = LocalSoftwareKeyboardController.current 27 | 28 | Row { 29 | val isKeyboardOpen by keyboardAsState() 30 | var search by remember { mutableStateOf("") } 31 | 32 | SearchMessagesTF(modifier = Modifier.weight(1f), search) { newValue -> 33 | search = newValue 34 | } 35 | AnimatedVisibility(visible = isKeyboardOpen is Keyboard.Opened) { 36 | TextButton(onClick = { 37 | search = "" 38 | keyboardController?.hide() 39 | }) { 40 | Text( 41 | "Cancel", 42 | style = SlackCloneTypography.subtitle1.copy(color = Color.White) 43 | ) 44 | } 45 | } 46 | } 47 | } 48 | 49 | @Composable 50 | private fun SearchMessagesTF(modifier: Modifier, search: String, onValueChange: (String) -> Unit) { 51 | BasicTextField( 52 | value = search, 53 | singleLine = true, 54 | maxLines = 1, 55 | onValueChange = { newSearch -> 56 | onValueChange(newSearch) 57 | }, 58 | textStyle = SlackCloneTypography.subtitle1.copy( 59 | color = Color.White, 60 | ), 61 | decorationBox = { innerTextField -> 62 | Row( 63 | Modifier 64 | .background( 65 | color = Color.White.copy(alpha = 0.2f), 66 | shape = RoundedCornerShape(12.dp) 67 | ) 68 | .padding(8.dp), verticalAlignment = Alignment.CenterVertically 69 | ) { 70 | Icon( 71 | imageVector = Icons.Default.Search, 72 | contentDescription = null, 73 | tint = Color.White 74 | ) 75 | if (search.isEmpty()) { 76 | Text( 77 | "Search for messages and files", 78 | style = SlackCloneTypography.subtitle1.copy( 79 | color = Color.White, 80 | ), 81 | modifier = Modifier.weight(1f) 82 | ) 83 | } else { 84 | innerTextField() 85 | } 86 | 87 | } 88 | }, 89 | modifier = modifier.padding(8.dp), 90 | cursorBrush = SolidColor(Color.White), 91 | ) 92 | 93 | } 94 | 95 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/java/dev/baseio/slackclone/uidashboard/nav/dashboardNavigation.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard.nav 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.compose.composable 5 | import androidx.navigation.navigation 6 | import dev.baseio.slackclone.navigator.ComposeNavigator 7 | import dev.baseio.slackclone.navigator.SlackRoute 8 | import dev.baseio.slackclone.navigator.SlackScreen 9 | import dev.baseio.slackclone.uichannels.createsearch.CreateNewChannelUI 10 | import dev.baseio.slackclone.uichannels.createsearch.SearchCreateChannelUI 11 | import dev.baseio.slackclone.uichat.newchat.NewChatThreadScreen 12 | import dev.baseio.slackclone.uidashboard.compose.DashboardUI 13 | import dev.baseio.slackclone.uidashboard.home.preferences.PreferencesUI 14 | 15 | fun NavGraphBuilder.dashboardNavigation( 16 | composeNavigator: ComposeNavigator, 17 | ) { 18 | navigation( 19 | startDestination = SlackScreen.Dashboard.name, 20 | route = SlackRoute.Dashboard.name 21 | ) { 22 | composable(SlackScreen.Dashboard.name) { 23 | DashboardUI(composeNavigator) 24 | } 25 | composable(SlackScreen.CreateChannelsScreen.name) { 26 | SearchCreateChannelUI(composeNavigator = composeNavigator) 27 | } 28 | composable(SlackScreen.CreateNewChannel.name) { 29 | CreateNewChannelUI(composeNavigator) 30 | } 31 | composable(SlackScreen.CreateNewDM.name) { 32 | NewChatThreadScreen(composeNavigator) 33 | } 34 | composable(SlackScreen.SlackPreferences.name) { 35 | PreferencesUI(composeNavigator = composeNavigator) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_baseline_close_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_baseline_front_hand_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_broken_image_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_call_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_feed_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_feedback_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_help_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_hourglass_bottom_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_image_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_info_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_keyboard_alt_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_language_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/drawable/ic_outline_speed_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ui-dashboard/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MainActivity 3 | 4 | -------------------------------------------------------------------------------- /ui-dashboard/src/test/java/dev/baseio/slackclone/uidashboard/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package dev.baseio.slackclone.uidashboard 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 | } --------------------------------------------------------------------------------