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