├── .gitignore
├── README.MD
├── build.gradle.kts
├── capillary-kmp
├── build.gradle.kts
├── capillary_kmp.podspec
└── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── dev
│ │ └── baseio
│ │ └── security
│ │ ├── AndroidKeyStoreRsaUtils.kt
│ │ ├── AndroidSecurityProvider.kt
│ │ ├── Capillary.kt
│ │ ├── CapillaryEncryption.kt
│ │ ├── CryptoChaCha20.kt
│ │ ├── PrivateKey.kt
│ │ ├── PublicKey.kt
│ │ └── toPrivateKey.kt
│ ├── commonMain
│ └── kotlin
│ │ └── dev
│ │ └── baseio
│ │ └── security
│ │ ├── Capillary.kt
│ │ ├── CapillaryEncryption.kt
│ │ ├── CapillaryInstances.kt
│ │ ├── Extensions.kt
│ │ ├── PrivateKey.kt
│ │ └── PublicKey.kt
│ ├── iosMain
│ └── kotlin
│ │ └── dev
│ │ └── baseio
│ │ └── security
│ │ ├── Capillary.kt
│ │ ├── CapillaryEncryption.kt
│ │ ├── PrivateKey.kt
│ │ ├── PublicKey.kt
│ │ ├── extensions
│ │ └── NSDataExt.kt
│ │ └── toPrivateKey.kt
│ ├── jsMain
│ └── kotlin
│ │ └── baseio
│ │ └── security
│ │ ├── Capillary.kt
│ │ ├── CapillaryEncryption.kt
│ │ ├── ForgeExternals.kt
│ │ ├── JSKeyStoreRsaUtils.kt
│ │ ├── PrivateKey.kt
│ │ ├── PublicKey.kt
│ │ └── toPrivateKey.kt
│ └── jvmMain
│ └── kotlin
│ └── dev
│ └── baseio
│ └── security
│ ├── Capillary.kt
│ ├── CapillaryEncryption.kt
│ ├── CryptoChaCha20.kt
│ ├── JVMKeyStoreRsaUtils.kt
│ ├── PrivateKey.kt
│ ├── PublicKey.kt
│ ├── RsaEcdsaConstants.kt
│ └── toPrivateKey.kt
├── composeApp
├── build.gradle.kts
├── composeApp.podspec
├── src
│ ├── androidMain
│ │ ├── AndroidManifest.xml
│ │ ├── google-services.json
│ │ ├── kotlin
│ │ │ └── dev
│ │ │ │ └── baseio
│ │ │ │ ├── SlackAndroidActivity.kt
│ │ │ │ ├── SlackApp.kt
│ │ │ │ ├── android
│ │ │ │ ├── communication
│ │ │ │ │ ├── SKPushNotificationNotifier.kt
│ │ │ │ │ ├── SKReplyGroupMessageReceiver.kt
│ │ │ │ │ └── SlackMessagingService.kt
│ │ │ │ └── util
│ │ │ │ │ └── Utils.kt
│ │ │ │ ├── mainDispatcher.kt
│ │ │ │ ├── slackclone
│ │ │ │ ├── NativeCoroutineScope.kt
│ │ │ │ ├── commonui
│ │ │ │ │ └── reusable
│ │ │ │ │ │ ├── BarCodeAnalyser.kt
│ │ │ │ │ │ ├── QrCodeScanner.kt
│ │ │ │ │ │ └── QrCodeView.kt
│ │ │ │ ├── keyboardAsState.kt
│ │ │ │ ├── onboarding
│ │ │ │ │ └── compose
│ │ │ │ │ │ └── PlatformSideEffects.kt
│ │ │ │ ├── platform.android.kt
│ │ │ │ ├── platform.kt
│ │ │ │ ├── platformModule.kt
│ │ │ │ └── qrscanner
│ │ │ │ │ └── qrCodeGenerate.kt
│ │ │ │ ├── slackdata
│ │ │ │ ├── DriverFactory.kt
│ │ │ │ └── SKKeyValueData.kt
│ │ │ │ └── slackdomain
│ │ │ │ └── util
│ │ │ │ └── TimeUnit.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── ic_baseline_notifications_24.xml
│ │ │ ├── ic_circle.xml
│ │ │ ├── slack.png
│ │ │ └── splash_image.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── values-v21
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── splash_theme.xml
│ │ │ ├── strings.xml
│ │ │ ├── styles.xml
│ │ │ └── themes.xml
│ ├── commonMain
│ │ ├── kotlin
│ │ │ ├── PainterRes.kt
│ │ │ └── dev
│ │ │ │ ├── MainDispatcher.kt
│ │ │ │ ├── baseio
│ │ │ │ ├── grpc
│ │ │ │ │ └── GrpcCalls.kt
│ │ │ │ ├── slackclone
│ │ │ │ │ ├── App.kt
│ │ │ │ │ ├── Keyboard.kt
│ │ │ │ │ ├── KoinInit.kt
│ │ │ │ │ ├── NativeCoroutineScope.kt
│ │ │ │ │ ├── Root.kt
│ │ │ │ │ ├── SlackViewModel.kt
│ │ │ │ │ ├── channels
│ │ │ │ │ │ ├── SlackChannelComponent.kt
│ │ │ │ │ │ ├── SlackChannelVM.kt
│ │ │ │ │ │ ├── createsearch
│ │ │ │ │ │ │ ├── CreateNewChannelComponent.kt
│ │ │ │ │ │ │ ├── CreateNewChannelUI.kt
│ │ │ │ │ │ │ ├── CreateNewChannelVM.kt
│ │ │ │ │ │ │ ├── SearchChannelVM.kt
│ │ │ │ │ │ │ ├── SearchChannelsComponent.kt
│ │ │ │ │ │ │ └── SearchCreateChannelsScreen.kt
│ │ │ │ │ │ ├── directmessages
│ │ │ │ │ │ │ ├── DMChannelsList.kt
│ │ │ │ │ │ │ ├── DirectMessagesComponent.kt
│ │ │ │ │ │ │ └── DirectMessagesVM.kt
│ │ │ │ │ │ └── views
│ │ │ │ │ │ │ ├── ExpandCollapseModel.kt
│ │ │ │ │ │ │ ├── SKExpandCollapseColumn.kt
│ │ │ │ │ │ │ ├── SlackAllChannels.kt
│ │ │ │ │ │ │ └── SlackRecentChannels.kt
│ │ │ │ │ ├── chatcore
│ │ │ │ │ │ └── views
│ │ │ │ │ │ │ └── SlackChannelItem.kt
│ │ │ │ │ ├── chatmessaging
│ │ │ │ │ │ ├── chatthread
│ │ │ │ │ │ │ ├── BoxState.kt
│ │ │ │ │ │ │ ├── ChannelMembersDialog.kt
│ │ │ │ │ │ │ ├── ChatAppBar.kt
│ │ │ │ │ │ │ ├── ChatScreenComponent.kt
│ │ │ │ │ │ │ ├── ChatScreenUI.kt
│ │ │ │ │ │ │ ├── ChatViewModel.kt
│ │ │ │ │ │ │ ├── MentionsPatterns.kt
│ │ │ │ │ │ │ ├── SecurityKeysOfferUI.kt
│ │ │ │ │ │ │ ├── SecurityKeysRequestUI.kt
│ │ │ │ │ │ │ ├── SendMessageDelegate.kt
│ │ │ │ │ │ │ ├── SpanInfos.kt
│ │ │ │ │ │ │ ├── TextFieldValue.kt
│ │ │ │ │ │ │ └── composables
│ │ │ │ │ │ │ │ ├── ChatMessage.kt
│ │ │ │ │ │ │ │ ├── ChatMessageBox.kt
│ │ │ │ │ │ │ │ ├── ChatMessagesUI.kt
│ │ │ │ │ │ │ │ └── ChatScreenContent.kt
│ │ │ │ │ │ └── newchat
│ │ │ │ │ │ │ ├── NewChatThreadComponent.kt
│ │ │ │ │ │ │ ├── NewChatThreadScreen.kt
│ │ │ │ │ │ │ └── SearchCreateChannelVM.kt
│ │ │ │ │ ├── common
│ │ │ │ │ │ └── extensions
│ │ │ │ │ │ │ ├── PrimitiveExtensions.kt
│ │ │ │ │ │ │ └── TimeGranularity.kt
│ │ │ │ │ ├── commonui
│ │ │ │ │ │ ├── material
│ │ │ │ │ │ │ ├── DefaultSnackbar.kt
│ │ │ │ │ │ │ ├── SlackSurfaceAppBar.kt
│ │ │ │ │ │ │ └── toTextFieldValue.kt
│ │ │ │ │ │ ├── reusable
│ │ │ │ │ │ │ ├── MentionsPatterns.kt
│ │ │ │ │ │ │ ├── QrCodeScanner.kt
│ │ │ │ │ │ │ ├── QrCodeView.kt
│ │ │ │ │ │ │ ├── SlackDragComposableView.kt
│ │ │ │ │ │ │ ├── SlackImageBox.kt
│ │ │ │ │ │ │ ├── SlackListItem.kt
│ │ │ │ │ │ │ └── SlackOnlineBox.kt
│ │ │ │ │ │ └── theme
│ │ │ │ │ │ │ ├── Color.kt
│ │ │ │ │ │ │ ├── PraxisSurface.kt
│ │ │ │ │ │ │ ├── Shape.kt
│ │ │ │ │ │ │ ├── Theme.kt
│ │ │ │ │ │ │ └── Type.kt
│ │ │ │ │ ├── dashboard
│ │ │ │ │ │ ├── compose
│ │ │ │ │ │ │ ├── DashboardUI.kt
│ │ │ │ │ │ │ ├── SideNavigation.kt
│ │ │ │ │ │ │ └── layouts
│ │ │ │ │ │ │ │ ├── SlackDesktopLayout.kt
│ │ │ │ │ │ │ │ ├── SlackSideBarDesktop.kt
│ │ │ │ │ │ │ │ └── SlackWorkspaceLayoutDesktop.kt
│ │ │ │ │ │ ├── home
│ │ │ │ │ │ │ ├── DirectMessagesUI.kt
│ │ │ │ │ │ │ ├── HomeScreenComponent.kt
│ │ │ │ │ │ │ ├── HomeScreenUI.kt
│ │ │ │ │ │ │ ├── MentionsReactionsUI.kt
│ │ │ │ │ │ │ ├── SearchMessagesUI.kt
│ │ │ │ │ │ │ ├── UserProfileComponent.kt
│ │ │ │ │ │ │ ├── UserProfileUI.kt
│ │ │ │ │ │ │ ├── UserProfileVM.kt
│ │ │ │ │ │ │ └── search
│ │ │ │ │ │ │ │ └── SearchCancel.kt
│ │ │ │ │ │ └── vm
│ │ │ │ │ │ │ ├── DashboardVM.kt
│ │ │ │ │ │ │ ├── DefaultDashboardComponent.kt
│ │ │ │ │ │ │ ├── SideNavComponent.kt
│ │ │ │ │ │ │ ├── SideNavVM.kt
│ │ │ │ │ │ │ └── UserProfileDelegate.kt
│ │ │ │ │ ├── data
│ │ │ │ │ │ └── injection
│ │ │ │ │ │ │ └── ViewModelModule.kt
│ │ │ │ │ ├── keyboardAsState.kt
│ │ │ │ │ ├── koincomponents
│ │ │ │ │ │ └── auth
│ │ │ │ │ │ │ └── AuthKoinComponents.kt
│ │ │ │ │ ├── onboarding
│ │ │ │ │ │ ├── AuthorizeTokenComponent.kt
│ │ │ │ │ │ ├── AuthorizeTokenVM.kt
│ │ │ │ │ │ ├── GettingStartedComponent.kt
│ │ │ │ │ │ ├── GettingStartedVM.kt
│ │ │ │ │ │ ├── QrCodeDelegate.kt
│ │ │ │ │ │ ├── compose
│ │ │ │ │ │ │ ├── CommonInputUI.kt
│ │ │ │ │ │ │ ├── EmailAddInputUI.kt
│ │ │ │ │ │ │ ├── GettingStarted.kt
│ │ │ │ │ │ │ ├── PlatformSideEffects.kt
│ │ │ │ │ │ │ ├── ProcessEmailWorkspaceSendEmailUI.kt
│ │ │ │ │ │ │ ├── ProcessTokenFromDeepLink.kt
│ │ │ │ │ │ │ ├── ScreenInputUI.kt
│ │ │ │ │ │ │ ├── SlackAnimation.kt
│ │ │ │ │ │ │ ├── WorkspaceInputView.kt
│ │ │ │ │ │ │ └── WorkspaceView.kt
│ │ │ │ │ │ └── vm
│ │ │ │ │ │ │ ├── EmailMagicLinkComponent.kt
│ │ │ │ │ │ │ └── SendMagicLinkForWorkspaceViewModel.kt
│ │ │ │ │ ├── platform.common.kt
│ │ │ │ │ ├── platform.kt
│ │ │ │ │ └── qrscanner
│ │ │ │ │ │ ├── QRScannerUI.kt
│ │ │ │ │ │ ├── QrScannerMode.kt
│ │ │ │ │ │ └── qrCodeGenerate.kt
│ │ │ │ ├── slackdata
│ │ │ │ │ ├── DriverFactory.kt
│ │ │ │ │ ├── EncryptedProtoExt.common.kt
│ │ │ │ │ ├── RealCoroutineDispatcherProvider.kt
│ │ │ │ │ ├── SKKeyValueData.kt
│ │ │ │ │ ├── datasources
│ │ │ │ │ │ ├── IDataDecryptorImpl.kt
│ │ │ │ │ │ ├── IDataEncrypterImpl.kt
│ │ │ │ │ │ ├── local
│ │ │ │ │ │ │ ├── SKLocalDatabaseSourceImpl.kt
│ │ │ │ │ │ │ ├── SKLocalKeyValueSourceImpl.kt
│ │ │ │ │ │ │ ├── channels
│ │ │ │ │ │ │ │ ├── SKLocalDataSourceChannelMembersImpl.kt
│ │ │ │ │ │ │ │ ├── SKLocalDataSourceCreateChannelsImpl.kt
│ │ │ │ │ │ │ │ ├── SKLocalDataSourceReadChannelsImpl.kt
│ │ │ │ │ │ │ │ └── SlackSKLocalDataSourceChannelLastMessage.kt
│ │ │ │ │ │ │ ├── messages
│ │ │ │ │ │ │ │ ├── IMessageDecrypterImpl.kt
│ │ │ │ │ │ │ │ └── SKLocalDataSourceMessagesImpl.kt
│ │ │ │ │ │ │ ├── users
│ │ │ │ │ │ │ │ ├── SKLocalDataSourceCreateUsersImpl.kt
│ │ │ │ │ │ │ │ └── SKLocalDataSourceUsersImpl.kt
│ │ │ │ │ │ │ └── workspaces
│ │ │ │ │ │ │ │ ├── SKLocalDataSourceReadWorkspacesImpl.kt
│ │ │ │ │ │ │ │ └── SKLocalDataSourceWriteWorkspacesImpl.kt
│ │ │ │ │ │ └── remote
│ │ │ │ │ │ │ ├── auth
│ │ │ │ │ │ │ ├── SKAuthNetworkDataSourceImpl.kt
│ │ │ │ │ │ │ └── SKNetworkSaveFcmTokenImpl.kt
│ │ │ │ │ │ │ ├── channels
│ │ │ │ │ │ │ ├── SKNetworkDataSourceReadChannelMembersImpl.kt
│ │ │ │ │ │ │ ├── SKNetworkDataSourceReadChannelsImpl.kt
│ │ │ │ │ │ │ ├── SKNetworkDataSourceWriteChannelsImpl.kt
│ │ │ │ │ │ │ └── SKNetworkSourceChannelImpl.kt
│ │ │ │ │ │ │ ├── messages
│ │ │ │ │ │ │ └── SKNetworkDataSourceMessagesImpl.kt
│ │ │ │ │ │ │ ├── users
│ │ │ │ │ │ │ └── SKNetworkDataSourceReadUsersImpl.kt
│ │ │ │ │ │ │ └── workspaces
│ │ │ │ │ │ │ ├── SKNetworkDataSourceReadWorkspacesImpl.kt
│ │ │ │ │ │ │ ├── SKNetworkDataSourceWriteWorkspacesImpl.kt
│ │ │ │ │ │ │ └── SKNetworkSourceWorkspacesImpl.kt
│ │ │ │ │ ├── injection
│ │ │ │ │ │ ├── DataMappersModule.kt
│ │ │ │ │ │ ├── DataSourceModule.kt
│ │ │ │ │ │ ├── DispatcherModule.kt
│ │ │ │ │ │ ├── EncryptionModule.kt
│ │ │ │ │ │ └── UseCaseModule.kt
│ │ │ │ │ ├── mapper
│ │ │ │ │ │ ├── EntityMapper.kt
│ │ │ │ │ │ ├── SlackDMChannelMapper.kt
│ │ │ │ │ │ ├── SlackMessageMapper.kt
│ │ │ │ │ │ ├── SlackPublicChannelMapper.kt
│ │ │ │ │ │ ├── SlackUserMapper.kt
│ │ │ │ │ │ └── SlackWorkspaceMapper.kt
│ │ │ │ │ ├── qrcode
│ │ │ │ │ │ └── QrCodeDataGeneratorImpl.kt
│ │ │ │ │ └── security
│ │ │ │ │ │ └── QRCodeDataService.kt
│ │ │ │ └── slackdomain
│ │ │ │ │ ├── Constants.kt
│ │ │ │ │ ├── CoroutineDispatcherProvider.kt
│ │ │ │ │ ├── Validator.kt
│ │ │ │ │ ├── datasources
│ │ │ │ │ ├── IDataEncrypter.kt
│ │ │ │ │ ├── local
│ │ │ │ │ │ ├── SKLocalDatabaseSource.kt
│ │ │ │ │ │ ├── SKLocalKeyValueSource.kt
│ │ │ │ │ │ ├── channels
│ │ │ │ │ │ │ ├── SKLocalDataSourceChannelLastMessage.kt
│ │ │ │ │ │ │ ├── SKLocalDataSourceChannelMembers.kt
│ │ │ │ │ │ │ ├── SKLocalDataSourceCreateChannels.kt
│ │ │ │ │ │ │ └── SKLocalDataSourceReadChannels.kt
│ │ │ │ │ │ ├── messages
│ │ │ │ │ │ │ ├── IMessageDecrypter.kt
│ │ │ │ │ │ │ └── SKLocalDataSourceMessages.kt
│ │ │ │ │ │ ├── users
│ │ │ │ │ │ │ ├── SKLocalDataSourceUsers.kt
│ │ │ │ │ │ │ └── SKLocalDataSourceWriteUsers.kt
│ │ │ │ │ │ └── workspaces
│ │ │ │ │ │ │ ├── SKLocalDataSourceReadWorkspaces.kt
│ │ │ │ │ │ │ └── SKLocalDataSourceWriteWorkspaces.kt
│ │ │ │ │ └── remote
│ │ │ │ │ │ ├── auth
│ │ │ │ │ │ ├── SKAuthNetworkDataSource.kt
│ │ │ │ │ │ └── SKNetworkSaveFcmToken.kt
│ │ │ │ │ │ ├── channels
│ │ │ │ │ │ ├── SKNetworkDataSourceReadChannelMembers.kt
│ │ │ │ │ │ ├── SKNetworkDataSourceReadChannels.kt
│ │ │ │ │ │ ├── SKNetworkDataSourceWriteChannels.kt
│ │ │ │ │ │ └── SKNetworkSourceChannel.kt
│ │ │ │ │ │ ├── messages
│ │ │ │ │ │ └── SKNetworkDataSourceMessages.kt
│ │ │ │ │ │ ├── users
│ │ │ │ │ │ └── SKNetworkDataSourceReadUsers.kt
│ │ │ │ │ │ └── workspaces
│ │ │ │ │ │ ├── SKNetworkDataSourceReadWorkspaces.kt
│ │ │ │ │ │ ├── SKNetworkDataSourceWriteWorkspaces.kt
│ │ │ │ │ │ └── SKNetworkSourceWorkspaces.kt
│ │ │ │ │ ├── model
│ │ │ │ │ ├── channel
│ │ │ │ │ │ └── DomainLayerChannels.kt
│ │ │ │ │ ├── message
│ │ │ │ │ │ └── DomainLayerMessages.kt
│ │ │ │ │ ├── security
│ │ │ │ │ │ └── SKUserPublicKey.kt
│ │ │ │ │ ├── users
│ │ │ │ │ │ └── DomainLayerUsers.kt
│ │ │ │ │ └── workspaces
│ │ │ │ │ │ └── SKWorkspace.kt
│ │ │ │ │ ├── qrcode
│ │ │ │ │ └── QrCodeDataGenerator.kt
│ │ │ │ │ ├── security
│ │ │ │ │ └── IByteArraySplitter.kt
│ │ │ │ │ ├── usecases
│ │ │ │ │ ├── BaseUseCase.kt
│ │ │ │ │ ├── auth
│ │ │ │ │ │ ├── UseCaseAuthWithQrCode.kt
│ │ │ │ │ │ ├── UseCaseFetchAndSaveCurrentUser.kt
│ │ │ │ │ │ ├── UseCaseLogout.kt
│ │ │ │ │ │ └── UseCaseSaveFCMToken.kt
│ │ │ │ │ ├── channels
│ │ │ │ │ │ ├── UseCaseCreateChannel.kt
│ │ │ │ │ │ ├── UseCaseFetchAllChannels.kt
│ │ │ │ │ │ ├── UseCaseFetchAndSaveChannelMembers.kt
│ │ │ │ │ │ ├── UseCaseFetchAndSaveChannels.kt
│ │ │ │ │ │ ├── UseCaseFetchAndUpdateChangeInChannels.kt
│ │ │ │ │ │ ├── UseCaseFetchChannelCount.kt
│ │ │ │ │ │ ├── UseCaseFetchChannelsWithLastMessage.kt
│ │ │ │ │ │ ├── UseCaseFetchRecentChannels.kt
│ │ │ │ │ │ ├── UseCaseGetChannelMembers.kt
│ │ │ │ │ │ ├── UseCaseInviteUserToChannel.kt
│ │ │ │ │ │ ├── UseCaseSearchChannel.kt
│ │ │ │ │ │ └── UseCaseWorkspaceChannelRequest.kt
│ │ │ │ │ ├── chat
│ │ │ │ │ │ ├── UseCaseFetchAndSaveMessages.kt
│ │ │ │ │ │ ├── UseCaseFetchAndUpdateChangeInMessages.kt
│ │ │ │ │ │ ├── UseCaseSendMessage.kt
│ │ │ │ │ │ └── UseCaseStreamLocalMessages.kt
│ │ │ │ │ ├── users
│ │ │ │ │ │ ├── UseCaseFetchAndSaveUsers.kt
│ │ │ │ │ │ ├── UseCaseFetchAndUpdateChangeInUsers.kt
│ │ │ │ │ │ ├── UseCaseFetchChannelsWithSearch.kt
│ │ │ │ │ │ └── UseCaseFetchLocalUsers.kt
│ │ │ │ │ └── workspaces
│ │ │ │ │ │ ├── UseCaseAuthWorkspace.kt
│ │ │ │ │ │ ├── UseCaseFetchAndSaveWorkspaces.kt
│ │ │ │ │ │ ├── UseCaseGetSelectedWorkspace.kt
│ │ │ │ │ │ ├── UseCaseGetWorkspaces.kt
│ │ │ │ │ │ └── UseCaseSetLastSelectedWorkspace.kt
│ │ │ │ │ └── util
│ │ │ │ │ └── TimeUnit.kt
│ │ │ │ └── icerock
│ │ │ │ └── moko
│ │ │ │ └── paging
│ │ │ │ ├── LambdaPagedListDataSource.kt
│ │ │ │ ├── LiveDataExt.kt
│ │ │ │ ├── PagedListDataSource.kt
│ │ │ │ ├── Pagination.kt
│ │ │ │ ├── ReachEndNotifierList.kt
│ │ │ │ └── ResourceState.kt
│ │ ├── resources
│ │ │ ├── gettingstarted.png
│ │ │ └── ic_launcher_foreground.png
│ │ └── sqldelight
│ │ │ └── database
│ │ │ └── SlackDB.sq
│ ├── iosMain
│ │ └── kotlin
│ │ │ ├── Main.ios.kt
│ │ │ └── dev
│ │ │ ├── MainDispatcher.kt
│ │ │ └── baseio
│ │ │ ├── extensions
│ │ │ └── NSDataExt.kt
│ │ │ ├── slackclone
│ │ │ ├── NativeCoroutineScope.kt
│ │ │ ├── commonui
│ │ │ │ └── reusable
│ │ │ │ │ ├── QrCodeScanner.kt
│ │ │ │ │ └── QrCodeView.kt
│ │ │ ├── keyboardAsState.kt
│ │ │ ├── onboarding
│ │ │ │ └── compose
│ │ │ │ │ └── PlatformSideEffects.kt
│ │ │ ├── platform.ios.kt
│ │ │ ├── platform.kt
│ │ │ ├── platformModule.kt
│ │ │ └── qrscanner
│ │ │ │ └── qrCodeGenerate.kt
│ │ │ ├── slackdata
│ │ │ ├── DriverFactory.kt
│ │ │ └── SKKeyValueData.kt
│ │ │ └── slackdomain
│ │ │ └── util
│ │ │ ├── TimeUnit.kt
│ │ │ └── toMillis.kt
│ ├── jsMain
│ │ ├── kotlin
│ │ │ ├── baseio
│ │ │ │ ├── MainDispatcher.kt
│ │ │ │ ├── slackclone
│ │ │ │ │ ├── NativeCoroutineScope.kt
│ │ │ │ │ ├── commonui
│ │ │ │ │ │ └── reusable
│ │ │ │ │ │ │ ├── QrCodeScanner.kt
│ │ │ │ │ │ │ └── QrCodeView.kt
│ │ │ │ │ ├── keyboardAsState.kt
│ │ │ │ │ ├── onboarding
│ │ │ │ │ │ └── compose
│ │ │ │ │ │ │ └── PlatformSideEffects.kt
│ │ │ │ │ ├── platform.jvm.kt
│ │ │ │ │ ├── platform.kt
│ │ │ │ │ ├── platformModule.kt
│ │ │ │ │ └── qrscanner
│ │ │ │ │ │ └── qrCodeGenerate.kt
│ │ │ │ ├── slackdata
│ │ │ │ │ ├── DriverFactory.kt
│ │ │ │ │ └── SKKeyValueData.kt
│ │ │ │ └── slackdomain
│ │ │ │ │ └── util
│ │ │ │ │ └── TimeUnit.kt
│ │ │ └── main.kt
│ │ └── resources
│ │ │ └── index.html
│ └── jvmMain
│ │ └── kotlin
│ │ ├── UriUtil.kt
│ │ ├── dev
│ │ └── baseio
│ │ │ ├── MainDispatcher.kt
│ │ │ ├── slackclone
│ │ │ ├── NativeCoroutineScope.kt
│ │ │ ├── commonui
│ │ │ │ └── reusable
│ │ │ │ │ ├── QrCodeScanner.kt
│ │ │ │ │ └── QrCodeView.kt
│ │ │ ├── keyboardAsState.kt
│ │ │ ├── onboarding
│ │ │ │ └── compose
│ │ │ │ │ └── PlatformSideEffects.kt
│ │ │ ├── platform.jvm.kt
│ │ │ ├── platform.kt
│ │ │ ├── platformModule.kt
│ │ │ └── qrscanner
│ │ │ │ └── qrCodeGenerate.kt
│ │ │ ├── slackdata
│ │ │ ├── DriverFactory.kt
│ │ │ └── SKKeyValueData.kt
│ │ │ └── slackdomain
│ │ │ └── util
│ │ │ └── TimeUnit.kt
│ │ └── main.kt
└── webpack.config.d
│ ├── config.js
│ └── sqljs-config.js
├── envoy-custom.yml
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── iosApp
├── Podfile
├── Podfile.lock
├── iosApp.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── iosApp.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── iosApp
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Info.plist
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ └── iosApp.swift
├── server
├── .gitignore
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
│ ├── main
│ └── kotlin
│ │ ├── Main.kt
│ │ └── dev
│ │ └── baseio
│ │ └── slackserver
│ │ ├── DataSourcesModule.kt
│ │ ├── communications
│ │ ├── NotificationType.kt
│ │ ├── PNChannel.kt
│ │ ├── PNChannelMember.kt
│ │ ├── PNMessages.kt
│ │ ├── PNSender.kt
│ │ └── SlackEmailHelper.kt
│ │ ├── data
│ │ ├── impl
│ │ │ ├── AuthDataSourceImpl.kt
│ │ │ ├── ChannelMemberDataSourceImpl.kt
│ │ │ ├── ChannelsDataSourceImpl.kt
│ │ │ ├── MessagesDataSourceImpl.kt
│ │ │ ├── UserPushTokenDataSourceImpl.kt
│ │ │ ├── UsersDataSourceImpl.kt
│ │ │ └── WorkspaceDataSourceImpl.kt
│ │ ├── models
│ │ │ ├── IDataMap.kt
│ │ │ ├── SKEncryptedMessage.kt
│ │ │ ├── SKLastMessage.kt
│ │ │ ├── SKUserPublicKey.kt
│ │ │ ├── SKUserPushToken.kt
│ │ │ ├── SKUserRole.kt
│ │ │ ├── SkChannel.kt
│ │ │ ├── SkMessage.kt
│ │ │ ├── SkUser.kt
│ │ │ └── SkWorkspace.kt
│ │ └── sources
│ │ │ ├── AuthDataSource.kt
│ │ │ ├── ChannelMemberDataSource.kt
│ │ │ ├── ChannelsDataSource.kt
│ │ │ ├── MessagesDataSource.kt
│ │ │ ├── UserPushTokenDataSource.kt
│ │ │ ├── UsersDataSource.kt
│ │ │ └── WorkspaceDataSource.kt
│ │ └── services
│ │ ├── AuthService.kt
│ │ ├── AuthenticationDelegate.kt
│ │ ├── ChannelService.kt
│ │ ├── IQrCodeGenerator.kt
│ │ ├── MessagingService.kt
│ │ ├── QrCodeService.kt
│ │ ├── SlackConstants.kt
│ │ ├── UserService.kt
│ │ ├── WorkspaceService.kt
│ │ └── interceptors
│ │ └── AuthInterceptor.kt
│ └── test
│ └── kotlin
│ ├── FakeQrCodeGenerator.kt
│ └── TestQRCodeService.kt
├── settings.gradle.kts
├── slack_capillary_ios
├── .gitignore
├── Podfile
├── Podfile.lock
├── capillaryslack.podspec
├── capillaryslack.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── capillaryslack.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── capillaryslack
│ ├── Asn1Parser.swift
│ ├── ClearMessage.swift
│ ├── Data+SHA.swift
│ ├── EncryptedData.swift
│ ├── EncryptedMessage.swift
│ ├── Info-tvOS.plist
│ ├── Info.plist
│ ├── Key.swift
│ ├── Message.swift
│ ├── PrivateKey.swift
│ ├── PublicKey.swift
│ ├── Signature.swift
│ ├── StoredKey.swift
│ ├── SwiftyRSA+ObjC.swift
│ ├── SwiftyRSA.swift
│ ├── SwiftyRSAError.swift
│ ├── X509Certificate.swift
│ ├── capillaryios-Bridging-Header.h
│ ├── capillaryios.swift
│ └── capillaryslack.h
├── capillaryslackTests
│ └── capillaryslackTests.swift
├── sampleapp
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── ContentView.swift
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ └── sampleappApp.swift
├── sampleappTests
│ └── sampleappTests.swift
└── sampleappUITests
│ ├── sampleappUITests.swift
│ └── sampleappUITestsLaunchTests.swift
├── slack_generate_protos
├── .gitignore
└── build.gradle.kts
└── slack_protos
├── .gitignore
├── build.gradle.kts
└── src
└── main
└── proto
├── common.proto
└── hello.proto
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.iml
3 | .gradle
4 | .idea
5 | .DS_Store
6 | build
7 | */build
8 | captures
9 | .externalNativeBuild
10 | .cxx
11 | local.properties
12 | xcuserdata/
13 | Pods/
14 | *.jks
15 | *yarn.lock
16 | setuplocal.sh
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.multiplatform).apply(false)
3 | alias(libs.plugins.compose).apply(false)
4 | alias(libs.plugins.android.application).apply(false)
5 | alias(libs.plugins.android.library).apply(false)
6 | alias(libs.plugins.buildConfig).apply(false)
7 | alias(libs.plugins.kotlinx.serialization).apply(false)
8 | alias(libs.plugins.sqlDelight).apply(false)
9 | alias(libs.plugins.kotlin.native.cocoapods).apply(false)
10 | alias(libs.plugins.grpcKmp).apply(false)
11 | alias(libs.plugins.kotlin.parcelize).apply(false)
12 | }
13 |
--------------------------------------------------------------------------------
/capillary-kmp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/capillary-kmp/src/androidMain/kotlin/dev/baseio/security/AndroidSecurityProvider.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import android.content.Context
4 | import com.google.android.gms.common.GoogleApiAvailability
5 | import com.google.android.gms.common.GooglePlayServicesNotAvailableException
6 | import com.google.android.gms.common.GooglePlayServicesRepairableException
7 | import com.google.android.gms.security.ProviderInstaller
8 |
9 | object AndroidSecurityProvider {
10 | const val KEYSTORE_ANDROID = "AndroidKeyStore"
11 |
12 | fun initialize(context: Context) {
13 | updateAndroidSecurityProvider(context)
14 | }
15 |
16 | private fun updateAndroidSecurityProvider(context: Context) {
17 | try {
18 | ProviderInstaller.installIfNeeded(context)
19 | } catch (e: GooglePlayServicesRepairableException) {
20 | // Indicates that Google Play services is out of date, disabled, etc.
21 | e.printStackTrace()
22 | // Prompt the user to install/update/enable Google Play services.
23 | GoogleApiAvailability.getInstance()
24 | .showErrorNotification(context, e.connectionStatusCode)
25 | } catch (e: GooglePlayServicesNotAvailableException) {
26 | // Indicates a non-recoverable error; the ProviderInstaller is not able
27 | // to install an up-to-date Provider.
28 | e.printStackTrace()
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/capillary-kmp/src/androidMain/kotlin/dev/baseio/security/PrivateKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import java.security.PrivateKey
4 |
5 | actual class PrivateKey(var privateKey: PrivateKey) {
6 | actual var encoded: ByteArray = emptyArray().toByteArray() // don't return encoded on android
7 | }
8 |
--------------------------------------------------------------------------------
/capillary-kmp/src/androidMain/kotlin/dev/baseio/security/PublicKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import java.security.PublicKey
4 |
5 | actual class PublicKey(var publicKey: PublicKey) {
6 | actual var encoded: ByteArray = publicKey.encoded
7 | }
8 |
--------------------------------------------------------------------------------
/capillary-kmp/src/androidMain/kotlin/dev/baseio/security/toPrivateKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import java.security.KeyFactory
4 | import java.security.spec.PKCS8EncodedKeySpec
5 | import java.security.spec.X509EncodedKeySpec
6 |
7 | actual suspend fun ByteArray.toPrivateKey(): PrivateKey {
8 | val spec = PKCS8EncodedKeySpec(this)
9 | val kf = KeyFactory.getInstance("RSA")
10 | return PrivateKey(kf.generatePrivate(spec))
11 | }
12 |
13 | actual suspend fun ByteArray.toPublicKey(): PublicKey {
14 | return PublicKey(
15 | KeyFactory.getInstance("RSA").generatePublic(
16 | X509EncodedKeySpec(this)
17 | )
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/capillary-kmp/src/commonMain/kotlin/dev/baseio/security/Capillary.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | expect class Capillary(chainId: String) {
4 | suspend fun initialize(isTest: Boolean)
5 | suspend fun privateKey(): PrivateKey
6 | suspend fun publicKey(): PublicKey
7 | suspend fun encrypt(byteArray: ByteArray, publicKey: PublicKey): EncryptedData
8 | suspend fun decrypt(byteArray: EncryptedData, privateKey: PrivateKey): ByteArray
9 | suspend fun getPublicKeyFromBytes(publicKeyBytes: ByteArray): PublicKey
10 | }
11 |
--------------------------------------------------------------------------------
/capillary-kmp/src/commonMain/kotlin/dev/baseio/security/CapillaryEncryption.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | const val TRANSFORMATION_ASYMMETRIC = "RSA/ECB/PKCS1Padding"
4 | const val KEY_SIZE: Int = 2048
5 |
6 | typealias EncryptedData = Pair
7 |
8 | expect object CapillaryEncryption {
9 |
10 | suspend fun encrypt(
11 | plaintext: ByteArray,
12 | publicKey: PublicKey,
13 | ): EncryptedData
14 |
15 | suspend fun decrypt(
16 | encryptedData: EncryptedData,
17 | privateKey: PrivateKey,
18 | ): ByteArray
19 | }
20 |
--------------------------------------------------------------------------------
/capillary-kmp/src/commonMain/kotlin/dev/baseio/security/CapillaryInstances.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import kotlinx.coroutines.sync.Mutex
4 | import kotlinx.coroutines.sync.withLock
5 |
6 | object CapillaryInstances {
7 | private val instances = hashMapOf()
8 | private val lock = Mutex()
9 | suspend fun getInstance(chainId: String, isTest: Boolean = false): Capillary {
10 | if (instances.containsKey(chainId)) {
11 | return instances[chainId]!!
12 | }
13 | lock.withLock {
14 | if (instances.containsKey(chainId)) {
15 | return instances[chainId]!!
16 | }
17 | return Capillary(chainId).also { capillary ->
18 | capillary.initialize(isTest = isTest)
19 | instances[chainId] = capillary
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/capillary-kmp/src/commonMain/kotlin/dev/baseio/security/Extensions.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | expect suspend fun ByteArray.toPrivateKey(): PrivateKey
4 | expect suspend fun ByteArray.toPublicKey(): PublicKey
5 |
--------------------------------------------------------------------------------
/capillary-kmp/src/commonMain/kotlin/dev/baseio/security/PrivateKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | expect class PrivateKey {
4 | var encoded: ByteArray
5 | }
6 |
--------------------------------------------------------------------------------
/capillary-kmp/src/commonMain/kotlin/dev/baseio/security/PublicKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | expect class PublicKey {
4 | var encoded: ByteArray
5 | }
6 |
--------------------------------------------------------------------------------
/capillary-kmp/src/iosMain/kotlin/dev/baseio/security/CapillaryEncryption.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(BetaInteropApi::class)
2 |
3 | package dev.baseio.security
4 |
5 | import cocoapods.capillaryslack.CapillaryIOS
6 | import dev.baseio.extensions.toByteArrayFromNSData
7 | import dev.baseio.extensions.toData
8 | import kotlinx.cinterop.BetaInteropApi
9 | import kotlinx.cinterop.ExperimentalForeignApi
10 | import kotlinx.cinterop.autoreleasepool
11 | import platform.Foundation.NSData
12 |
13 | @OptIn(ExperimentalForeignApi::class)
14 | actual object CapillaryEncryption {
15 | actual suspend fun encrypt(
16 | plaintext: ByteArray,
17 | publicKey: PublicKey
18 | ): EncryptedData {
19 | autoreleasepool {
20 | val encryptedResponse =
21 | CapillaryIOS.encryptWithData(plaintext.toData(), publicKey.encoded.toData())
22 | return EncryptedData(
23 | encryptedResponse.firstItem() ?: "",
24 | encryptedResponse.secondItem() ?: ""
25 | )
26 | }
27 | }
28 |
29 | actual suspend fun decrypt(
30 | encryptedData: EncryptedData,
31 | privateKey: PrivateKey
32 | ): ByteArray {
33 | autoreleasepool {
34 | NSData
35 | return CapillaryIOS.decryptWithSymmetricKeyCiphertext(
36 | encryptedData.first, encryptedData.second,
37 | privateKey.encodedBytes.toData()
38 | )!!.toByteArrayFromNSData()
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/capillary-kmp/src/iosMain/kotlin/dev/baseio/security/PrivateKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | actual class PrivateKey(var encodedBytes: ByteArray) {
4 | actual var encoded: ByteArray = encodedBytes
5 | }
6 |
--------------------------------------------------------------------------------
/capillary-kmp/src/iosMain/kotlin/dev/baseio/security/PublicKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | actual class PublicKey(encodedBytes: ByteArray) {
4 | actual var encoded: ByteArray = encodedBytes
5 | }
6 |
--------------------------------------------------------------------------------
/capillary-kmp/src/iosMain/kotlin/dev/baseio/security/extensions/NSDataExt.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalForeignApi::class)
2 |
3 | package dev.baseio.extensions
4 |
5 | import kotlinx.cinterop.ExperimentalForeignApi
6 | import kotlinx.cinterop.addressOf
7 | import kotlinx.cinterop.pin
8 | import kotlinx.cinterop.usePinned
9 | import platform.Foundation.NSData
10 | import platform.Foundation.create
11 | import platform.posix.memcpy
12 |
13 | inline fun ByteArray.toData(offset: Int = 0, length: Int = size - offset): NSData {
14 | require(offset + length <= size) { "offset + length > size" }
15 | if (isEmpty()) return NSData()
16 | val pinned = pin()
17 | return NSData.create(pinned.addressOf(offset), length.toULong()) { _, _ -> pinned.unpin() }
18 | }
19 |
20 | fun NSData.toByteArrayFromNSData(): ByteArray {
21 | val size = length.toInt()
22 | val bytes = ByteArray(size)
23 |
24 | if (size > 0) {
25 | bytes.usePinned { pinned ->
26 | memcpy(pinned.addressOf(0), this.bytes, this.length)
27 | }
28 | }
29 |
30 | return bytes
31 | }
32 |
--------------------------------------------------------------------------------
/capillary-kmp/src/iosMain/kotlin/dev/baseio/security/toPrivateKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import dev.baseio.extensions.toByteArrayFromNSData
4 | import dev.baseio.extensions.toData
5 | import kotlinx.cinterop.ExperimentalForeignApi
6 |
7 | @OptIn(ExperimentalForeignApi::class)
8 | actual suspend fun ByteArray.toPrivateKey(): PrivateKey {
9 | return PrivateKey(
10 | cocoapods.capillaryslack.CapillaryIOS.privateKeyFromBytesWithData(this.toData())!!.toByteArrayFromNSData()
11 | )
12 | }
13 |
14 | @OptIn(ExperimentalForeignApi::class)
15 | actual suspend fun ByteArray.toPublicKey(): PublicKey {
16 | return PublicKey(
17 | cocoapods.capillaryslack.CapillaryIOS.publicKeyFromBytesWithData(this.toData())!!.toByteArrayFromNSData()
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/capillary-kmp/src/jsMain/kotlin/baseio/security/CapillaryEncryption.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import kotlin.io.encoding.Base64
4 | import kotlin.io.encoding.ExperimentalEncodingApi
5 |
6 | actual object CapillaryEncryption {
7 |
8 | actual suspend fun encrypt(
9 | plaintext: ByteArray,
10 | publicKey: PublicKey,
11 | ): EncryptedData {
12 | TODO("Pair(symmetricKeyCiphertext.base64(), payloadCiphertext.base64())")
13 | }
14 |
15 | actual suspend fun decrypt(
16 | encryptedData: EncryptedData,
17 | privateKey: PrivateKey,
18 | ): ByteArray {
19 | TODO("")
20 | /*return CryptoChaCha20.decrypt(
21 | encryptedData.second.frombase64()!!,
22 | CryptoChaCha20
23 | .secretFrom(symmetricKeyBytes)
24 | )*/
25 | }
26 | }
27 |
28 | @OptIn(ExperimentalEncodingApi::class)
29 | private fun String.frombase64(): ByteArray {
30 | return Base64.decode(this)
31 | }
32 |
33 | @OptIn(ExperimentalEncodingApi::class)
34 | private fun ByteArray.base64(): String {
35 | return Base64.encode(this)
36 | }
37 |
--------------------------------------------------------------------------------
/capillary-kmp/src/jsMain/kotlin/baseio/security/PrivateKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import baseio.security.NodeForge
4 | import baseio.security.NodeForge.pki.rsa
5 |
6 | actual class PrivateKey(var privateKey: rsa.PrivateKey) {
7 | actual var encoded: ByteArray = getEncodedPrivateKey(privateKey)
8 | }
9 |
10 | fun getEncodedPrivateKey(privateKey: rsa.PrivateKey): ByteArray {
11 | val asn1 = NodeForge.pki.privateKeyToAsn1(privateKey)
12 | val der = NodeForge.asn1.toDer(asn1).getBytes()
13 | return der.encodeToByteArray()
14 | }
--------------------------------------------------------------------------------
/capillary-kmp/src/jsMain/kotlin/baseio/security/PublicKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import baseio.security.NodeForge
4 | import baseio.security.NodeForge.pki.rsa.PublicKey
5 |
6 | actual class PublicKey(var publicKey: NodeForge.pki.rsa.PublicKey) {
7 | actual var encoded: ByteArray = getEncodedPublicKey(publicKey)
8 | }
9 |
10 | fun getEncodedPublicKey(publicKey: PublicKey): ByteArray {
11 | // Assuming publicKey is your node-forge public key object
12 | val asn1PublicKey = NodeForge.pki.publicKeyToAsn1(publicKey); // Convert to ASN.1
13 | val derPublicKey = NodeForge.asn1.toDer(asn1PublicKey).getBytes(); // Serialize ASN.1 to DER
14 |
15 | // If you need the result in a format like Base64, which is common:
16 | val base64EncodedPublicKey = NodeForge.util.encode64(derPublicKey);
17 | return base64EncodedPublicKey.encodeToByteArray()
18 | }
--------------------------------------------------------------------------------
/capillary-kmp/src/jvmMain/kotlin/dev/baseio/security/Capillary.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | actual class Capillary actual constructor(chainId: String) {
4 | val keychainId = "rsa_ecdsa_jvm$chainId"
5 |
6 | actual suspend fun initialize(isTest: Boolean) {
7 | JVMKeyStoreRsaUtils.generateKeyPair(keychainId)
8 | }
9 |
10 | actual suspend fun privateKey(): PrivateKey {
11 | return JVMKeyStoreRsaUtils.getPrivateKey(keychainId)
12 | }
13 |
14 | actual suspend fun publicKey(): PublicKey {
15 | return JVMKeyStoreRsaUtils.getPublicKey(keychainId)
16 | }
17 |
18 | actual suspend fun encrypt(byteArray: ByteArray, publicKey: PublicKey): EncryptedData {
19 | return CapillaryEncryption.encrypt(
20 | byteArray,
21 | publicKey,
22 | )
23 | }
24 |
25 | actual suspend fun decrypt(byteArray: EncryptedData, privateKey: PrivateKey): ByteArray {
26 | return CapillaryEncryption.decrypt(
27 | byteArray, privateKey,
28 | )
29 | }
30 |
31 | actual suspend fun getPublicKeyFromBytes(publicKeyBytes: ByteArray): PublicKey {
32 | return JVMKeyStoreRsaUtils.getPublicKeyFromBytes(publicKeyBytes)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/capillary-kmp/src/jvmMain/kotlin/dev/baseio/security/PrivateKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import java.security.PrivateKey
4 |
5 | actual class PrivateKey(var privateKey: PrivateKey) {
6 | actual var encoded: ByteArray = privateKey.encoded
7 | }
8 |
--------------------------------------------------------------------------------
/capillary-kmp/src/jvmMain/kotlin/dev/baseio/security/PublicKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import java.security.PublicKey
4 |
5 | actual class PublicKey(var publicKey: PublicKey) {
6 | actual var encoded: ByteArray = publicKey.encoded
7 | }
8 |
--------------------------------------------------------------------------------
/capillary-kmp/src/jvmMain/kotlin/dev/baseio/security/RsaEcdsaConstants.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
--------------------------------------------------------------------------------
/capillary-kmp/src/jvmMain/kotlin/dev/baseio/security/toPrivateKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.security
2 |
3 | import java.security.KeyFactory
4 | import java.security.spec.PKCS8EncodedKeySpec
5 | import java.security.spec.X509EncodedKeySpec
6 |
7 | actual suspend fun ByteArray.toPrivateKey(): PrivateKey {
8 | val spec = PKCS8EncodedKeySpec(this)
9 | val kf = KeyFactory.getInstance("RSA")
10 | return PrivateKey(kf.generatePrivate(spec))
11 | }
12 |
13 | actual suspend fun ByteArray.toPublicKey(): PublicKey {
14 | return PublicKey(
15 | KeyFactory.getInstance("RSA").generatePublic(
16 | X509EncodedKeySpec(this)
17 | )
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "22076706949",
4 | "project_id": "slackcomposemultiplatform",
5 | "storage_bucket": "slackcomposemultiplatform.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:22076706949:android:7b7f64f3253b0f549119ec",
11 | "android_client_info": {
12 | "package_name": "dev.baseio.slackclone"
13 | }
14 | },
15 | "oauth_client": [
16 | {
17 | "client_id": "22076706949-qfjpsjln247627m2u92d2s930flhvo5s.apps.googleusercontent.com",
18 | "client_type": 3
19 | }
20 | ],
21 | "api_key": [
22 | {
23 | "current_key": "AIzaSyDT5h3STEtvooh_nvYPmYR1_pu265S4D8s"
24 | }
25 | ],
26 | "services": {
27 | "appinvite_service": {
28 | "other_platform_oauth_client": [
29 | {
30 | "client_id": "22076706949-qfjpsjln247627m2u92d2s930flhvo5s.apps.googleusercontent.com",
31 | "client_type": 3
32 | }
33 | ]
34 | }
35 | }
36 | }
37 | ],
38 | "configuration_version": "1"
39 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/SlackApp.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.android
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import dev.baseio.security.AndroidSecurityProvider
6 | import dev.baseio.slackclone.initKoin
7 | import org.koin.core.KoinApplication
8 | import org.koin.dsl.module
9 |
10 | class SlackApp : Application() {
11 | lateinit var koinApplication: KoinApplication
12 |
13 | override fun onCreate() {
14 | super.onCreate()
15 | AndroidSecurityProvider.initialize(this)
16 | koinApplication = initKoin().also {
17 | it.modules(module { single { this@SlackApp } })
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/android/util/Utils.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.android.util
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 |
6 | fun Context.showToast(msg: String, isLongToast: Boolean = false) {
7 | Toast.makeText(this, msg, if (isLongToast) Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show()
8 | }
9 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/mainDispatcher.kt:
--------------------------------------------------------------------------------
1 | import kotlinx.coroutines.CoroutineDispatcher
2 | import kotlinx.coroutines.Dispatchers
3 |
4 | actual val mainDispatcher: CoroutineDispatcher
5 | get() = Dispatchers.Main
6 | actual val ioDispatcher: CoroutineDispatcher
7 | get() = Dispatchers.IO
8 | actual val defaultDispatcher: CoroutineDispatcher
9 | get() = Dispatchers.Default
10 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackclone/NativeCoroutineScope.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.SupervisorJob
6 | import kotlin.coroutines.CoroutineContext
7 |
8 | internal actual fun NativeCoroutineScope(context: CoroutineContext): CoroutineScope =
9 | CoroutineScope(SupervisorJob() + Dispatchers.Default + context)
10 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackclone/commonui/reusable/QrCodeView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import android.graphics.BitmapFactory
4 | import androidx.compose.foundation.Image
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.asImageBitmap
8 | import dev.baseio.slackdata.protos.KMSKQrCodeResponse
9 |
10 | @Composable
11 | internal actual fun QrCodeView(
12 | modifier: Modifier,
13 | response: KMSKQrCodeResponse
14 | ) {
15 | val byteArray = response.byteArrayList.map { it.byte.toByte() }.toByteArray()
16 | val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
17 | Image(bitmap = bitmap.asImageBitmap(), contentDescription = null, modifier = modifier)
18 | }
19 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackclone/onboarding/compose/PlatformSideEffects.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.onboarding.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.SideEffect
5 | import androidx.compose.ui.graphics.Color
6 | import dev.baseio.slackclone.commonui.theme.SlackCloneColor
7 |
8 | actual object PlatformSideEffects {
9 |
10 | @Composable
11 | internal actual fun SlackCloneColorOnPlatformUI() {
12 | }
13 |
14 | @Composable
15 | internal actual fun PlatformColors(
16 | topColor: Color,
17 | bottomColor: Color
18 | ) {
19 | SideEffect {
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackclone/platform.android.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import androidx.compose.foundation.layout.displayCutoutPadding
4 | import androidx.compose.foundation.layout.statusBarsPadding
5 | import androidx.compose.ui.Modifier
6 |
7 | actual fun Modifier.notchPadding(): Modifier = this.displayCutoutPadding().statusBarsPadding()
8 |
9 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackclone/platform.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import com.google.firebase.messaging.FirebaseMessaging
4 | import kotlin.coroutines.resume
5 | import kotlin.coroutines.resumeWithException
6 | import kotlin.coroutines.suspendCoroutine
7 |
8 | actual fun platformType(): Platform = Platform.ANDROID
9 | actual suspend fun fcmToken(): String {
10 | return suspendCoroutine { continuation ->
11 | FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
12 | if (task.isSuccessful) {
13 | continuation.resume(task.result)
14 | } else {
15 | continuation.resumeWithException(task.exception ?: RuntimeException("Unknown task exception"))
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackclone/platformModule.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import android.app.NotificationManager
4 | import android.content.Context
5 | import dev.baseio.database.SlackDB
6 | import dev.baseio.slackdata.DriverFactory
7 | import dev.baseio.slackdata.SKKeyValueData
8 | import org.koin.core.module.Module
9 | import org.koin.dsl.module
10 |
11 | actual fun platformModule(): Module {
12 | return module {
13 | single { SKKeyValueData(get()) }
14 | single { get().getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
15 | single { SlackDB.invoke(DriverFactory(get()).createDriver()) }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackclone/qrscanner/qrCodeGenerate.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.qrscanner
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.Color
5 | import com.google.zxing.BarcodeFormat
6 | import com.google.zxing.qrcode.QRCodeWriter
7 | import java.io.ByteArrayOutputStream
8 |
9 | actual suspend fun qrCodeGenerate(data: String): ByteArray {
10 | val writer = QRCodeWriter()
11 | val bitMatrix = writer.encode(data, BarcodeFormat.QR_CODE, 400, 400)
12 | val w = bitMatrix.width
13 | val h = bitMatrix.height
14 | val pixels = IntArray(w * h)
15 | for (y in 0 until h) {
16 | for (x in 0 until w) {
17 | pixels[y * w + x] = if (bitMatrix[x, y]) Color.BLACK else Color.WHITE
18 | }
19 | }
20 | val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
21 | bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
22 | val stream = ByteArrayOutputStream()
23 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
24 | val byteArray = stream.toByteArray()
25 | bitmap.recycle()
26 | return byteArray
27 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackdata/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import android.content.Context
4 | import app.cash.sqldelight.async.coroutines.synchronous
5 | import app.cash.sqldelight.db.SqlDriver
6 | import app.cash.sqldelight.driver.android.AndroidSqliteDriver
7 | import dev.baseio.database.SlackDB
8 |
9 | actual class DriverFactory(private val context: Context) {
10 | actual fun createDriver(): SqlDriver {
11 | return AndroidSqliteDriver(SlackDB.Schema.synchronous(), context, "SlackDB.db")
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/dev/baseio/slackdomain/util/TimeUnit.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.util
2 |
3 | actual enum class TimeUnit(val javaTimeUnit: java.util.concurrent.TimeUnit) {
4 | DAYS(java.util.concurrent.TimeUnit.DAYS),
5 | HOURS(java.util.concurrent.TimeUnit.HOURS),
6 | MILLISECONDS(java.util.concurrent.TimeUnit.MILLISECONDS),
7 | MINUTES(java.util.concurrent.TimeUnit.MINUTES),
8 | SECONDS(java.util.concurrent.TimeUnit.SECONDS)
9 | }
10 |
11 | actual fun TimeUnit.toMillis(duration: Long): Long {
12 | return javaTimeUnit.toMillis(duration)
13 | }
14 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable/ic_baseline_notifications_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable/ic_circle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable/slack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/drawable/slack.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable/splash_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/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 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/values/splash_theme.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MainActivity
3 | SlackClone
4 | Push Notifications
5 | Notification permission needs to be allowed to receive
6 | push notifications!
7 |
8 | %1$d Messages
9 | Enter your message
10 | Reply
11 | Message Sent!
12 |
13 |
14 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
17 |
18 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/PainterRes.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.runtime.Composable
2 | import androidx.compose.ui.graphics.painter.Painter
3 | import org.jetbrains.compose.resources.*
4 |
5 | object PainterRes {
6 | @OptIn(ExperimentalResourceApi::class)
7 | @Composable
8 | internal fun gettingStarted(): Painter {
9 | return painterResource(DrawableResource("gettingstarted.png"))
10 | }
11 |
12 | @OptIn(ExperimentalResourceApi::class)
13 | @Composable
14 | internal fun slackLogo(): Painter {
15 | return painterResource(DrawableResource("ic_launcher_foreground.png"))
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/MainDispatcher.kt:
--------------------------------------------------------------------------------
1 | import kotlinx.coroutines.CoroutineDispatcher
2 |
3 | expect val mainDispatcher: CoroutineDispatcher
4 | expect val ioDispatcher: CoroutineDispatcher
5 | expect val defaultDispatcher: CoroutineDispatcher
6 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/Keyboard.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | sealed class Keyboard {
4 | data class Opened(var height: Int) : Keyboard()
5 | object Closed : Keyboard()
6 | object HardwareKeyboard : Keyboard()
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/NativeCoroutineScope.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlin.coroutines.CoroutineContext
5 | import kotlin.coroutines.EmptyCoroutineContext
6 |
7 | internal expect fun NativeCoroutineScope(
8 | context: CoroutineContext = EmptyCoroutineContext
9 | ): CoroutineScope
10 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/SlackViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import com.arkivanov.essenty.instancekeeper.InstanceKeeper
4 | import dev.baseio.slackdomain.CoroutineDispatcherProvider
5 | import kotlinx.coroutines.SupervisorJob
6 | import kotlinx.coroutines.cancel
7 | import kotlinx.coroutines.CoroutineScope
8 |
9 | abstract class SlackViewModel(coroutineDispatcherProvider: CoroutineDispatcherProvider) :
10 | InstanceKeeper.Instance {
11 | private val job = SupervisorJob()
12 |
13 | val viewModelScope = CoroutineScope(job + coroutineDispatcherProvider.main)
14 |
15 | override fun onDestroy() {
16 | viewModelScope.cancel() // Cancel the scope when the instance is destroyed
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/channels/SlackChannelComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.channels
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 |
7 | class SlackChannelComponent(
8 | componentContext: ComponentContext,
9 | key: String
10 | ) : ComponentContext by componentContext {
11 |
12 | val viewModel = instanceKeeper.getOrCreate(key) {
13 | SlackChannelVM(
14 | getKoin().get(),
15 | getKoin().get(),
16 | getKoin().get(),
17 | getKoin().get()
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/channels/createsearch/CreateNewChannelComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.channels.createsearch
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
7 |
8 | class CreateNewChannelComponent constructor(
9 | componentContext: ComponentContext,
10 | val navigationPop: () -> Unit,
11 | val navigationWith: (DomainLayerChannels.SKChannel) -> Unit
12 | ) : ComponentContext by componentContext {
13 | fun onChannelSelected(channel: DomainLayerChannels.SKChannel) {
14 | navigationWith(channel)
15 | }
16 |
17 | val viewModel = instanceKeeper.getOrCreate {
18 | CreateNewChannelVM(getKoin().get(), getKoin().get(), getKoin().get()) {
19 | navigationWith(it)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/channels/createsearch/SearchChannelsComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.channels.createsearch
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.RootComponent
6 | import dev.baseio.slackclone.getKoin
7 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
8 |
9 | class SearchChannelsComponent constructor(
10 | componentContext: ComponentContext,
11 | val navigationPop: () -> Unit,
12 | val navigateRoot: (RootComponent.Config) -> Unit,
13 | val navigationPopWith: (DomainLayerChannels.SKChannel) -> Unit
14 |
15 | ) : ComponentContext by componentContext {
16 |
17 | val viewModel = instanceKeeper.getOrCreate {
18 | SearchChannelVM(
19 | getKoin().get(),
20 | getKoin().get(),
21 | getKoin().get(),
22 | getKoin().get()
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/channels/directmessages/DMChannelsList.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.channels.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.collectAsState
7 | import androidx.compose.runtime.getValue
8 | import dev.baseio.slackclone.chatcore.views.DMLastMessageItem
9 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
10 | import mainDispatcher
11 |
12 | @Composable
13 | internal fun DMChannelsList(
14 | onItemClick: (DomainLayerChannels.SKChannel) -> Unit,
15 | component: DirectMessagesComponent
16 | ) {
17 | val channels by component.viewModel.channels.collectAsState(mainDispatcher)
18 | val channelsFlow by channels.collectAsState(emptyList(), mainDispatcher)
19 | val listState = rememberLazyListState()
20 |
21 | LazyColumn(state = listState) {
22 | for (channelIndex in channelsFlow.indices) {
23 | val channel = channelsFlow[channelIndex]
24 |
25 | item {
26 | DMLastMessageItem({
27 | onItemClick(it)
28 | }, channel.channel, channel.message)
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/channels/directmessages/DirectMessagesComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.channels.directmessages
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 |
7 | class DirectMessagesComponent(
8 | componentContext: ComponentContext
9 | ) : ComponentContext by componentContext {
10 |
11 | val viewModel =
12 | instanceKeeper.getOrCreate { DirectMessagesVM(getKoin().get(), getKoin().get(), getKoin().get()) }
13 | }
14 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/channels/directmessages/DirectMessagesVM.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.channels.directmessages
2 |
3 | import dev.baseio.slackclone.SlackViewModel
4 | import dev.baseio.slackdomain.CoroutineDispatcherProvider
5 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
6 | import dev.baseio.slackdomain.usecases.channels.UseCaseFetchChannelsWithLastMessage
7 | import dev.baseio.slackdomain.usecases.workspaces.UseCaseGetSelectedWorkspace
8 | import kotlinx.coroutines.FlowPreview
9 | import kotlinx.coroutines.flow.Flow
10 | import kotlinx.coroutines.flow.MutableStateFlow
11 | import kotlinx.coroutines.flow.flatMapConcat
12 |
13 | class DirectMessagesVM(
14 | private val useCaseFetchChannels: UseCaseFetchChannelsWithLastMessage,
15 | private val useCaseGetSelectedWorkspace: UseCaseGetSelectedWorkspace,
16 | coroutineDispatcherProvider: CoroutineDispatcherProvider
17 | ) : SlackViewModel(coroutineDispatcherProvider) {
18 |
19 | val channels = MutableStateFlow(fetchFlow())
20 |
21 | @OptIn(FlowPreview::class)
22 | fun fetchFlow(): Flow> {
23 | return useCaseGetSelectedWorkspace.invokeFlow().flatMapConcat {
24 | useCaseFetchChannels(it!!.uuid)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/channels/views/ExpandCollapseModel.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.channels.views
2 |
3 | data class ExpandCollapseModel(
4 | val id: Int,
5 | val title: String,
6 | val needsPlusButton: Boolean,
7 | var isOpen: Boolean
8 | )
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/channels/views/SlackRecentChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.channels.views
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.collectAsState
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import dev.baseio.slackclone.channels.SlackChannelComponent
10 | import dev.baseio.slackclone.channels.SlackChannelVM
11 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
12 | import mainDispatcher
13 |
14 | @Composable
15 | internal fun SlackRecentChannels(
16 | onItemClick: (DomainLayerChannels.SKChannel) -> Unit = {},
17 | onClickAdd: () -> Unit,
18 | component: SlackChannelComponent,
19 | channelVM: SlackChannelVM = component.viewModel
20 | ) {
21 | val recent = "Recent"
22 | val channels by channelVM.loadRecentChannels().collectAsState(initial = emptyList(), mainDispatcher)
23 |
24 | var expandCollapseModel by remember {
25 | mutableStateOf(
26 | ExpandCollapseModel(
27 | 2,
28 | recent,
29 | needsPlusButton = false,
30 | isOpen = true
31 | )
32 | )
33 | }
34 | SKExpandCollapseColumn(expandCollapseModel, onItemClick, {
35 | expandCollapseModel = expandCollapseModel.copy(isOpen = it)
36 | }, channels, onClickAdd)
37 | }
38 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/chatmessaging/chatthread/BoxState.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.chatmessaging.chatthread
2 |
3 | enum class BoxState { Collapsed, Expanded }
4 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/chatmessaging/chatthread/ChatScreenComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.chatmessaging.chatthread
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 |
7 | class ChatScreenComponent(
8 | componentContext: ComponentContext,
9 | ) : ComponentContext by componentContext {
10 |
11 | val chatViewModel = instanceKeeper.getOrCreate {
12 | ChatViewModel()
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/chatmessaging/chatthread/MentionsPatterns.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.chatmessaging.chatthread
2 |
3 | object MentionsPatterns {
4 |
5 | const val HASH_TAG = "HASH"
6 | const val INVITE_TAG = "INVITE_TAG"
7 | const val URL_TAG = "URL"
8 | const val AT_THE_RATE = "AT_RATE"
9 | val urlPattern = Regex(
10 | "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" +
11 | "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" +
12 | "[\\p{Alnum}.,%_=?\\-+()\\[\\]\\*$~@!:/{};']*)",
13 | hashSetOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)
14 | )
15 |
16 | val hashTagPattern = Regex("\\B(\\#[a-zA-Z0-9._]+\\b)(?!;)")
17 | val inviteTagPattern = Regex("\\B(\\/[invite]+\\b)(?!;)")
18 | val mentionTagPattern = Regex("\\B(\\@[a-zA-Z0-9._]+\\b)(?!;)")
19 | }
20 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/chatmessaging/chatthread/SecurityKeysOfferUI.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.chatmessaging.chatthread
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 |
6 | @Composable
7 | fun SecurityKeysOfferUI(viewModel: ChatViewModel) {
8 | LaunchedEffect(Unit) {
9 | viewModel.offerPrivateKeyViaQRCode()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/chatmessaging/chatthread/SpanInfos.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.chatmessaging.chatthread
2 |
3 | data class SpanInfos(
4 | val spanText: String,
5 | val start: Int,
6 | val end: Int,
7 | val tag: String
8 | )
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/chatmessaging/chatthread/TextFieldValue.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.chatmessaging.chatthread
2 |
3 | data class TextFieldValue(
4 | val text: String = "",
5 | val selection: TextRange = TextRange.Zero,
6 | val composition: TextRange? = null
7 | )
8 |
9 | data class TextRange(
10 | val start: Int,
11 | val end: Int
12 | ) {
13 | companion object {
14 | val Zero = TextRange(0)
15 | }
16 | }
17 |
18 | fun TextRange(index: Int): TextRange = TextRange(start = index, end = index)
19 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/chatmessaging/newchat/NewChatThreadComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.chatmessaging.newchat
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
7 |
8 | class NewChatThreadComponent(
9 | componentContext: ComponentContext,
10 | val navigationPop: () -> Unit,
11 | val navigationPopWith: (DomainLayerChannels.SKChannel) -> Unit
12 | ) : ComponentContext by componentContext {
13 |
14 | val viewModel = instanceKeeper.getOrCreate {
15 | SearchCreateChannelVM(
16 | getKoin().get(),
17 | getKoin().get(),
18 | getKoin().get(),
19 | getKoin().get(),
20 | getKoin().get()
21 | ) {
22 | navigationPopWith(it)
23 | }
24 | }
25 |
26 | init {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/common/extensions/PrimitiveExtensions.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.common.extensions
2 |
3 | import kotlinx.datetime.Instant
4 | import kotlinx.datetime.TimeZone
5 | import kotlinx.datetime.toLocalDateTime
6 |
7 | fun Long.calendar(): Instant = Instant.fromEpochMilliseconds(this)
8 |
9 | fun Instant.formattedMonthDate(): String {
10 | val localDateTime = this.toLocalDateTime(TimeZone.currentSystemDefault())
11 | return "${localDateTime.date.dayOfMonth} ${localDateTime.month.name}"
12 | }
13 |
14 | fun Instant.formattedTime(): String {
15 | val localDateTime = this.toLocalDateTime(TimeZone.currentSystemDefault())
16 | return "${localDateTime.hour}:${localDateTime.minute}"
17 | }
18 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/commonui/material/toTextFieldValue.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.material
2 |
3 | import androidx.compose.ui.text.input.TextFieldValue
4 | import dev.baseio.slackclone.chatmessaging.chatthread.TextRange
5 | import androidx.compose.ui.text.TextRange as AndroidTextRange
6 |
7 | fun dev.baseio.slackclone.chatmessaging.chatthread.TextFieldValue.toTextFieldValue(): TextFieldValue {
8 | return TextFieldValue(
9 | this.text,
10 | this.selection.toCommonTextSelection(),
11 | this.composition?.toCommonComposition()
12 | )
13 | }
14 |
15 | fun TextRange.toCommonComposition(): androidx.compose.ui.text.TextRange {
16 | return androidx.compose.ui.text.TextRange(this.start, this.end)
17 | }
18 |
19 | fun TextRange.toCommonTextSelection(): androidx.compose.ui.text.TextRange {
20 | return androidx.compose.ui.text.TextRange(this.start, this.end)
21 | }
22 |
23 | fun AndroidTextRange.toCommonTextRange() = TextRange(this.start, this.end)
24 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/commonui/reusable/QrCodeScanner.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 |
6 | @Composable
7 | internal expect fun QrCodeScanner(modifier: Modifier, onQrCodeScanned: (String) -> Unit)
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/commonui/reusable/QrCodeView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import dev.baseio.slackdata.protos.KMSKQrCodeResponse
6 |
7 | @Composable
8 | internal expect fun QrCodeView(modifier: Modifier, response: KMSKQrCodeResponse)
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/commonui/reusable/SlackImageBox.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.draw.clip
7 | import androidx.compose.ui.layout.ContentScale
8 | import io.kamel.image.KamelImage
9 | import io.kamel.image.lazyPainterResource
10 |
11 | @Composable
12 | internal fun SlackImageBox(modifier: Modifier, imageUrl: String) {
13 | KamelImage(
14 | resource = lazyPainterResource(
15 | data = imageUrl
16 | ),
17 | contentDescription = null,
18 | modifier = modifier.clip(RoundedCornerShape(25)),
19 | contentScale = ContentScale.FillBounds
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/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 SlackGreen = Color(52, 120, 92, 255)
12 | val LineColorLight = Color.Black.copy(alpha = 0.4f)
13 | val LineColorDark = Color.White.copy(alpha = 0.3f)
14 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/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 | )
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/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.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | val SlackCloneTypography = Typography(
10 | body1 = TextStyle(
11 | fontWeight = FontWeight.Normal,
12 | fontSize = 16.sp
13 | ),
14 | button = TextStyle(
15 | fontWeight = FontWeight.W500,
16 | fontSize = 14.sp
17 | ),
18 | caption = TextStyle(
19 | fontWeight = FontWeight.Normal,
20 | fontSize = 12.sp
21 | )
22 | )
23 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/dashboard/compose/layouts/SlackDesktopLayout.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.dashboard.compose.layouts
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.unit.dp
7 |
8 | @Composable
9 | internal fun SlackDesktopLayout(
10 | modifier: Modifier = Modifier,
11 | sideBar: @Composable (Modifier) -> Unit,
12 | workSpaceAndChannels: @Composable (Modifier) -> Unit,
13 | content: @Composable (Modifier) -> Unit
14 | ) {
15 | Row(modifier) {
16 | sideBar(Modifier.width(80.dp).fillMaxHeight())
17 | workSpaceAndChannels(Modifier.weight(1f).fillMaxHeight())
18 | content(Modifier.weight(3f).fillMaxHeight())
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/dashboard/home/HomeScreenComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.dashboard.home
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import dev.baseio.slackdomain.usecases.workspaces.UseCaseGetSelectedWorkspace
5 | import kotlinx.coroutines.flow.*
6 |
7 | class HomeScreenComponent(
8 | componentContext: ComponentContext,
9 | private val useCaseGetSelectedWorkspace: UseCaseGetSelectedWorkspace
10 | ) : ComponentContext by componentContext {
11 |
12 | var lastSelectedWorkspace = MutableStateFlow(flow())
13 | private set
14 |
15 | fun flow() = useCaseGetSelectedWorkspace.invokeFlow()
16 | }
17 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/dashboard/home/MentionsReactionsUI.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.dashboard.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.LocalSlackCloneColor
12 | import dev.baseio.slackclone.commonui.theme.SlackCloneSurface
13 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography
14 |
15 | @Composable
16 | internal fun MentionsReactionsUI() {
17 | SlackCloneSurface(color = LocalSlackCloneColor.current.uiBackground, modifier = Modifier.fillMaxSize()) {
18 | Column {
19 | MRTopAppBar()
20 | }
21 | }
22 | }
23 |
24 | @Composable
25 | internal fun MRTopAppBar() {
26 | SlackSurfaceAppBar(
27 | title = {
28 | Text(text = "Mentions & Reactions", style = SlackCloneTypography.h5.copy(color = Color.White, fontWeight = FontWeight.Bold))
29 | },
30 | backgroundColor = LocalSlackCloneColor.current.appBarColor
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/dashboard/home/UserProfileComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.dashboard.home
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 |
7 | class UserProfileComponent(
8 | componentContext: ComponentContext,
9 | val navigateOnboardingRoot: () -> Unit
10 | ) :
11 | ComponentContext by componentContext {
12 |
13 | val viewModel = instanceKeeper.getOrCreate { UserProfileVM(getKoin().get(), getKoin().get(), navigateOnboardingRoot) }
14 | }
15 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/dashboard/home/UserProfileVM.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.dashboard.home
2 |
3 | import dev.baseio.slackclone.SlackViewModel
4 | import dev.baseio.slackclone.dashboard.vm.UserProfileDelegate
5 | import dev.baseio.slackdomain.CoroutineDispatcherProvider
6 |
7 | class UserProfileVM(
8 | private val userProfileDelegate: UserProfileDelegate,
9 | coroutineDispatcherProvider: CoroutineDispatcherProvider,
10 | navigateOnboardingRoot: () -> Unit
11 | ) : SlackViewModel(coroutineDispatcherProvider), UserProfileDelegate by userProfileDelegate {
12 | init {
13 | getCurrentUser(viewModelScope, navigateOnboardingRoot)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/dashboard/vm/SideNavComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.dashboard.vm
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 |
7 | class SideNavComponent(
8 | componentContext: ComponentContext,
9 | navigateOnboardingRoot: () -> Unit
10 | ) : ComponentContext by componentContext {
11 |
12 | val viewModel = instanceKeeper.getOrCreate {
13 | SideNavVM(
14 | getKoin().get(),
15 | getKoin().get(),
16 | getKoin().get(),
17 | getKoin().get(),
18 | getKoin().get(),
19 | navigateOnboardingRoot
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/data/injection/ViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.data.injection
2 |
3 | import dev.baseio.slackclone.chatmessaging.chatthread.SendMessageDelegate
4 | import dev.baseio.slackclone.chatmessaging.chatthread.SendMessageDelegateImpl
5 | import dev.baseio.slackclone.dashboard.vm.UserProfileDelegate
6 | import dev.baseio.slackclone.dashboard.vm.UserProfileDelegateImpl
7 | import dev.baseio.slackclone.onboarding.QrCodeDelegate
8 | import dev.baseio.slackclone.onboarding.QrCodeDelegateImpl
9 | import org.koin.dsl.module
10 |
11 | val viewModelDelegateModule = module {
12 | single {
13 | UserProfileDelegateImpl(getKoin().get(), getKoin().get())
14 | }
15 | single {
16 | SendMessageDelegateImpl(get(), get(), get())
17 | }
18 | single {
19 | QrCodeDelegateImpl(get(), get())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/keyboardAsState.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.State
5 |
6 | @Composable
7 | internal expect fun keyboardAsState(): State
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/onboarding/AuthorizeTokenComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.onboarding
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 |
7 | class AuthorizeTokenComponent(
8 | componentContext: ComponentContext,
9 | val navigateBack: () -> Unit,
10 | private val navigateDashboard: () -> Unit,
11 | private val token: String
12 | ) : ComponentContext by componentContext {
13 |
14 | val viewModel =
15 | instanceKeeper.getOrCreate {
16 | AuthorizeTokenVM(
17 | coroutineDispatcherProvider = getKoin().get(),
18 | useCaseFetchAndSaveCurrentUser = getKoin().get(),
19 | useCaseFetchAndSaveUserWorkspace = getKoin().get(),
20 | authToken = token,
21 | navigateDashboard = navigateDashboard
22 | )
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/onboarding/compose/PlatformSideEffects.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.onboarding.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.Color
5 |
6 | expect object PlatformSideEffects {
7 | @Composable
8 | internal fun SlackCloneColorOnPlatformUI()
9 |
10 | @Composable
11 | internal fun PlatformColors(topColor: Color, bottomColor: Color)
12 | }
13 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/onboarding/compose/WorkspaceInputView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.onboarding.compose
2 |
3 | import androidx.compose.material.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.text.font.FontWeight
6 | import androidx.compose.ui.text.style.TextAlign
7 | import androidx.compose.ui.text.style.TextOverflow
8 | import dev.baseio.slackclone.commonui.theme.LocalSlackCloneColor
9 | import dev.baseio.slackclone.commonui.theme.SlackCloneTypography
10 |
11 | @Composable
12 | internal fun TextHttps() {
13 | Text(
14 | text = "https://",
15 | style = textStyleField().copy(
16 | color = LocalSlackCloneColor.current.textPrimary.copy(
17 | alpha = 0.4f
18 | )
19 | )
20 | )
21 | }
22 |
23 | @Composable
24 | internal fun TextSlackCom() {
25 | Text(
26 | ".slack.com",
27 | style = textStyleField().copy(
28 | color = LocalSlackCloneColor.current.textPrimary.copy(
29 | alpha = 0.4f
30 | )
31 | ),
32 | overflow = TextOverflow.Clip,
33 | maxLines = 1
34 | )
35 | }
36 |
37 | @Composable
38 | internal fun textStyleField() = SlackCloneTypography.subtitle1.copy(
39 | color = LocalSlackCloneColor.current.textPrimary.copy(alpha = 0.7f),
40 | fontWeight = FontWeight.Normal,
41 | textAlign = TextAlign.Start
42 | )
43 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/onboarding/vm/EmailMagicLinkComponent.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.onboarding.vm
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.instancekeeper.getOrCreate
5 | import dev.baseio.slackclone.getKoin
6 |
7 | class EmailMagicLinkComponent(
8 | componentContext: ComponentContext,
9 | private val email: String,
10 | val workspace: String,
11 | private val navigateDashboard: () -> Unit,
12 | val navigateBack: () -> Unit
13 | ) : ComponentContext by componentContext {
14 |
15 | val viewModel =
16 | instanceKeeper.getOrCreate {
17 | SendMagicLinkForWorkspaceViewModel(
18 | coroutineDispatcherProvider = getKoin().get(),
19 | useCaseAuthWorkspace = getKoin().get(),
20 | useCaseSaveFCMToken = getKoin().get(),
21 | email = email,
22 | workspace = workspace
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/platform.common.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import androidx.compose.ui.Modifier
4 |
5 | expect fun Modifier.notchPadding(): Modifier
6 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/platform.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | enum class Platform {
4 | ANDROID, IOS, JVM
5 | }
6 |
7 | expect fun platformType(): Platform
8 | expect suspend fun fcmToken(): String
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/qrscanner/QrScannerMode.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.qrscanner
2 |
3 | enum class QrScannerMode {
4 | SHOW_QR_SCANNER_CAMERA, SHOW_QR_CODE_VIEW
5 | }
6 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackclone/qrscanner/qrCodeGenerate.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.qrscanner
2 |
3 | expect suspend fun qrCodeGenerate(data: String): ByteArray
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 |
5 | expect class DriverFactory {
6 | fun createDriver(): SqlDriver
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/EncryptedProtoExt.common.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import dev.baseio.slackdata.protos.KMSKEncryptedMessage
4 | import dev.baseio.slackdata.protos.kmSKEncryptedMessage
5 |
6 | fun Pair.toSKEncryptedMessage(): KMSKEncryptedMessage {
7 | return kmSKEncryptedMessage {
8 | this.first = this@toSKEncryptedMessage.first.decodeToString()
9 | this.second = this@toSKEncryptedMessage.second.decodeToString()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/RealCoroutineDispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import defaultDispatcher
4 | import dev.baseio.slackdomain.CoroutineDispatcherProvider
5 | import ioDispatcher
6 | import kotlinx.coroutines.CoroutineDispatcher
7 | import kotlinx.coroutines.Dispatchers
8 | import mainDispatcher
9 |
10 | open class RealCoroutineDispatcherProvider : CoroutineDispatcherProvider {
11 | override val main: CoroutineDispatcher by lazy { mainDispatcher }
12 | override val io: CoroutineDispatcher by lazy { ioDispatcher }
13 | override val default: CoroutineDispatcher by lazy { defaultDispatcher }
14 | override val unconfirmed: CoroutineDispatcher by lazy { Dispatchers.Unconfined }
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/SKKeyValueData.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | expect class SKKeyValueData {
4 | fun save(key: String, value: String, workspaceId: String? = null)
5 | fun get(key: String, workspaceId: String? = null): String?
6 | fun clear(workspaceId: String? = null)
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/IDataDecryptorImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources
2 |
3 | import dev.baseio.security.CapillaryEncryption
4 | import dev.baseio.security.EncryptedData
5 | import dev.baseio.security.toPrivateKey
6 | import dev.baseio.slackdomain.datasources.IDataDecryptor
7 |
8 | class IDataDecryptorImpl : IDataDecryptor {
9 | override suspend fun decrypt(byteArray: EncryptedData, privateKeyBytes: ByteArray): ByteArray {
10 | return CapillaryEncryption.decrypt(
11 | byteArray, privateKeyBytes.toPrivateKey()
12 | )
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/IDataEncrypterImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources
2 |
3 | import dev.baseio.security.CapillaryEncryption
4 | import dev.baseio.security.toPublicKey
5 | import dev.baseio.slackdomain.datasources.IDataEncrypter
6 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
7 |
8 | class IDataEncrypterImpl : IDataEncrypter {
9 | override suspend fun encrypt(
10 | byteArray: ByteArray,
11 | publicKeyBytes: ByteArray
12 | ): DomainLayerUsers.SKEncryptedMessage {
13 | val encrypted = CapillaryEncryption.encrypt(
14 | byteArray, publicKeyBytes.toPublicKey()
15 | )
16 | return DomainLayerUsers.SKEncryptedMessage(encrypted.first, encrypted.second)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/local/SKLocalDatabaseSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.local
2 |
3 | import dev.baseio.database.SlackDB
4 | import dev.baseio.slackdomain.datasources.local.SKLocalDatabaseSource
5 |
6 | class SKLocalDatabaseSourceImpl(private val slackDB: SlackDB) : SKLocalDatabaseSource {
7 | override suspend fun clear() {
8 | with(slackDB.slackDBQueries) {
9 | deleteAllMessages()
10 | deleteAllDMChannels()
11 | deleteMessages()
12 | deleteAllUsers()
13 | deleteSlackUser()
14 | deleteAllPublicChannels()
15 | deleteChannelMembers()
16 | deleteSlackWorkspaces()
17 | deleteAllMessages()
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/local/SKLocalKeyValueSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.local
2 |
3 | import dev.baseio.slackdata.SKKeyValueData
4 | import dev.baseio.slackdomain.datasources.local.SKLocalKeyValueSource
5 |
6 | class SKLocalKeyValueSourceImpl(private val skKeyValueData: SKKeyValueData) :
7 | SKLocalKeyValueSource {
8 | override fun clear(workspaceId: String?) {
9 | skKeyValueData.clear(workspaceId)
10 | }
11 |
12 | override fun get(key: String, workspaceId: String?): String? {
13 | return skKeyValueData.get(key, workspaceId)
14 | }
15 |
16 | override fun save(key: String, value: Any, workspaceId: String?) {
17 | return skKeyValueData.save(key, value as String, workspaceId)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/local/users/SKLocalDataSourceCreateUsersImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.local.users
2 |
3 | import dev.baseio.slackdomain.CoroutineDispatcherProvider
4 | import dev.baseio.slackdomain.datasources.local.users.SKLocalDataSourceUsers
5 | import dev.baseio.slackdomain.datasources.local.users.SKLocalDataSourceWriteUsers
6 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
7 | import kotlinx.coroutines.withContext
8 |
9 | class SKLocalDataSourceCreateUsersImpl(
10 | private val coroutineDispatcherProvider: CoroutineDispatcherProvider,
11 | private val skLocalDataSourceUsers: SKLocalDataSourceUsers
12 | ) : SKLocalDataSourceWriteUsers {
13 | override suspend fun saveUsers(users: List) {
14 | withContext(coroutineDispatcherProvider.io) {
15 | users.forEach {
16 | skLocalDataSourceUsers.saveUser(it)
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/local/workspaces/SKLocalDataSourceWriteWorkspacesImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.local.workspaces
2 |
3 | import dev.baseio.database.SlackDB
4 | import dev.baseio.slackdomain.CoroutineDispatcherProvider
5 | import dev.baseio.slackdomain.datasources.local.workspaces.SKLocalDataSourceWriteWorkspaces
6 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
7 | import kotlinx.coroutines.withContext
8 |
9 | class SKLocalDataSourceWriteWorkspacesImpl(
10 | private val slackDB: SlackDB,
11 | private val coroutineDispatcherProvider: CoroutineDispatcherProvider
12 | ) : SKLocalDataSourceWriteWorkspaces {
13 | override suspend fun saveWorkspaces(list: List) {
14 | return withContext(coroutineDispatcherProvider.io) {
15 | slackDB.transaction {
16 | list.map { skWorkspace ->
17 | slackDB.slackDBQueries.insertWorkspace(
18 | skWorkspace.uuid,
19 | skWorkspace.uuid + skWorkspace.token,
20 | skWorkspace.name,
21 | skWorkspace.domain,
22 | skWorkspace.picUrl,
23 | skWorkspace.modifiedTime,
24 | skWorkspace.token
25 | )
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/remote/auth/SKAuthNetworkDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.remote.auth
2 |
3 | import dev.baseio.grpc.IGrpcCalls
4 | import dev.baseio.slackdata.datasources.remote.channels.toUserPublicKey
5 | import dev.baseio.slackdomain.datasources.remote.auth.SKAuthNetworkDataSource
6 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
7 |
8 | class SKAuthNetworkDataSourceImpl(private val grpcCalls: IGrpcCalls) : SKAuthNetworkDataSource {
9 | override suspend fun getLoggedInUser(): Result {
10 | return kotlin.runCatching {
11 | val result = grpcCalls.currentLoggedInUser()
12 | DomainLayerUsers.SKUser(
13 | result.uuid,
14 | result.workspaceId,
15 | result.gender,
16 | result.name,
17 | result.location,
18 | result.email,
19 | result.username,
20 | result.userSince,
21 | result.phone,
22 | result.avatarUrl,
23 | result.publicKey.toUserPublicKey()
24 | )
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/remote/auth/SKNetworkSaveFcmTokenImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.remote.auth
2 |
3 | import dev.baseio.grpc.IGrpcCalls
4 | import dev.baseio.slackdata.protos.kmSKPushToken
5 | import dev.baseio.slackdomain.LOGGED_IN_USER
6 | import dev.baseio.slackdomain.datasources.local.SKLocalKeyValueSource
7 | import dev.baseio.slackdomain.datasources.remote.auth.SKNetworkSaveFcmToken
8 |
9 | class SKNetworkSaveFcmTokenImpl(
10 | private val iGrpcCalls: IGrpcCalls,
11 | private val skLocalKeyValueSource: SKLocalKeyValueSource
12 | ) : SKNetworkSaveFcmToken {
13 | override suspend fun save(token: String) {
14 | skLocalKeyValueSource.get(LOGGED_IN_USER)?.let {
15 | iGrpcCalls.saveFcmToken(
16 | kmSKPushToken {
17 | this.token = token
18 | this.platform = 0
19 | }
20 | )
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/remote/channels/SKNetworkDataSourceReadChannelMembersImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.remote.channels
2 |
3 | import dev.baseio.grpc.IGrpcCalls
4 | import dev.baseio.slackdata.protos.KMSKChannelMember
5 | import dev.baseio.slackdomain.datasources.remote.channels.SKNetworkDataSourceReadChannelMembers
6 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
7 | import dev.baseio.slackdomain.usecases.channels.UseCaseWorkspaceChannelRequest
8 |
9 | class SKNetworkDataSourceReadChannelMembersImpl(private val grpcCalls: IGrpcCalls) :
10 | SKNetworkDataSourceReadChannelMembers {
11 | override suspend fun fetchChannelMembers(request: UseCaseWorkspaceChannelRequest): Result> {
12 | return kotlin.runCatching {
13 | val channelMembers = grpcCalls.fetchChannelMembers(request)
14 | channelMembers.membersList.map {
15 | it.toDomain()
16 | }
17 | }
18 | }
19 | }
20 |
21 | fun KMSKChannelMember.toDomain(): DomainLayerChannels.SkChannelMember {
22 | return DomainLayerChannels.SkChannelMember(this.uuid, this.workspaceId, this.channelId, this.memberId, this.channelPrivateKey.toDomainSKEncryptedMessage())
23 | }
24 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/remote/workspaces/SKNetworkDataSourceReadWorkspacesImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.remote.workspaces
2 |
3 | import dev.baseio.grpc.IGrpcCalls
4 | import dev.baseio.slackdata.protos.KMSKWorkspace
5 | import dev.baseio.slackdomain.datasources.remote.workspaces.SKNetworkDataSourceReadWorkspaces
6 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
7 |
8 | class SKNetworkDataSourceReadWorkspacesImpl(private val grpcCalls: IGrpcCalls) : SKNetworkDataSourceReadWorkspaces {
9 | override suspend fun getWorkspaces(token: String): List {
10 | return kotlin.run {
11 | val workspaces = grpcCalls.getWorkspaces(token)
12 | workspaces.workspacesList.map { kmskWorkspace ->
13 | kmskWorkspace.skWorkspace(token)
14 | }
15 | }
16 | }
17 | }
18 |
19 | fun KMSKWorkspace.skWorkspace(token: String) =
20 | DomainLayerWorkspaces.SKWorkspace(
21 | this.uuid,
22 | this.name,
23 | this.domain,
24 | this.picUrl,
25 | this.modifiedTime,
26 | token
27 | )
28 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/datasources/remote/workspaces/SKNetworkDataSourceWriteWorkspacesImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.datasources.remote.workspaces
2 |
3 | import dev.baseio.grpc.IGrpcCalls
4 | import dev.baseio.slackdomain.datasources.remote.workspaces.SKNetworkDataSourceWriteWorkspaces
5 |
6 | class SKNetworkDataSourceWriteWorkspacesImpl(private val grpcCalls: IGrpcCalls) : SKNetworkDataSourceWriteWorkspaces
7 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/injection/DispatcherModule.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.injection
2 |
3 | import dev.baseio.slackdata.RealCoroutineDispatcherProvider
4 | import dev.baseio.slackdomain.CoroutineDispatcherProvider
5 | import org.koin.dsl.module
6 |
7 | val dispatcherModule = module {
8 | single { RealCoroutineDispatcherProvider() }
9 | }
10 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/injection/EncryptionModule.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.injection
2 |
3 | import dev.baseio.slackdata.datasources.IDataDecryptorImpl
4 | import dev.baseio.slackdata.datasources.IDataEncrypterImpl
5 | import dev.baseio.slackdomain.datasources.IDataDecryptor
6 | import dev.baseio.slackdomain.datasources.IDataEncrypter
7 | import org.koin.dsl.module
8 |
9 | val encryptionModule = module {
10 | factory {
11 | IDataEncrypterImpl()
12 | }
13 | factory {
14 | IDataDecryptorImpl()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/mapper/EntityMapper.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.mapper
2 |
3 | interface EntityMapper {
4 | fun mapToDomain(entity: Data): Domain
5 | fun mapToData(model: Domain): Data
6 | }
7 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/mapper/SlackMessageMapper.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.mapper
2 |
3 | import database.SlackMessage
4 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
5 |
6 | class SlackMessageMapper : EntityMapper {
7 | override fun mapToDomain(entity: SlackMessage): DomainLayerMessages.SKMessage {
8 | return DomainLayerMessages.SKMessage(
9 | entity.uuid,
10 | entity.workspaceId,
11 | entity.channelId,
12 | entity.messageFirst,
13 | entity.messageSecond,
14 | entity.sender,
15 | entity.createdDate,
16 | entity.modifiedDate,
17 | isDeleted = entity.isDeleted == 1L,
18 | isSynced = entity.isSynced == 1L,
19 | )
20 | }
21 |
22 | override fun mapToData(model: DomainLayerMessages.SKMessage): SlackMessage {
23 | return SlackMessage(
24 | model.uuid,
25 | model.workspaceId,
26 | model.channelId,
27 | model.messageFirst,
28 | model.messageSecond,
29 | model.sender,
30 | model.createdDate,
31 | model.modifiedDate,
32 | if (model.isDeleted) 1 else 0,
33 | if (model.isSynced) 1 else 0,
34 | )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/mapper/SlackWorkspaceMapper.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.mapper
2 |
3 | import database.SlackWorkspaces
4 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
5 |
6 | class SlackWorkspaceMapper : EntityMapper {
7 | override fun mapToDomain(entity: SlackWorkspaces): DomainLayerWorkspaces.SKWorkspace {
8 | return DomainLayerWorkspaces.SKWorkspace(
9 | entity.uid,
10 | entity.name,
11 | entity.domain,
12 | entity.picUrl,
13 | entity.modifiedTime ?: 0L, entity.token
14 | )
15 | }
16 |
17 | override fun mapToData(model: DomainLayerWorkspaces.SKWorkspace): SlackWorkspaces {
18 | return SlackWorkspaces(
19 | model.uuid,
20 | model.uuid + model.token,
21 | model.name,
22 | model.domain,
23 | model.picUrl,
24 | model.modifiedTime,
25 | model.token
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/qrcode/QrCodeDataGeneratorImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.qrcode
2 |
3 | import dev.baseio.slackdomain.qrcode.QrCodeDataGenerator
4 | import dev.baseio.slackdomain.security.SecurityKeyDataPart
5 | import kotlin.math.ceil
6 |
7 | class QrCodeDataGeneratorImpl : QrCodeDataGenerator {
8 | override fun generateFrom(key: ByteArray): List {
9 | return key.splitAsKeyDataPart()
10 | }
11 | }
12 |
13 | private fun ByteArray.splitAsKeyDataPart(): List {
14 | val parts = mutableListOf()
15 | var startByte = 0
16 | val endBytes = this.size
17 | var partNumber = 0
18 | val totalParts = ceil(endBytes.div(2953.0)).toInt()
19 |
20 | while (partNumber != totalParts) {
21 | val allocation = kotlin.math.min(2953, endBytes)
22 | val end = kotlin.math.min(endBytes, allocation.plus(startByte))
23 | val part = this.copyOfRange(startByte, end)
24 | parts.add(
25 | SecurityKeyDataPart(
26 | partNumber = partNumber,
27 | totalParts = totalParts,
28 | partData = part.contentToString()
29 | )
30 | )
31 | startByte += allocation.plus(1)
32 | partNumber += 1
33 | }
34 | return parts
35 | }
36 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdata/security/QRCodeDataService.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata.security
2 |
3 | import dev.baseio.slackdomain.qrcode.QrCodeDataGenerator
4 | import dev.baseio.slackdomain.security.IByteArraySplitter
5 | import dev.baseio.slackdomain.security.SecurityKeyDataPart
6 |
7 | class ByteArraySplitterImpl(private val qrCodeDataGenerator: QrCodeDataGenerator) : IByteArraySplitter {
8 | override fun split(key: ByteArray): List {
9 | return qrCodeDataGenerator.generateFrom(key)
10 | }
11 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/Constants.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain
2 |
3 | const val AUTH_TOKEN = "auth_token"
4 | const val LOGGED_IN_USER = "userLoggedInId"
5 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/CoroutineDispatcherProvider.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain
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 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/Validator.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain
2 |
3 | fun isEmailValid(email: String): Boolean {
4 | return Regex(
5 | "^(([\\w-]+\\.)+[\\w-]+|([a-zA-Z]|[\\w-]{2,}))@" +
6 | "((([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\.([0-1]?" +
7 | "[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\." +
8 | "([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\\.([0-1]?" +
9 | "[0-9]{1,2}|25[0-5]|2[0-4][0-9]))|" +
10 | "([a-zA-Z]+[\\w-]+\\.)+[a-zA-Z]{2,4})$"
11 | ).matches(email)
12 | }
13 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/IDataEncrypter.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources
2 |
3 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
4 |
5 | interface IDataEncrypter {
6 | suspend fun encrypt(byteArray: ByteArray, publicKeyBytes: ByteArray): DomainLayerUsers.SKEncryptedMessage
7 | }
8 |
9 | interface IDataDecryptor {
10 | suspend fun decrypt(byteArray: Pair, privateKeyBytes: ByteArray): ByteArray
11 | }
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/SKLocalDatabaseSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local
2 |
3 | interface SKLocalDatabaseSource {
4 | suspend fun clear()
5 | }
6 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/SKLocalKeyValueSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local
2 |
3 | interface SKLocalKeyValueSource {
4 | fun save(key: String, value: Any, workspaceId: String? = null)
5 | fun clear(workspaceId: String? = null)
6 | fun get(key: String, workspaceId: String? = null): String?
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/channels/SKLocalDataSourceChannelLastMessage.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.channels
2 |
3 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SKLocalDataSourceChannelLastMessage {
7 | fun fetchChannelsWithLastMessage(workspaceId: String): Flow>
8 | }
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/channels/SKLocalDataSourceChannelMembers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.channels
2 |
3 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SKLocalDataSourceChannelMembers {
7 | suspend fun save(members: List)
8 | fun get(workspaceId: String, channelId: String): Flow>
9 | suspend fun getNow(workspaceId: String, channelId: String): List
10 | fun getChannelPrivateKeyForMe(
11 | workspaceId: String,
12 | channelId: String,
13 | uuid: String
14 | ): DomainLayerChannels.SkChannelMember?
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/channels/SKLocalDataSourceCreateChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.channels
2 |
3 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
4 |
5 | interface SKLocalDataSourceCreateChannels {
6 | suspend fun saveChannel(params: DomainLayerChannels.SKChannel): DomainLayerChannels.SKChannel
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/channels/SKLocalDataSourceReadChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.channels
2 |
3 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
4 | import dev.baseio.slackdomain.usecases.channels.UseCaseWorkspaceChannelRequest
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface SKLocalDataSourceReadChannels {
8 | suspend fun channelCount(workspaceId: String): Long
9 | suspend fun getChannel(request: UseCaseWorkspaceChannelRequest): DomainLayerChannels.SKChannel?
10 | fun fetchAllChannels(workspaceId: String): Flow>
11 | fun fetchChannelsOrByName(workspaceId: String, params: String?): Flow>
12 | suspend fun getChannelById(workspaceId: String, uuid: String): DomainLayerChannels.SKChannel?
13 | suspend fun getChannelByReceiverId(workspaceId: String, uuid: String): DomainLayerChannels.SKChannel.SkDMChannel?
14 | suspend fun getChannelByReceiverIdAndSenderId(workspaceId: String, receiverId: String, senderId: String): DomainLayerChannels.SKChannel.SkDMChannel?
15 | suspend fun getChannelByChannelId(channelId: String): DomainLayerChannels.SKChannel?
16 | }
17 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/messages/IMessageDecrypter.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.messages
2 |
3 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
4 |
5 | interface IMessageDecrypter {
6 | suspend fun decrypted(message: DomainLayerMessages.SKMessage): Result
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/messages/SKLocalDataSourceMessages.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.messages
2 |
3 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SKLocalDataSourceMessages {
7 | fun streamLocalMessages(
8 | workspaceId: String,
9 | userId: String,
10 | limit: Int,
11 | offset: Int
12 | ): Flow>
13 |
14 | fun streamLocalMessages(
15 | workspaceId: String,
16 | channelId: String,
17 | ): Flow>
18 |
19 | suspend fun getLocalMessages(
20 | workspaceId: String,
21 | userId: String,
22 | limit: Int,
23 | offset: Int
24 | ): List
25 |
26 | suspend fun getLocalMessages(
27 | workspaceId: String,
28 | userId: String,
29 | ): List
30 |
31 | suspend fun saveMessage(
32 | params: DomainLayerMessages.SKMessage,
33 | ): DomainLayerMessages.SKMessage
34 | }
35 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/users/SKLocalDataSourceUsers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.users
2 |
3 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SKLocalDataSourceUsers {
7 | fun getUsersByWorkspace(workspace: String): Flow>
8 | fun getUsersByWorkspaceAndName(workspace: String, name: String): Flow>
9 | fun getUsers(workspace: String): List
10 | fun getUser(workspaceId: String, uuid: String): DomainLayerUsers.SKUser?
11 | suspend fun saveUser(senderInfo: DomainLayerUsers.SKUser?)
12 | fun getUserByUserName(workspaceId: String, userName: String): DomainLayerUsers.SKUser?
13 | fun saveLoggedInUser(user: DomainLayerUsers.SKUser?)
14 | }
15 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/users/SKLocalDataSourceWriteUsers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.users
2 |
3 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
4 |
5 | interface SKLocalDataSourceWriteUsers {
6 | suspend fun saveUsers(users: List)
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/workspaces/SKLocalDataSourceReadWorkspaces.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.workspaces
2 |
3 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SKLocalDataSourceReadWorkspaces {
7 | suspend fun lastSelectedWorkspace(): DomainLayerWorkspaces.SKWorkspace?
8 | fun lastSelectedWorkspaceAsFlow(): Flow
9 |
10 | suspend fun setLastSelected(skWorkspace: DomainLayerWorkspaces.SKWorkspace)
11 | suspend fun workspacesCount(): Long
12 | suspend fun getWorkspace(uuid: String): DomainLayerWorkspaces.SKWorkspace?
13 | fun fetchWorkspaces(): Flow>
14 | }
15 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/local/workspaces/SKLocalDataSourceWriteWorkspaces.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.local.workspaces
2 |
3 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
4 |
5 | interface SKLocalDataSourceWriteWorkspaces {
6 | suspend fun saveWorkspaces(list: List)
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/auth/SKAuthNetworkDataSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.auth
2 |
3 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
4 |
5 | interface SKAuthNetworkDataSource {
6 | suspend fun getLoggedInUser(): Result
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/auth/SKNetworkSaveFcmToken.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.auth
2 |
3 | interface SKNetworkSaveFcmToken {
4 | suspend fun save(token: String)
5 | }
6 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/channels/SKNetworkDataSourceReadChannelMembers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.channels
2 |
3 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
4 | import dev.baseio.slackdomain.usecases.channels.UseCaseWorkspaceChannelRequest
5 |
6 | interface SKNetworkDataSourceReadChannelMembers {
7 | suspend fun fetchChannelMembers(request: UseCaseWorkspaceChannelRequest): Result>
8 | }
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/channels/SKNetworkDataSourceReadChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.channels
2 |
3 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SKNetworkDataSourceReadChannels {
7 | suspend fun fetchChannels(workspaceId: String, offset: Int, limit: Int): Result>
8 | fun listenToChangeInChannels(workspaceId: String): Flow>
9 | }
10 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/channels/SKNetworkDataSourceWriteChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.channels
2 |
3 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
4 |
5 | interface SKNetworkDataSourceWriteChannels {
6 | suspend fun createChannel(params: DomainLayerChannels.SKChannel): Result
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/channels/SKNetworkSourceChannel.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.channels
2 |
3 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
4 |
5 | interface SKNetworkSourceChannel {
6 | suspend fun inviteUserToChannelFromOtherDeviceOrUser(
7 | channel: DomainLayerChannels.SKChannel,
8 | userName: String
9 | ): List
10 | }
11 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/messages/SKNetworkDataSourceMessages.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.messages
2 |
3 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
4 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
5 | import dev.baseio.slackdomain.usecases.channels.UseCaseWorkspaceChannelRequest
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface SKNetworkDataSourceMessages {
9 | suspend fun sendMessage(params: DomainLayerMessages.SKMessage, publicKey: DomainLayerUsers.SKSlackKey): DomainLayerMessages.SKMessage
10 | suspend fun fetchMessages(request: UseCaseWorkspaceChannelRequest): Result>
11 | fun registerChangeInMessages(request: UseCaseWorkspaceChannelRequest): Flow>
12 | suspend fun deleteMessage(
13 | params: DomainLayerMessages.SKMessage,
14 | publicKey: DomainLayerUsers.SKSlackKey
15 | ): DomainLayerMessages.SKMessage
16 | }
17 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/users/SKNetworkDataSourceReadUsers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.users
2 |
3 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface SKNetworkDataSourceReadUsers {
7 | suspend fun fetchUsers(workspaceId: String): Result>
8 | fun listenToChangeInUsers(workspaceId: String): Flow>
9 | }
10 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/workspaces/SKNetworkDataSourceReadWorkspaces.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.workspaces
2 |
3 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
4 |
5 | interface SKNetworkDataSourceReadWorkspaces {
6 | suspend fun getWorkspaces(token: String): List
7 | }
8 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/workspaces/SKNetworkDataSourceWriteWorkspaces.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.workspaces
2 |
3 | interface SKNetworkDataSourceWriteWorkspaces
4 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/datasources/remote/workspaces/SKNetworkSourceWorkspaces.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.datasources.remote.workspaces
2 |
3 | interface SKNetworkSourceWorkspaces {
4 | suspend fun sendMagicLink(email: String, domain: String)
5 | }
6 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/model/security/SKUserPublicKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.model.security
2 |
3 | data class SKUserPublicKey(
4 | val algorithm: String,
5 | val keyBytes: ByteArray
6 | ) {
7 | override fun equals(other: Any?): Boolean {
8 | if (this === other) return true
9 | if (other == null || this::class != other::class) return false
10 |
11 | other as SKUserPublicKey
12 |
13 | if (algorithm != other.algorithm) return false
14 | if (!keyBytes.contentEquals(other.keyBytes)) return false
15 |
16 | return true
17 | }
18 |
19 | override fun hashCode(): Int {
20 | var result = algorithm.hashCode()
21 | result = 31 * result + keyBytes.contentHashCode()
22 | return result
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/model/workspaces/SKWorkspace.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.model.workspaces
2 |
3 | interface DomainLayerWorkspaces {
4 | data class SKWorkspace(
5 | val uuid: String,
6 | val name: String,
7 | val domain: String,
8 | val picUrl: String?,
9 | val modifiedTime: Long,
10 | val token: String,
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/qrcode/QrCodeDataGenerator.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.qrcode
2 |
3 | import dev.baseio.slackdomain.security.SecurityKeyDataPart
4 |
5 | interface QrCodeDataGenerator {
6 | fun generateFrom(key: ByteArray) : List
7 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/security/IByteArraySplitter.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.security
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | interface IByteArraySplitter {
6 | fun split(key: ByteArray): List
7 | }
8 |
9 |
10 | interface SecurityKeyPartReader {
11 | fun readBytes(file: ByteArray): Flow
12 | }
13 |
14 | interface SecurityKeyPartWriter {
15 | fun writeBytesToFile(bytes: ByteArray): SecurityKeyDataPart
16 | }
17 |
18 | data class SecurityKeyDataPart(
19 | val partNumber: Int,
20 | val totalParts: Int,
21 | val partData: String
22 | )
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/BaseUseCase.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | interface BaseUseCase {
6 | suspend fun perform(): Result? = throw NotImplementedError()
7 | suspend fun perform(params: ExecutableParam): Result? = throw NotImplementedError()
8 | suspend fun performNullable(params: ExecutableParam?): Result? = throw NotImplementedError()
9 | fun performStreaming(params: ExecutableParam): Flow = throw NotImplementedError()
10 | fun performStreamingNullable(params: ExecutableParam?): Flow = throw NotImplementedError()
11 | }
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/auth/UseCaseAuthWithQrCode.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.auth
2 |
3 | import dev.baseio.slackdomain.datasources.local.SKLocalDatabaseSource
4 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
5 | import dev.baseio.slackdomain.usecases.workspaces.UseCaseFetchAndSaveWorkspaces
6 |
7 | class UseCaseAuthWithQrCode(
8 | private val skLocalDatabaseSource: SKLocalDatabaseSource,
9 | private val useCaseFetchAndSaveWorkspaces: UseCaseFetchAndSaveWorkspaces,
10 | ) {
11 | suspend operator fun invoke(result: DomainLayerUsers.SKAuthResult) {
12 | skLocalDatabaseSource.clear()
13 | useCaseFetchAndSaveWorkspaces.invoke(result.token)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/auth/UseCaseFetchAndSaveCurrentUser.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.auth
2 |
3 | import dev.baseio.slackdomain.datasources.local.users.SKLocalDataSourceUsers
4 | import dev.baseio.slackdomain.datasources.remote.auth.SKAuthNetworkDataSource
5 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
6 |
7 | class UseCaseFetchAndSaveCurrentUser(
8 | private val skAuthNetworkDataSource: SKAuthNetworkDataSource,
9 | private val skLocalDataSourceUsers: SKLocalDataSourceUsers
10 | ) {
11 | suspend operator fun invoke(): Result {
12 | return skAuthNetworkDataSource.getLoggedInUser().also {
13 | skLocalDataSourceUsers.saveLoggedInUser(it.getOrNull())
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/auth/UseCaseLogout.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.auth
2 |
3 | import dev.baseio.slackdomain.datasources.local.SKLocalDatabaseSource
4 | import dev.baseio.slackdomain.datasources.local.SKLocalKeyValueSource
5 | import dev.baseio.slackdomain.usecases.workspaces.UseCaseGetSelectedWorkspace
6 |
7 | class UseCaseLogout(
8 | private val skKeyValueData: SKLocalKeyValueSource,
9 | private val getSelectedWorkspace: UseCaseGetSelectedWorkspace,
10 | private val slackChannelDao: SKLocalDatabaseSource,
11 | ) {
12 | suspend operator fun invoke() {
13 | getSelectedWorkspace.invoke()?.let {
14 | skKeyValueData.clear(it.uuid)
15 | }
16 | skKeyValueData.clear()
17 | slackChannelDao.clear()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/auth/UseCaseSaveFCMToken.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.auth
2 |
3 | import dev.baseio.slackdomain.datasources.local.SKLocalKeyValueSource
4 | import dev.baseio.slackdomain.datasources.remote.auth.SKNetworkSaveFcmToken
5 |
6 | const val FCM_TOKEN = "fcmToken"
7 |
8 | class UseCaseSaveFCMToken(
9 | private val skLocalKeyValueSource: SKLocalKeyValueSource,
10 | private val skNetworkSaveFcmToken: SKNetworkSaveFcmToken
11 | ) {
12 | suspend operator fun invoke(token: String) {
13 | if (skLocalKeyValueSource.get(FCM_TOKEN) != token) {
14 | skNetworkSaveFcmToken.save(token)
15 | skLocalKeyValueSource.save(FCM_TOKEN, token)
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseCreateChannel.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceCreateChannels
4 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceReadChannels
5 | import dev.baseio.slackdomain.datasources.remote.channels.SKNetworkDataSourceWriteChannels
6 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
7 |
8 | class UseCaseCreateChannel(
9 | private val SKLocalDataSourceCreateChannels: SKLocalDataSourceCreateChannels,
10 | private val skNetworkDataSourceWriteChannels: SKNetworkDataSourceWriteChannels,
11 | private val skLocalDataSourceReadChannels: SKLocalDataSourceReadChannels
12 | ) {
13 | suspend operator fun invoke(params: DomainLayerChannels.SKChannel): Result {
14 | return kotlin.runCatching {
15 | val channel = skNetworkDataSourceWriteChannels.createChannel(params).getOrThrow()
16 | SKLocalDataSourceCreateChannels.saveChannel(channel)
17 | skLocalDataSourceReadChannels.getChannelById(channel.workspaceId, channel.channelId)!!
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseFetchAllChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceReadChannels
4 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
5 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | class UseCaseFetchAllChannels(
9 | private val skLocalDataSourceReadChannels: SKLocalDataSourceReadChannels,
10 | ) {
11 | operator fun invoke(workspaceId: String): Flow> {
12 | return skLocalDataSourceReadChannels.fetchAllChannels(workspaceId)
13 | }
14 | }
15 |
16 | fun DomainLayerUsers.SKUser.otherUserInDMChannel(
17 | skChannel: DomainLayerChannels.SKChannel.SkDMChannel
18 | ) = if (this.uuid == skChannel.receiverId) {
19 | skChannel.senderId
20 | } else {
21 | skChannel.receiverId
22 | }
23 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseFetchAndSaveChannelMembers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceChannelMembers
4 | import dev.baseio.slackdomain.datasources.remote.channels.SKNetworkDataSourceReadChannelMembers
5 |
6 | class UseCaseFetchAndSaveChannelMembers(
7 | private val networkSource: SKNetworkDataSourceReadChannelMembers,
8 | private val localSource: SKLocalDataSourceChannelMembers
9 | ) {
10 | suspend operator fun invoke(useCaseWorkspaceChannelRequest: UseCaseWorkspaceChannelRequest) {
11 | networkSource.fetchChannelMembers(useCaseWorkspaceChannelRequest).mapCatching {
12 | println("saving $it")
13 | localSource.save(it)
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseFetchAndSaveChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceCreateChannels
4 | import dev.baseio.slackdomain.datasources.remote.channels.SKNetworkDataSourceReadChannels
5 |
6 | class UseCaseFetchAndSaveChannels(
7 | private val skNetworkDataSourceReadChannels: SKNetworkDataSourceReadChannels,
8 | private val skLocalDataSourceWriteChannels: SKLocalDataSourceCreateChannels,
9 | ) {
10 | suspend operator fun invoke(
11 | workspaceId: String,
12 | offset: Int,
13 | limit: Int
14 | ) {
15 | kotlin.runCatching {
16 | val channels = skNetworkDataSourceReadChannels
17 | .fetchChannels(workspaceId = workspaceId, offset, limit).getOrThrow()
18 | channels.map { skChannel ->
19 | skLocalDataSourceWriteChannels.saveChannel(skChannel)
20 | }
21 | }.run {
22 | when {
23 | isFailure -> {
24 | // TODO update upstream of errors!
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseFetchAndUpdateChangeInChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceCreateChannels
4 | import dev.baseio.slackdomain.datasources.remote.channels.SKNetworkDataSourceReadChannels
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.catch
7 | import kotlinx.coroutines.flow.map
8 |
9 | class UseCaseFetchAndUpdateChangeInChannels(
10 | private val readChannelsSource: SKNetworkDataSourceReadChannels,
11 | private val localSourceChannel: SKLocalDataSourceCreateChannels
12 | ) {
13 | operator fun invoke(workspaceId: String): Flow {
14 | return readChannelsSource.listenToChangeInChannels(workspaceId)
15 | .map { messageChangeSnapshot ->
16 | messageChangeSnapshot.second?.let {
17 | localSourceChannel.saveChannel(it)
18 | }
19 | Unit
20 | }
21 | .catch {
22 | // TODO tell upstream of exceptions if any
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseFetchChannelCount.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceReadChannels
4 |
5 | class UseCaseFetchChannelCount(private val skLocalDataSourceReadChannels: SKLocalDataSourceReadChannels) {
6 | suspend operator fun invoke(workspaceId: String): Int {
7 | return skLocalDataSourceReadChannels.channelCount(workspaceId).toInt()
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseFetchChannelsWithLastMessage.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceChannelLastMessage
4 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | class UseCaseFetchChannelsWithLastMessage(
8 | private val SKLocalDataSourceChannelLastMessage: SKLocalDataSourceChannelLastMessage,
9 | ) {
10 | operator fun invoke(workspaceId: String): Flow> {
11 | return SKLocalDataSourceChannelLastMessage.fetchChannelsWithLastMessage(workspaceId = workspaceId)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseFetchRecentChannels.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceChannelLastMessage
4 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
5 | import kotlinx.coroutines.ExperimentalCoroutinesApi
6 | import kotlinx.coroutines.flow.*
7 |
8 | @OptIn(ExperimentalCoroutinesApi::class)
9 | class UseCaseFetchRecentChannels(private val skLocalDataSourceChannelLastMessage: SKLocalDataSourceChannelLastMessage) {
10 | operator fun invoke(workspaceId: String): Flow> {
11 | return skLocalDataSourceChannelLastMessage.fetchChannelsWithLastMessage(workspaceId = workspaceId)
12 | .mapLatest { skLastMessageList ->
13 | skLastMessageList.map { skLastMessage -> skLastMessage.channel }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseInviteUserToChannel.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.remote.channels.SKNetworkSourceChannel
4 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
5 |
6 | class UseCaseInviteUserToChannel(private val networkSourceChannels: SKNetworkSourceChannel) {
7 |
8 | suspend fun inviteUserToChannelFromOtherDeviceOrUser(channel: DomainLayerChannels.SKChannel, userName: String): Result> {
9 | return kotlin.runCatching {
10 | networkSourceChannels.inviteUserToChannelFromOtherDeviceOrUser(channel, userName)
11 | }.also {
12 | it.exceptionOrNull()?.printStackTrace()
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseSearchChannel.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | import dev.baseio.slackdomain.datasources.local.channels.SKLocalDataSourceReadChannels
4 | import dev.baseio.slackdomain.model.channel.DomainLayerChannels
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | class UseCaseSearchChannel(private val sdkDataSource: SKLocalDataSourceReadChannels) {
8 | operator fun invoke(params: UseCaseWorkspaceChannelRequest): Flow> {
9 | return sdkDataSource.fetchChannelsOrByName(params.workspaceId, params.channelId)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/channels/UseCaseWorkspaceChannelRequest.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.channels
2 |
3 | data class UseCaseWorkspaceChannelRequest(
4 | val workspaceId: String,
5 | val channelId: String? = null,
6 | val limit: Int = 20,
7 | val offset: Int = 0
8 | )
9 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/chat/UseCaseFetchAndSaveMessages.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.chat
2 |
3 | import dev.baseio.slackdomain.datasources.local.messages.SKLocalDataSourceMessages
4 | import dev.baseio.slackdomain.datasources.remote.messages.SKNetworkDataSourceMessages
5 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
6 | import dev.baseio.slackdomain.usecases.channels.UseCaseWorkspaceChannelRequest
7 |
8 | class UseCaseFetchAndSaveMessages(
9 | private val skLocalDataSourceMessages: SKLocalDataSourceMessages,
10 | private val skNetworkDataSourceMessages: SKNetworkDataSourceMessages
11 | ) {
12 | suspend operator fun invoke(request: UseCaseWorkspaceChannelRequest): List {
13 | return kotlin.run {
14 | skNetworkDataSourceMessages.fetchMessages(request).getOrThrow().map { skMessage ->
15 | skLocalDataSourceMessages.saveMessage(skMessage)
16 | skMessage
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/chat/UseCaseFetchAndUpdateChangeInMessages.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.chat
2 |
3 | import dev.baseio.slackdomain.datasources.local.messages.SKLocalDataSourceMessages
4 | import dev.baseio.slackdomain.datasources.remote.messages.SKNetworkDataSourceMessages
5 | import dev.baseio.slackdomain.usecases.channels.UseCaseWorkspaceChannelRequest
6 | import kotlinx.coroutines.flow.*
7 |
8 | class UseCaseFetchAndUpdateChangeInMessages(
9 | private val SKLocalDataSourceMessages: SKLocalDataSourceMessages,
10 | private val skNetworkDataSourceMessages: SKNetworkDataSourceMessages
11 | ) {
12 | operator fun invoke(request: UseCaseWorkspaceChannelRequest): Flow {
13 | return skNetworkDataSourceMessages.registerChangeInMessages(request)
14 | .map { messageChangeSnapshot ->
15 | messageChangeSnapshot.first?.let {
16 | // skLocalDataSourceUsers.saveUser(it.senderInfo) // TODO remove senderInfo from model once we have users stream finalized
17 | // SKLocalDataSourceMessages.saveMessage(it)
18 | }
19 | messageChangeSnapshot.second?.let {
20 | SKLocalDataSourceMessages.saveMessage(it)
21 | }
22 | Unit
23 | }
24 | .catch {
25 | // TODO tell upstream of exceptions if any
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/chat/UseCaseStreamLocalMessages.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.chat
2 |
3 | import dev.baseio.slackdomain.datasources.local.messages.SKLocalDataSourceMessages
4 | import dev.baseio.slackdomain.model.message.DomainLayerMessages
5 | import dev.baseio.slackdomain.usecases.channels.UseCaseWorkspaceChannelRequest
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | class UseCaseStreamLocalMessages(
9 | private val skLocalDataSourceMessages: SKLocalDataSourceMessages
10 | ) {
11 | operator fun invoke(useCaseWorkspaceChannelRequest: UseCaseWorkspaceChannelRequest): Flow> {
12 | return skLocalDataSourceMessages.streamLocalMessages(
13 | workspaceId = useCaseWorkspaceChannelRequest.workspaceId,
14 | useCaseWorkspaceChannelRequest.channelId!!,
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/users/UseCaseFetchAndSaveUsers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.users
2 |
3 | import dev.baseio.slackdomain.datasources.local.users.SKLocalDataSourceWriteUsers
4 | import dev.baseio.slackdomain.datasources.remote.users.SKNetworkDataSourceReadUsers
5 |
6 | class UseCaseFetchAndSaveUsers(
7 | private val skLocalDataSourceWriteUsers: SKLocalDataSourceWriteUsers,
8 | private val skNetworkDataSourceReadUsers: SKNetworkDataSourceReadUsers
9 | ) {
10 | suspend operator fun invoke(params: String) {
11 | return kotlin.runCatching {
12 | val users = skNetworkDataSourceReadUsers.fetchUsers(workspaceId = params).getOrThrow()
13 | skLocalDataSourceWriteUsers.saveUsers(users)
14 | }.run {
15 | when {
16 | isFailure -> {
17 | // TODO update upstream of errors!
18 | }
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/users/UseCaseFetchAndUpdateChangeInUsers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.users
2 |
3 | import dev.baseio.slackdomain.datasources.local.users.SKLocalDataSourceUsers
4 | import dev.baseio.slackdomain.datasources.remote.users.SKNetworkDataSourceReadUsers
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.catch
7 | import kotlinx.coroutines.flow.map
8 |
9 | class UseCaseFetchAndUpdateChangeInUsers(
10 | private val skNetworkDataSourceMessages: SKNetworkDataSourceReadUsers,
11 | private val skLocalDataSourceUsers: SKLocalDataSourceUsers
12 | ) {
13 | operator fun invoke(workspaceId: String): Flow {
14 | return skNetworkDataSourceMessages.listenToChangeInUsers(workspaceId)
15 | .map { messageChangeSnapshot ->
16 | messageChangeSnapshot.second?.let {
17 | skLocalDataSourceUsers.saveUser(it)
18 | }
19 | Unit
20 | }
21 | .catch {
22 | // TODO tell upstream of exceptions if any
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/users/UseCaseFetchLocalUsers.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.users
2 |
3 | import dev.baseio.slackdomain.datasources.local.users.SKLocalDataSourceUsers
4 | import dev.baseio.slackdomain.model.users.DomainLayerUsers
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | class UseCaseFetchLocalUsers(
8 | private val SKLocalDataSourceUsers: SKLocalDataSourceUsers
9 | ) {
10 | operator fun invoke(workspaceId: String, search: String): Flow> {
11 | return SKLocalDataSourceUsers.getUsersByWorkspaceAndName(workspaceId, search)
12 | }
13 |
14 | operator fun invoke(workspaceId: String): Flow> {
15 | return SKLocalDataSourceUsers.getUsersByWorkspace(workspaceId)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/workspaces/UseCaseAuthWorkspace.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.workspaces
2 |
3 | import dev.baseio.slackdomain.datasources.remote.workspaces.SKNetworkSourceWorkspaces
4 | import dev.baseio.slackdomain.isEmailValid
5 |
6 | class UseCaseAuthWorkspace(
7 | private val workspaceSource: SKNetworkSourceWorkspaces,
8 | ) {
9 | suspend operator fun invoke(email: String, domain: String) {
10 | if (isEmailValid(email)) {
11 | return workspaceSource.sendMagicLink(email, domain)
12 | } else {
13 | throw Exception("Email is invalid.")
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/workspaces/UseCaseFetchAndSaveWorkspaces.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.workspaces
2 |
3 | import dev.baseio.slackdomain.AUTH_TOKEN
4 | import dev.baseio.slackdomain.datasources.local.SKLocalKeyValueSource
5 | import dev.baseio.slackdomain.datasources.local.workspaces.SKLocalDataSourceWriteWorkspaces
6 | import dev.baseio.slackdomain.datasources.remote.workspaces.SKNetworkDataSourceReadWorkspaces
7 |
8 | class UseCaseFetchAndSaveWorkspaces(
9 | private val skLocalKeyValueSource: SKLocalKeyValueSource,
10 | private val skNetworkDataSourceReadWorkspaces: SKNetworkDataSourceReadWorkspaces,
11 | private val skLocalDataSourceWriteWorkspaces: SKLocalDataSourceWriteWorkspaces,
12 | private val setLastSelectedWorkspace: UseCaseSetLastSelectedWorkspace
13 | ) {
14 | suspend operator fun invoke(token: String): Result {
15 | return kotlin.runCatching {
16 | skLocalKeyValueSource.save(AUTH_TOKEN, token)
17 | val kmSKWorkspaces = skNetworkDataSourceReadWorkspaces.getWorkspaces(token)
18 | // TODO there will be always one workspace change list- to Item in grpc call.
19 | val workspaces = kmSKWorkspaces.map { skWorkspace -> skWorkspace.copy(token = token) }
20 | skLocalDataSourceWriteWorkspaces.saveWorkspaces(workspaces)
21 | setLastSelectedWorkspace.invoke(workspaces.first())
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/workspaces/UseCaseGetSelectedWorkspace.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.workspaces
2 |
3 | import dev.baseio.slackdomain.datasources.local.workspaces.SKLocalDataSourceReadWorkspaces
4 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | class UseCaseGetSelectedWorkspace(private val skLocalDataSourceReadWorkspaces: SKLocalDataSourceReadWorkspaces) {
8 | suspend operator fun invoke(): DomainLayerWorkspaces.SKWorkspace? {
9 | return skLocalDataSourceReadWorkspaces.lastSelectedWorkspace()
10 | }
11 |
12 | fun invokeFlow(): Flow {
13 | return skLocalDataSourceReadWorkspaces.lastSelectedWorkspaceAsFlow()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/workspaces/UseCaseGetWorkspaces.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.workspaces
2 |
3 | import dev.baseio.slackdomain.datasources.local.workspaces.SKLocalDataSourceReadWorkspaces
4 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | class UseCaseGetWorkspaces(
8 | private val readWorkspacesSource: SKLocalDataSourceReadWorkspaces,
9 | ) {
10 | operator fun invoke(): Flow
> {
11 | return readWorkspacesSource.fetchWorkspaces()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/usecases/workspaces/UseCaseSetLastSelectedWorkspace.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.usecases.workspaces
2 |
3 | import dev.baseio.slackdomain.datasources.local.workspaces.SKLocalDataSourceReadWorkspaces
4 | import dev.baseio.slackdomain.model.workspaces.DomainLayerWorkspaces
5 |
6 | class UseCaseSetLastSelectedWorkspace(private val skLocalDataSourceReadWorkspaces: SKLocalDataSourceReadWorkspaces) {
7 | suspend operator fun invoke(skWorkspace: DomainLayerWorkspaces.SKWorkspace) {
8 | return skLocalDataSourceReadWorkspaces.setLastSelected(skWorkspace)
9 | // / TODO
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/baseio/slackdomain/util/TimeUnit.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.util
2 |
3 | expect enum class TimeUnit {
4 | DAYS,
5 | HOURS,
6 | MILLISECONDS,
7 | MINUTES,
8 | SECONDS
9 | }
10 |
11 | expect fun TimeUnit.toMillis(duration: Long): Long
12 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/icerock/moko/paging/LambdaPagedListDataSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3 | */
4 |
5 | package dev.icerock.moko.paging
6 |
7 | class LambdaPagedListDataSource(
8 | private val loadPageLambda: suspend (List?) -> List
9 | ) : PagedListDataSource {
10 | override suspend fun loadPage(currentList: List?): List {
11 | return loadPageLambda(currentList)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/icerock/moko/paging/LiveDataExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3 | */
4 |
5 | package dev.icerock.moko.paging
6 |
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.StateFlow
9 | import kotlinx.coroutines.flow.combine
10 | import kotlinx.coroutines.flow.map
11 |
12 |
13 | fun StateFlow>.withLoadingItem(
14 | loading: StateFlow,
15 | itemFactory: () -> T
16 | ): Flow> = combine(this, loading) { items, nextPageLoading ->
17 | if (nextPageLoading) {
18 | items + itemFactory()
19 | } else {
20 | items
21 | }
22 | }
23 |
24 | fun StateFlow>.withReachEndNotifier(
25 | action: (Int) -> Unit
26 | ): Flow> = map { list ->
27 | list.withReachEndNotifier(action)
28 | }
29 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/dev/icerock/moko/paging/PagedListDataSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3 | */
4 |
5 | package dev.icerock.moko.paging
6 |
7 | interface PagedListDataSource {
8 | suspend fun loadPage(currentList: List?): List
9 | }
10 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/resources/gettingstarted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/commonMain/resources/gettingstarted.png
--------------------------------------------------------------------------------
/composeApp/src/commonMain/resources/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/composeApp/src/commonMain/resources/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/Main.ios.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.ComposeUIViewController
2 | import platform.UIKit.UIViewController
3 | import com.arkivanov.decompose.DefaultComponentContext
4 | import com.arkivanov.essenty.lifecycle.LifecycleRegistry
5 | import dev.baseio.slackclone.RootComponent
6 | import dev.baseio.slackclone.SlackApp
7 | import dev.baseio.slackclone.commonui.theme.SlackCloneTheme
8 | import dev.baseio.slackclone.initKoin
9 |
10 | val lifecycle = LifecycleRegistry()
11 | val rootComponent by lazy {
12 | RootComponent(
13 | context = DefaultComponentContext(lifecycle = lifecycle),
14 | )
15 | }
16 |
17 | fun MainViewController(): UIViewController = ComposeUIViewController {
18 | initKoin()
19 | SlackCloneTheme(isDarkTheme = true) {
20 | SlackApp {
21 | rootComponent
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/MainDispatcher.kt:
--------------------------------------------------------------------------------
1 | import kotlinx.coroutines.CoroutineDispatcher
2 | import kotlinx.coroutines.Runnable
3 | import platform.darwin.dispatch_async
4 | import platform.darwin.dispatch_get_main_queue
5 | import platform.darwin.dispatch_queue_t
6 | import kotlin.coroutines.CoroutineContext
7 |
8 | actual val mainDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue())
9 | actual val ioDispatcher: CoroutineDispatcher = mainDispatcher
10 | actual val defaultDispatcher: CoroutineDispatcher = mainDispatcher
11 |
12 | // NsQueueDispatcher(dispatch_get_main_queue())
13 | internal class NsQueueDispatcher(private val dispatchQueue: dispatch_queue_t) : CoroutineDispatcher() {
14 | override fun dispatch(context: CoroutineContext, block: Runnable) {
15 | dispatch_async(dispatchQueue) { block.run() }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/extensions/NSDataExt.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalForeignApi::class)
2 |
3 | package dev.baseio.extensions
4 |
5 | import kotlinx.cinterop.ExperimentalForeignApi
6 | import kotlinx.cinterop.addressOf
7 | import kotlinx.cinterop.pin
8 | import kotlinx.cinterop.usePinned
9 | import platform.Foundation.NSData
10 | import platform.Foundation.create
11 | import platform.posix.memcpy
12 |
13 | inline fun ByteArray.toData(offset: Int = 0, length: Int = size - offset): NSData {
14 | require(offset + length <= size) { "offset + length > size" }
15 | if (isEmpty()) return NSData()
16 | val pinned = pin()
17 | return NSData.create(pinned.addressOf(offset), length.toULong()) { _, _ -> pinned.unpin() }
18 | }
19 |
20 | fun NSData.toByteArrayFromNSData(): ByteArray {
21 | val size = length.toInt()
22 | val bytes = ByteArray(size)
23 |
24 | if (size > 0) {
25 | bytes.usePinned { pinned ->
26 | memcpy(pinned.addressOf(0), this.bytes, this.length)
27 | }
28 | }
29 |
30 | return bytes
31 | }
32 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackclone/NativeCoroutineScope.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.SupervisorJob
6 | import kotlin.coroutines.CoroutineContext
7 |
8 | internal actual fun NativeCoroutineScope(context: CoroutineContext): CoroutineScope =
9 | CoroutineScope(SupervisorJob() + Dispatchers.Main + context)
10 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackclone/commonui/reusable/QrCodeView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.asComposeImageBitmap
7 | import dev.baseio.slackdata.protos.KMSKQrCodeResponse
8 | import org.jetbrains.skia.Bitmap
9 |
10 | @Composable
11 | internal actual fun QrCodeView(modifier: Modifier, response: KMSKQrCodeResponse) {
12 | val byteArray = response.byteArrayList.map { it.byte.toByte() }.toByteArray()
13 | val bitmap = Bitmap.makeFromImage(org.jetbrains.skia.Image.makeFromEncoded(byteArray))
14 | Image(bitmap = bitmap.asComposeImageBitmap(), contentDescription = null, modifier = modifier)
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackclone/keyboardAsState.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.State
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 |
8 | @Composable
9 | internal actual fun keyboardAsState(): State {
10 | val keyboardState = remember { mutableStateOf(Keyboard.Closed) }
11 | return keyboardState
12 | }
13 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackclone/platform.ios.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalForeignApi::class)
2 |
3 | package dev.baseio.slackclone
4 |
5 | import androidx.compose.foundation.layout.WindowInsets
6 | import androidx.compose.foundation.layout.windowInsetsPadding
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.unit.Density
9 | import androidx.compose.ui.unit.LayoutDirection
10 | import kotlinx.cinterop.CValue
11 | import kotlinx.cinterop.ExperimentalForeignApi
12 | import kotlinx.cinterop.useContents
13 | import platform.UIKit.UIApplication
14 | import platform.UIKit.UIEdgeInsets
15 |
16 | private val iosNotchInset = object : WindowInsets {
17 | override fun getTop(density: Density): Int {
18 | val safeAreaInsets: CValue? = UIApplication.sharedApplication.keyWindow?.safeAreaInsets
19 | return if (safeAreaInsets != null) {
20 | val topInset = safeAreaInsets.useContents { this.top }
21 | (topInset * density.density).toInt()
22 | } else {
23 | 0
24 | }
25 | }
26 |
27 | override fun getLeft(density: Density, layoutDirection: LayoutDirection): Int = 0
28 | override fun getRight(density: Density, layoutDirection: LayoutDirection): Int = 0
29 | override fun getBottom(density: Density): Int = 0
30 | }
31 |
32 | actual fun Modifier.notchPadding(): Modifier =
33 | this.windowInsetsPadding(iosNotchInset)
34 |
35 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackclone/platform.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | actual fun platformType(): Platform = Platform.IOS
4 |
5 | actual suspend fun fcmToken() = ""
6 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackclone/platformModule.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import dev.baseio.database.SlackDB
4 | import dev.baseio.slackdata.DriverFactory
5 | import dev.baseio.slackdata.SKKeyValueData
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.SupervisorJob
9 | import org.koin.core.module.Module
10 | import org.koin.dsl.module
11 |
12 | actual fun platformModule(): Module {
13 | return module {
14 | single { SKKeyValueData() }
15 | single { SlackDB.invoke(DriverFactory().createDriver()) }
16 | factory { CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackclone/qrscanner/qrCodeGenerate.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalForeignApi::class)
2 |
3 | package dev.baseio.slackclone.qrscanner
4 |
5 | import dev.baseio.extensions.toByteArrayFromNSData
6 | import kotlinx.cinterop.ExperimentalForeignApi
7 | import platform.CoreGraphics.CGAffineTransformMakeScale
8 | import platform.CoreGraphics.CGColorSpaceCreateDeviceRGB
9 | import platform.CoreImage.CIContext
10 | import platform.CoreImage.CIFilter
11 | import platform.CoreImage.PNGRepresentationOfImage
12 | import platform.CoreImage.kCIFormatRGBA8
13 | import platform.Foundation.setValue
14 |
15 | actual suspend fun qrCodeGenerate(data: String): ByteArray {
16 | CIFilter().apply {
17 | name = "CIQRCodeGenerator"
18 | }.let {
19 | it.setValue(data, forKey = "inputMessage")
20 |
21 | val context = CIContext()
22 | it.outputImage?.imageByApplyingTransform(CGAffineTransformMakeScale(3.0, 3.0))
23 | ?.let { output ->
24 | context.PNGRepresentationOfImage(
25 | image = output,
26 | format = kCIFormatRGBA8,
27 | colorSpace = CGColorSpaceCreateDeviceRGB(),
28 | options = hashMapOf()
29 | )?.toByteArrayFromNSData()
30 | }
31 | }
32 | return ByteArray(0)
33 | }
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackdata/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 | import app.cash.sqldelight.driver.native.NativeSqliteDriver
5 | import app.cash.sqldelight.driver.native.wrapConnection
6 | import co.touchlab.sqliter.DatabaseConfiguration
7 | import dev.baseio.database.SlackDB
8 |
9 | actual class DriverFactory {
10 | private var index = 0
11 |
12 | actual fun createDriver(): SqlDriver {
13 | index++
14 | return NativeSqliteDriver(
15 | DatabaseConfiguration(
16 | name = "test-$index.db",
17 | version = SlackDB.Schema.version.toInt(),
18 | create = { connection ->
19 | wrapConnection(connection) { SlackDB.Schema.create(it) }
20 | },
21 | inMemory = true
22 | )
23 | ).also {
24 | SlackDB.Schema.create(it)
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackdata/SKKeyValueData.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import platform.Foundation.NSUserDefaults
4 | import platform.Foundation.setValue
5 |
6 | actual class SKKeyValueData {
7 |
8 | actual fun save(key: String, value: String, workspaceId: String?) {
9 | (workspaceId?.let {
10 | NSUserDefaults(suiteName = workspaceId)
11 | } ?: NSUserDefaults.standardUserDefaults()).setValue(value = value, forKey = key)
12 | }
13 |
14 | actual fun get(key: String, workspaceId: String?): String? {
15 | return workspaceId?.let {
16 | NSUserDefaults(suiteName = workspaceId).stringForKey(key)
17 | } ?: NSUserDefaults.standardUserDefaults().stringForKey(key)
18 | }
19 |
20 | actual fun clear(workspaceId: String?) {
21 | workspaceId?.let {
22 | NSUserDefaults.standardUserDefaults().removePersistentDomainForName(workspaceId)
23 | } ?: NSUserDefaults.resetStandardUserDefaults()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackdomain/util/TimeUnit.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.util
2 |
3 | actual enum class TimeUnit(val toMilliFactor: Long) {
4 | DAYS(24 * 60 * 60 * 1000),
5 | HOURS(60 * 60 * 1000),
6 | MILLISECONDS(1),
7 | MINUTES(60 * 1000),
8 | SECONDS(1000)
9 | }
10 |
--------------------------------------------------------------------------------
/composeApp/src/iosMain/kotlin/dev/baseio/slackdomain/util/toMillis.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.util
2 |
3 | actual fun TimeUnit.toMillis(duration: Long): Long {
4 | return toMilliFactor.times(duration)
5 | }
6 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/MainDispatcher.kt:
--------------------------------------------------------------------------------
1 | import kotlinx.coroutines.CoroutineDispatcher
2 | import kotlinx.coroutines.Dispatchers
3 |
4 | actual val mainDispatcher: CoroutineDispatcher = Dispatchers.Main
5 | actual val ioDispatcher: CoroutineDispatcher = Dispatchers.Default
6 | actual val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
7 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/NativeCoroutineScope.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.SupervisorJob
6 | import kotlin.coroutines.CoroutineContext
7 |
8 | internal actual fun NativeCoroutineScope(context: CoroutineContext): CoroutineScope =
9 | CoroutineScope(SupervisorJob() + Dispatchers.Default + context)
10 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/commonui/reusable/QrCodeScanner.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 |
6 | @Composable
7 | internal actual fun QrCodeScanner(modifier: Modifier, onQrCodeScanned: (String) -> Unit) {
8 | }
9 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/commonui/reusable/QrCodeView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.asComposeImageBitmap
7 | import dev.baseio.slackdata.protos.KMSKQrCodeResponse
8 | import org.jetbrains.skia.Bitmap
9 |
10 | @Composable
11 | internal actual fun QrCodeView(modifier: Modifier, response: KMSKQrCodeResponse) {
12 | val byteArray = response.byteArrayList.map { it.byte.toByte() }.toByteArray()
13 | val bitmap = Bitmap.makeFromImage(org.jetbrains.skia.Image.makeFromEncoded(byteArray))
14 | Image(bitmap = bitmap.asComposeImageBitmap(), contentDescription = null, modifier = modifier)
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/keyboardAsState.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.State
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 |
8 | @Composable
9 | internal actual fun keyboardAsState(): State {
10 | val keyboardState = remember { mutableStateOf(Keyboard.HardwareKeyboard) }
11 | return keyboardState
12 | }
13 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/onboarding/compose/PlatformSideEffects.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.onboarding.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.Color
5 |
6 | actual object PlatformSideEffects {
7 | @Composable
8 | internal actual fun SlackCloneColorOnPlatformUI() {
9 | }
10 |
11 | @Composable
12 | internal actual fun PlatformColors(
13 | topColor: Color,
14 | bottomColor: Color
15 | ) {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/platform.jvm.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import androidx.compose.ui.Modifier
4 |
5 | actual fun Modifier.notchPadding(): Modifier = Modifier
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/platform.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | actual fun platformType(): Platform = Platform.JVM
4 |
5 | actual suspend fun fcmToken() = ""
6 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/platformModule.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import dev.baseio.database.SlackDB
4 | import dev.baseio.slackdata.DriverFactory
5 | import dev.baseio.slackdata.SKKeyValueData
6 | import org.koin.core.module.Module
7 | import org.koin.dsl.module
8 |
9 | actual fun platformModule(): Module {
10 | return module {
11 | single { SKKeyValueData() }
12 | single { SlackDB.invoke(DriverFactory().createDriver()) }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackclone/qrscanner/qrCodeGenerate.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.qrscanner
2 |
3 | import io.ktor.utils.io.core.toByteArray
4 | import kotlinx.coroutines.await
5 | import kotlin.js.Promise
6 |
7 | actual suspend fun qrCodeGenerate(data: String): ByteArray {
8 | return QRCode.toDataURL(data).await().toByteArray()
9 | }
10 |
11 | @JsModule("qrcode")
12 | external object QRCode {
13 | fun toDataURL(text: String, options: dynamic = definedExternally): Promise
14 | // Add other methods you might need
15 | }
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackdata/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 | import app.cash.sqldelight.driver.worker.WebWorkerDriver
5 | import dev.baseio.database.SlackDB
6 | import org.w3c.dom.Worker
7 |
8 | actual class DriverFactory {
9 | actual fun createDriver(): SqlDriver {
10 | val driver = WebWorkerDriver(
11 | Worker(
12 | js("""new URL("@cashapp/sqldelight-sqljs-worker/sqljs.worker.js", import.meta.url)""")
13 | )
14 | )
15 | SlackDB.Schema.create(driver)
16 | return driver
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackdata/SKKeyValueData.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import kotlinx.browser.localStorage
4 | import org.w3c.dom.get
5 | import org.w3c.dom.set
6 |
7 | actual class SKKeyValueData {
8 |
9 | actual fun save(key: String, value: String, workspaceId: String?) {
10 | localStorage.set(key + workspaceId, value)
11 | }
12 |
13 | actual fun get(key: String, workspaceId: String?): String? {
14 | return localStorage.get(key + workspaceId)
15 | }
16 |
17 | actual fun clear(workspaceId: String?) {
18 | localStorage.clear()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/baseio/slackdomain/util/TimeUnit.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.util
2 |
3 | actual enum class TimeUnit(val toMilliFactor: Long) {
4 | DAYS(24 * 60 * 60 * 1000),
5 | HOURS(60 * 60 * 1000),
6 | MILLISECONDS(1),
7 | MINUTES(60 * 1000),
8 | SECONDS(1000)
9 | }
10 |
11 | actual fun TimeUnit.toMillis(duration: Long): Long {
12 | return toMilliFactor.times(duration)
13 | }
14 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.ExperimentalComposeUiApi
2 | import androidx.compose.ui.window.CanvasBasedWindow
3 | import com.arkivanov.decompose.DefaultComponentContext
4 | import com.arkivanov.essenty.lifecycle.LifecycleRegistry
5 | import dev.baseio.slackclone.RootComponent
6 | import dev.baseio.slackclone.SlackApp
7 | import dev.baseio.slackclone.commonui.theme.SlackCloneTheme
8 | import dev.baseio.slackclone.initKoin
9 | import org.jetbrains.skiko.wasm.onWasmReady
10 |
11 | val lifecycle = LifecycleRegistry()
12 | val rootComponent by lazy {
13 | RootComponent(
14 | context = DefaultComponentContext(lifecycle = lifecycle),
15 | )
16 | }
17 |
18 | @OptIn(ExperimentalComposeUiApi::class)
19 | fun main() {
20 | initKoin()
21 | onWasmReady {
22 | CanvasBasedWindow("SlackCMP") {
23 | SlackCloneTheme {
24 | SlackApp {
25 | rootComponent
26 | }
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/composeApp/src/jsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SlackCMP
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/MainDispatcher.kt:
--------------------------------------------------------------------------------
1 | import kotlinx.coroutines.CoroutineDispatcher
2 | import kotlinx.coroutines.Dispatchers
3 | import kotlinx.coroutines.swing.Swing
4 |
5 | actual val mainDispatcher: CoroutineDispatcher = Dispatchers.Swing
6 | actual val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
7 | actual val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
8 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/NativeCoroutineScope.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.SupervisorJob
6 | import kotlin.coroutines.CoroutineContext
7 |
8 | internal actual fun NativeCoroutineScope(context: CoroutineContext): CoroutineScope =
9 | CoroutineScope(SupervisorJob() + Dispatchers.Default + context)
10 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/commonui/reusable/QrCodeScanner.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 |
6 | @Composable
7 | internal actual fun QrCodeScanner(modifier: Modifier, onQrCodeScanned: (String) -> Unit) {
8 | }
9 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/commonui/reusable/QrCodeView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.commonui.reusable
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.asComposeImageBitmap
7 | import dev.baseio.slackdata.protos.KMSKQrCodeResponse
8 | import org.jetbrains.skia.Bitmap
9 |
10 | @Composable
11 | internal actual fun QrCodeView(modifier: Modifier, response: KMSKQrCodeResponse) {
12 | val byteArray = response.byteArrayList.map { it.byte.toByte() }.toByteArray()
13 | val bitmap = Bitmap.makeFromImage(org.jetbrains.skia.Image.makeFromEncoded(byteArray))
14 | Image(bitmap = bitmap.asComposeImageBitmap(), contentDescription = null, modifier = modifier)
15 | }
16 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/keyboardAsState.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.State
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 |
8 | @Composable
9 | internal actual fun keyboardAsState(): State {
10 | val keyboardState = remember { mutableStateOf(Keyboard.HardwareKeyboard) }
11 | return keyboardState
12 | }
13 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/onboarding/compose/PlatformSideEffects.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.onboarding.compose
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.Color
5 |
6 | actual object PlatformSideEffects {
7 | @Composable
8 | internal actual fun SlackCloneColorOnPlatformUI() {
9 | }
10 |
11 | @Composable
12 | internal actual fun PlatformColors(
13 | topColor: Color,
14 | bottomColor: Color
15 | ) {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/platform.jvm.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import androidx.compose.ui.Modifier
4 |
5 | actual fun Modifier.notchPadding(): Modifier = Modifier
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/platform.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | actual fun platformType(): Platform = Platform.JVM
4 |
5 | actual suspend fun fcmToken() = ""
6 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/platformModule.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone
2 |
3 | import dev.baseio.database.SlackDB
4 | import dev.baseio.slackdata.DriverFactory
5 | import dev.baseio.slackdata.SKKeyValueData
6 | import org.koin.core.module.Module
7 | import org.koin.dsl.module
8 |
9 | actual fun platformModule(): Module {
10 | return module {
11 | single { SKKeyValueData() }
12 | single { SlackDB.invoke(DriverFactory().createDriver()) }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackclone/qrscanner/qrCodeGenerate.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackclone.qrscanner
2 |
3 | import com.google.zxing.BarcodeFormat
4 | import com.google.zxing.client.j2se.MatrixToImageWriter
5 | import com.google.zxing.qrcode.QRCodeWriter
6 | import java.io.ByteArrayOutputStream
7 |
8 | actual suspend fun qrCodeGenerate(data: String): ByteArray {
9 | val writer = QRCodeWriter()
10 | val bitMatrix = writer.encode(data, BarcodeFormat.QR_CODE, 400, 400)
11 | with(ByteArrayOutputStream()) {
12 | MatrixToImageWriter.writeToStream(bitMatrix, "png", this)
13 | return this.toByteArray()
14 | }
15 | }
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackdata/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import app.cash.sqldelight.db.QueryResult.AsyncValue
4 | import app.cash.sqldelight.db.SqlDriver
5 | import app.cash.sqldelight.db.SqlSchema
6 | import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
7 | import dev.baseio.database.SlackDB
8 | import java.io.File
9 |
10 | actual class DriverFactory {
11 | actual fun createDriver(): SqlDriver {
12 | val databasePath = File(System.getProperty("user.home"), "SlackDB.db")
13 | return JdbcSqliteDriver("jdbc:sqlite:" + databasePath.absolutePath).also {
14 | SlackDB.Schema.create(it)
15 | }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackdata/SKKeyValueData.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdata
2 |
3 | import java.util.prefs.Preferences
4 |
5 | actual class SKKeyValueData {
6 | private val rootPreferences: Preferences = Preferences.userRoot()
7 | private var preferences: Preferences? = null
8 | private val defaultPreferences = rootPreferences.node(System.getProperty("user.home"))
9 |
10 | actual fun save(key: String, value: String, workspaceId: String?) {
11 | (preferences ?: defaultPreferences).put(key, value)
12 | }
13 |
14 | actual fun get(key: String, workspaceId: String?): String? {
15 | return (preferences ?: defaultPreferences).get(key, null)
16 | }
17 |
18 | actual fun clear(workspaceId: String?) {
19 | (preferences ?: defaultPreferences).clear()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/composeApp/src/jvmMain/kotlin/dev/baseio/slackdomain/util/TimeUnit.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackdomain.util
2 |
3 | actual enum class TimeUnit(val javaTimeUnit: java.util.concurrent.TimeUnit) {
4 | DAYS(java.util.concurrent.TimeUnit.DAYS),
5 | HOURS(java.util.concurrent.TimeUnit.HOURS),
6 | MILLISECONDS(java.util.concurrent.TimeUnit.MILLISECONDS),
7 | MINUTES(java.util.concurrent.TimeUnit.MINUTES),
8 | SECONDS(java.util.concurrent.TimeUnit.SECONDS)
9 | }
10 |
11 | actual fun TimeUnit.toMillis(duration: Long): Long {
12 | return javaTimeUnit.toMillis(duration)
13 | }
14 |
--------------------------------------------------------------------------------
/composeApp/webpack.config.d/config.js:
--------------------------------------------------------------------------------
1 | const TerserPlugin = require("terser-webpack-plugin");
2 |
3 | config.optimization = config.optimization || {};
4 | config.optimization.minimize = true;
5 | config.optimization.minimizer = [
6 | new TerserPlugin({
7 | terserOptions: {
8 | mangle: true, // Note: By default, mangle is set to true.
9 | compress: false, // Disable the transformations that reduce the code size.
10 | output: {
11 | beautify: false,
12 | },
13 | },
14 | }),
15 | ];
--------------------------------------------------------------------------------
/composeApp/webpack.config.d/sqljs-config.js:
--------------------------------------------------------------------------------
1 | // {project}/webpack.config.d/sqljs.js
2 | config.resolve = {
3 | fallback: {
4 | fs: false,
5 | path: false,
6 | crypto: false,
7 | }
8 | };
9 |
10 | const CopyWebpackPlugin = require('copy-webpack-plugin');
11 | config.plugins.push(
12 | new CopyWebpackPlugin({
13 | patterns: [
14 | '../../node_modules/sql.js/dist/sql-wasm.wasm'
15 | ]
16 | })
17 | );
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #Gradle
2 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
3 | org.gradle.caching=true
4 | org.gradle.configuration-cache=true
5 |
6 | #Kotlin
7 | kotlin.code.style=official
8 | kotlin.js.compiler=ir
9 |
10 | #Android
11 | android.useAndroidX=true
12 | android.nonTransitiveRClass=true
13 |
14 | #Compose
15 | org.jetbrains.compose.experimental.uikit.enabled=true
16 | org.jetbrains.compose.experimental.jscanvas.enabled=true
17 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/iosApp/Podfile:
--------------------------------------------------------------------------------
1 | target 'iosApp' do
2 | use_frameworks!
3 | platform :ios, '14.1'
4 | pod 'Protobuf'
5 | pod 'gRPC-ProtoRPC'
6 | pod 'capillaryslack', :path => '../slack_capillary_ios'
7 | end
8 |
9 | post_install do |installer|
10 | installer.pods_project.targets.each do |target|
11 | target.build_configurations.each do |config|
12 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleURLTypes
6 |
7 |
8 | CFBundleTypeRole
9 | Editor
10 | CFBundleURLSchemes
11 |
12 | slackclone
13 |
14 |
15 |
16 | UISupportedInterfaceOrientations
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationLandscapeLeft
20 | UIInterfaceOrientationLandscapeRight
21 |
22 | UISupportedInterfaceOrientations~ipad
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationPortraitUpsideDown
26 | UIInterfaceOrientationLandscapeLeft
27 | UIInterfaceOrientationLandscapeRight
28 |
29 | NSCameraUsageDescription
30 | This app uses camera for capturing photos
31 | UIApplicationSceneManifest
32 |
33 | UIApplicationSupportsMultipleScenes
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/iosApp.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import ComposeApp
4 |
5 | @main
6 | struct iosApp: App {
7 | var body: some Scene {
8 | WindowGroup {
9 | ContentView().onOpenURL { url in
10 | print(url.absoluteString)
11 | if let scheme = url.scheme,
12 | scheme.localizedCaseInsensitiveCompare("slackclone") == .orderedSame,
13 | let _ = url.host {
14 |
15 | var parameters: [String: String] = [:]
16 | URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.forEach {
17 | parameters[$0.name] = $0.value
18 | }
19 | if let token = parameters["token"] {
20 | if !token.isEmpty {
21 | Main_iosKt.rootComponent.navigateAuthorizeWithToken(token: token)
22 | }
23 | }
24 | }
25 | }
26 | }
27 | }
28 | }
29 |
30 | struct ContentView: View {
31 | var body: some View {
32 | ComposeView().ignoresSafeArea(.all)
33 | }
34 | }
35 |
36 | struct ComposeView: UIViewControllerRepresentable {
37 | func makeUIViewController(context: Context) -> UIViewController {
38 | Main_iosKt.MainViewController()
39 | }
40 |
41 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
42 | }
43 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | adminsdk.json
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/server/README.md
--------------------------------------------------------------------------------
/server/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | application
4 | }
5 |
6 | group = "dev.baseio.slackserver"
7 | version = "1.0"
8 |
9 | dependencies {
10 | implementation(libs.mail)
11 | testImplementation(kotlin("test"))
12 | testImplementation(libs.turbine)
13 | testImplementation(libs.kotlinx.coroutines.test)
14 | testImplementation(libs.grpc.testing)
15 |
16 | implementation(libs.bcprov.jdk16)
17 | implementation(libs.firebase.admin)
18 | implementation(libs.thymeleaf)
19 |
20 | implementation(project(":slack_generate_protos"))
21 |
22 | implementation(libs.koin.core)
23 |
24 | implementation(libs.grpc.netty)
25 | implementation(libs.zxing.core)
26 | implementation(libs.zxing.javase)
27 |
28 | // mongodb
29 | implementation(libs.kmongo)
30 | implementation(libs.kmongo.async)
31 | implementation(libs.kmongo.coroutine)
32 |
33 | // jwt
34 | implementation(libs.jjwt.api)
35 | runtimeOnly(libs.jjwt.impl)
36 | runtimeOnly(libs.jjwt.orgjson)
37 |
38 | // passwords
39 | implementation(libs.bcrypt)
40 | implementation(project(":capillary-kmp"))
41 |
42 | }
43 |
44 | tasks.test {
45 | useJUnitPlatform()
46 | }
47 |
48 | application {
49 | mainClass.set("MainKt")
50 | }
51 |
--------------------------------------------------------------------------------
/server/gradle.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/server/gradle.properties
--------------------------------------------------------------------------------
/server/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/server/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/server/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/server/gradle/wrapper/gradle-wrapper.properties
--------------------------------------------------------------------------------
/server/gradlew:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/server/gradlew
--------------------------------------------------------------------------------
/server/gradlew.bat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/server/gradlew.bat
--------------------------------------------------------------------------------
/server/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "server"
2 |
3 | include(":slack_generate_protos")
4 | include(":slack_protos")
5 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/communications/NotificationType.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.communications
2 |
3 | enum class NotificationType(val titleMessage: String, val bodyMessage: String) {
4 | CHANNEL_CREATED(
5 | bodyMessage = "A new channel %s was created",
6 | titleMessage = "New Group Message Channel!"
7 | ),
8 | DM_CHANNEL_CREATED(
9 | bodyMessage = "A new conversation was initiated by %s",
10 | titleMessage = "New Direct Message Channel!"
11 | ),
12 | ADDED_CHANNEL(
13 | titleMessage = "Added to Channel",
14 | bodyMessage = "You were added to a slack channel by %s"
15 | ),
16 | NEW_MESSAGE(titleMessage = "New Message", bodyMessage = "You have received a new message. %s")
17 | }
18 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/communications/PNChannel.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.communications
2 |
3 | import dev.baseio.slackserver.data.models.SKUserPushToken
4 | import dev.baseio.slackserver.data.models.SkChannel
5 | import dev.baseio.slackserver.data.models.SkUser
6 | import dev.baseio.slackserver.data.sources.ChannelMemberDataSource
7 | import dev.baseio.slackserver.data.sources.UserPushTokenDataSource
8 | import dev.baseio.slackserver.data.sources.UsersDataSource
9 |
10 | class PNChannel(
11 | private val usersDataSource: UsersDataSource,
12 | private val channelMemberDataSource: ChannelMemberDataSource,
13 | private val userPushTokenDataSource: UserPushTokenDataSource
14 | ) :
15 | PNSender() {
16 |
17 | override suspend fun getSender(senderUserId: String, request: SkChannel): SkUser {
18 | return usersDataSource.getUser(senderUserId, request.workspaceId)!!
19 | }
20 |
21 | override suspend fun getPushTokens(request: SkChannel): List {
22 | val tokens = mutableListOf()
23 | channelMemberDataSource.getMembers(request.workspaceId, request.channelId).map { it.memberId }
24 | .let { skChannelMembers ->
25 | tokens.addAll(userPushTokenDataSource.getPushTokensFor(skChannelMembers))
26 | }
27 | return tokens
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/communications/PNChannelMember.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.communications
2 |
3 | import dev.baseio.slackserver.data.models.SKUserPushToken
4 | import dev.baseio.slackserver.data.models.SkChannelMember
5 | import dev.baseio.slackserver.data.models.SkUser
6 | import dev.baseio.slackserver.data.sources.UserPushTokenDataSource
7 | import dev.baseio.slackserver.data.sources.UsersDataSource
8 |
9 | class PNChannelMember(
10 | private val userPushTokenDataSource: UserPushTokenDataSource,
11 | private val usersDataSource: UsersDataSource
12 | ) : PNSender() {
13 | override suspend fun getSender(senderUserId: String, request: SkChannelMember): SkUser? {
14 | return usersDataSource.getUser(senderUserId, request.workspaceId)
15 | }
16 |
17 | override suspend fun getPushTokens(request: SkChannelMember): List {
18 | return userPushTokenDataSource.getPushTokensFor(listOf(request.memberId))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/communications/PNMessages.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.communications
2 |
3 | import dev.baseio.slackserver.data.models.SKUserPushToken
4 | import dev.baseio.slackserver.data.models.SkMessage
5 | import dev.baseio.slackserver.data.models.SkUser
6 | import dev.baseio.slackserver.data.sources.ChannelMemberDataSource
7 | import dev.baseio.slackserver.data.sources.UserPushTokenDataSource
8 | import dev.baseio.slackserver.data.sources.UsersDataSource
9 |
10 | class PNMessages(
11 | private val channelMemberDataSource: ChannelMemberDataSource,
12 | private val userPushTokenDataSource: UserPushTokenDataSource,
13 | private val usersDataSource: UsersDataSource
14 | ) : PNSender() {
15 |
16 | override suspend fun getSender(senderUserId: String, request: SkMessage): SkUser {
17 | return usersDataSource.getUser(senderUserId, request.workspaceId)!!
18 | }
19 |
20 | override suspend fun getPushTokens(request: SkMessage): List {
21 | val tokens = mutableListOf()
22 | channelMemberDataSource.getMembers(request.workspaceId, request.channelId).map { it.memberId }
23 | .let { skChannelMembers ->
24 | tokens.addAll(userPushTokenDataSource.getPushTokensFor(skChannelMembers))
25 | }
26 | return tokens
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/impl/AuthDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.impl
2 |
3 | import dev.baseio.slackserver.data.models.SkUser
4 | import dev.baseio.slackserver.data.sources.AuthDataSource
5 | import kotlinx.coroutines.reactive.awaitFirstOrNull
6 | import org.litote.kmongo.coroutine.CoroutineDatabase
7 | import org.litote.kmongo.eq
8 | import org.litote.kmongo.reactivestreams.findOne
9 | import java.util.*
10 |
11 | class AuthDataSourceImpl(private val slackCloneDB: CoroutineDatabase) : AuthDataSource {
12 | override suspend fun register(user: SkUser): SkUser? {
13 | // save the user details
14 | if (user.email.trim().isEmpty()) {
15 | throw Exception("email cannot be empty!")
16 | }
17 | if (user.uuid.trim().isEmpty()) {
18 | throw Exception("user uuid cannot be empty!")
19 | }
20 | slackCloneDB.getCollection().collection.insertOne(
21 | user
22 | ).awaitFirstOrNull()
23 | // save the auth
24 |
25 | return slackCloneDB.getCollection().collection.findOne(SkUser::uuid eq user.uuid).awaitFirstOrNull()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/impl/UserPushTokenDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.impl
2 |
3 | import dev.baseio.slackserver.data.models.SKUserPushToken
4 | import dev.baseio.slackserver.data.sources.UserPushTokenDataSource
5 | import org.litote.kmongo.coroutine.CoroutineDatabase
6 | import org.litote.kmongo.coroutine.insertOne
7 | import org.litote.kmongo.eq
8 | import org.litote.kmongo.`in`
9 |
10 | class UserPushTokenDataSourceImpl(private val coroutineDatabase: CoroutineDatabase) : UserPushTokenDataSource {
11 | override suspend fun getPushTokensFor(userIds: List): List {
12 | return coroutineDatabase.getCollection().find(SKUserPushToken::userId `in` userIds).toList()
13 | }
14 |
15 | override suspend fun savePushToken(toSkUserPushToken: SKUserPushToken) {
16 | val exists =
17 | coroutineDatabase.getCollection()
18 | .find(SKUserPushToken::token eq toSkUserPushToken.token).toList().isNotEmpty()
19 | if (!exists) {
20 | coroutineDatabase.getCollection().insertOne(toSkUserPushToken)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/models/IDataMap.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.models
2 |
3 | interface IDataMap {
4 | fun provideMap(): Map
5 | }
6 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/models/SKEncryptedMessage.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.models
2 |
3 | data class SKEncryptedMessage(val first: String, val second: String)
4 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/models/SKLastMessage.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.models
2 |
3 | data class SKLastMessage(
4 | val channel: SkChannel,
5 | val message: SkMessage
6 | )
7 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/models/SKUserPublicKey.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.models
2 |
3 | data class SKUserPublicKey(
4 | val keyBytes: ByteArray
5 | ) {
6 | override fun equals(other: Any?): Boolean {
7 | if (this === other) return true
8 | if (javaClass != other?.javaClass) return false
9 |
10 | other as SKUserPublicKey
11 |
12 | if (!keyBytes.contentEquals(other.keyBytes)) return false
13 |
14 | return true
15 | }
16 |
17 | override fun hashCode(): Int {
18 | return keyBytes.contentHashCode()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/models/SKUserPushToken.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.models
2 |
3 | data class SKUserPushToken(val uuid: String, val userId: String, val platform: Int, val token: String)
4 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/models/SKUserRole.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.models
2 |
3 | /**
4 | * @property roleName: The SKUserRoleName associated with the user
5 | * @property userId: The user id of the slack user.
6 | */
7 | data class SKUserRole(val role: SKUserRoleName, val userId: String)
8 |
9 | enum class SKUserRoleName {
10 | GUEST, MEMBER, ADMIN, WORKSPACE_OWNER, SLACK_ADMIN
11 | }
12 |
13 | /**
14 | * @property permissions: The permissions of the user
15 | * @property userId: The user id of the slack user
16 | */
17 | data class SKUserPermission(val permissions: List, val userId: String)
18 |
19 | enum class SKUserPermissionName {
20 | SEND_MESSAGE, UPLOAD_FILES,
21 | JOIN_PUBLIC_CHANNEL,
22 | DELETE_MESSAGE,
23 | DELETE_OWN_MESSAGE,
24 | CREATE_CHANNEL,
25 | CREATE_PRIVATE_CHANNEL,
26 | CONVERT_CHANNEL,
27 | ARCHIVE_CHANNEL,
28 | RENAME_CHANNEL,
29 | DELETE_CHANNEL,
30 | INSTALL_APPS,
31 | DOWNGRADE_WORKSPACE,
32 | UPGRADE_WORKSPACE,
33 | DEACTIVATE_WORKSPACE
34 | }
35 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/models/SkUser.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.models
2 |
3 | data class SkUser(
4 | val uuid: String,
5 | val workspaceId: String,
6 | val gender: String?,
7 | val name: String,
8 | val location: String?,
9 | val email: String,
10 | val username: String,
11 | val userSince: Long,
12 | val phone: String,
13 | val avatarUrl: String,
14 | val publicKey: SKUserPublicKey
15 | ) {
16 | companion object {
17 | const val NAME = "skUser"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/models/SkWorkspace.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.models
2 |
3 | data class SkWorkspace(
4 | val uuid: String,
5 | val name: String,
6 | val domain: String,
7 | val picUrl: String?,
8 | val modifiedTime: Long
9 | )
10 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/sources/AuthDataSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.sources
2 |
3 | import dev.baseio.slackserver.data.models.SkUser
4 |
5 | interface AuthDataSource {
6 | suspend fun register(user: SkUser): SkUser?
7 | }
8 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/sources/ChannelMemberDataSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.sources
2 |
3 | import dev.baseio.slackserver.data.models.SkChannel
4 | import dev.baseio.slackserver.data.models.SkChannelMember
5 |
6 | interface ChannelMemberDataSource {
7 | suspend fun addMembers(listOf: List)
8 | suspend fun getMembers(workspaceId: String, channelId: String): List
9 | suspend fun getChannelIdsForUserAndWorkspace(userId: String, workspaceId: String): List
10 | suspend fun isMember(userId: String, workspaceId: String, channelId: String): Boolean
11 | }
12 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/sources/ChannelsDataSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.sources
2 |
3 | import dev.baseio.slackserver.data.models.SkChannel
4 | import dev.baseio.slackserver.data.models.SkChannelMember
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface ChannelsDataSource {
8 | fun getChannelChangeStream(workspaceId: String): Flow>
9 | fun getDMChannelChangeStream(workspaceId: String): Flow>
10 | suspend fun savePublicChannel(request: SkChannel.SkGroupChannel, adminId: String): SkChannel.SkGroupChannel?
11 | suspend fun saveDMChannel(request: SkChannel.SkDMChannel): SkChannel.SkDMChannel?
12 | suspend fun getAllChannels(workspaceId: String, userId: String): List
13 | suspend fun getAllDMChannels(workspaceId: String, userId: String): List
14 | suspend fun checkIfDMChannelExists(userId: String, receiverId: String?): SkChannel.SkDMChannel?
15 | suspend fun getChannelById(channelId: String, workspaceId: String): SkChannel?
16 |
17 | suspend fun getChannelByName(channelId: String, workspaceId: String): SkChannel?
18 | fun getChannelMemberChangeStream(
19 | workspaceId: String,
20 | memberId: String
21 | ): Flow>
22 |
23 | suspend fun checkIfGroupExisits(workspaceId: String?, name: String?): Boolean
24 | }
25 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/sources/MessagesDataSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.sources
2 |
3 | import dev.baseio.slackdata.protos.SKWorkspaceChannelRequest
4 | import dev.baseio.slackserver.data.models.SkMessage
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface MessagesDataSource {
8 | suspend fun saveMessage(request: SkMessage): SkMessage
9 | suspend fun getMessages(workspaceId: String, channelId: String, limit: Int, offset: Int): List
10 | fun registerForChanges(request: SKWorkspaceChannelRequest): Flow>
11 | suspend fun updateMessage(request: SkMessage): SkMessage?
12 | suspend fun getMessage(uuid: String, workspaceId: String): SkMessage?
13 | }
14 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/sources/UserPushTokenDataSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.sources
2 |
3 | import dev.baseio.slackserver.data.models.SKUserPushToken
4 |
5 | interface UserPushTokenDataSource {
6 | suspend fun getPushTokensFor(userIds: List): List
7 | suspend fun savePushToken(toSkUserPushToken: SKUserPushToken)
8 | }
9 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/sources/UsersDataSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.sources
2 |
3 | import dev.baseio.slackserver.data.models.SkUser
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface UsersDataSource {
7 | suspend fun saveUser(skUser: SkUser): SkUser?
8 | fun getChangeInUserFor(workspaceId: String): Flow>
9 | suspend fun getUsers(workspaceId: String): List
10 | suspend fun getUser(userId: String, workspaceId: String): SkUser?
11 | suspend fun updateUser(request: SkUser): SkUser?
12 | suspend fun getUserWithEmailId(emailId: String, workspaceId: String): SkUser?
13 | suspend fun getUserWithUsername(userName: String?, workspaceId: String): SkUser?
14 | suspend fun getUserWithUserId(userId: String, workspaceId: String): SkUser?
15 | }
16 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/data/sources/WorkspaceDataSource.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.data.sources
2 |
3 | import dev.baseio.slackserver.data.models.SkWorkspace
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface WorkspaceDataSource {
7 | suspend fun getWorkspaces(): List
8 | suspend fun saveWorkspace(skWorkspace: SkWorkspace): SkWorkspace?
9 | suspend fun getWorkspace(workspaceId: String): SkWorkspace?
10 | suspend fun findWorkspacesForEmail(email: String): List
11 | suspend fun findWorkspaceForName(name: String): SkWorkspace?
12 | suspend fun updateWorkspace(toDBWorkspace: SkWorkspace): SkWorkspace?
13 | fun registerForChanges(uuid: String?): Flow>
14 | }
15 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/services/IQrCodeGenerator.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.services
2 |
3 | import dev.baseio.slackdata.protos.SKAuthResult
4 | import dev.baseio.slackdata.protos.SKQRAuthVerify
5 | import dev.baseio.slackdata.protos.SKQrCodeResponse
6 | import java.nio.file.Path
7 | import kotlin.io.path.deleteIfExists
8 |
9 | interface IQrCodeGenerator {
10 | val inMemoryQrCodes: HashMap Unit>> // TODO this is dirty!
11 |
12 | fun process(data: String): Pair
13 |
14 | fun notifyAuthenticated(result: SKAuthResult, request: SKQRAuthVerify) {
15 | inMemoryQrCodes[request.token]?.first?.deleteIfExists()
16 | inMemoryQrCodes[request.token]?.second?.invoke(result)
17 | inMemoryQrCodes.remove(request.token)
18 | }
19 |
20 | fun removeQrCode(data: String) {
21 | inMemoryQrCodes[data]?.first?.deleteIfExists()
22 | inMemoryQrCodes.remove(data)
23 | }
24 |
25 | fun find(token: String): Pair Unit>? {
26 | return inMemoryQrCodes[token]
27 | }
28 |
29 | fun put(data: String, result: Pair, function: (SKAuthResult) -> Unit) {
30 | inMemoryQrCodes[data] = Pair(result.second, function)
31 | }
32 |
33 | fun randomToken(): String
34 | }
35 |
--------------------------------------------------------------------------------
/server/src/main/kotlin/dev/baseio/slackserver/services/SlackConstants.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.slackserver.services
2 |
3 | class SlackConstants {
4 | companion object {
5 | const val CIPHERTEXT_KEY: String = "CIPHERTEXT_KEY"
6 | const val KEY_ALGORITHM_KEY = "KEY_ALGORITHM_KEY"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/server/src/test/kotlin/FakeQrCodeGenerator.kt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/server/src/test/kotlin/FakeQrCodeGenerator.kt
--------------------------------------------------------------------------------
/server/src/test/kotlin/TestQRCodeService.kt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/SlackComposeMultiplatform/667d6afe3d1bc58ea30c29e44a67c544b516e151/server/src/test/kotlin/TestQRCodeService.kt
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "SlackCMP"
2 | include(":composeApp")
3 | include(":slack_protos")
4 | include(":slack_generate_protos")
5 | include(":capillary-kmp")
6 | include(":server")
7 |
8 | pluginManagement {
9 | repositories {
10 | google()
11 | gradlePluginPortal()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | dependencyResolutionManagement {
17 | repositories {
18 | google()
19 | mavenCentral()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/slack_capillary_ios/Podfile:
--------------------------------------------------------------------------------
1 | target 'capillaryslack' do
2 | use_frameworks!
3 |
4 | end
5 |
--------------------------------------------------------------------------------
/slack_capillary_ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODFILE CHECKSUM: 420a22286b0c342fd762af9a25dd1bcc71bf13bd
2 |
3 | COCOAPODS: 1.12.0
4 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 |
3 | s.name = "capillaryslack"
4 | s.version = "1.0.0"
5 | s.summary = "Public key RSA encryption."
6 | s.static_framework = true
7 | s.description = <<-DESC
8 | Encrypt with a RSA public key, decrypt with a RSA private key.
9 | DESC
10 |
11 | s.homepage = "https://github.com/oianmol/slack_capillary_ios"
12 | s.license = "MIT"
13 | s.author = { "Anmol Verma" => "anmol.verma4@gmail.com" }
14 |
15 | s.source = { :git => "https://github.com/oianmol/slack_capillary_ios.git", :branch => "master" }
16 | s.source_files = "capillaryslack/*.{swift,m,h}"
17 | s.framework = "Security"
18 | s.requires_arc = true
19 |
20 | s.swift_version = "5.0"
21 | s.ios.deployment_target = "14.0"
22 |
23 | s.subspec "ObjC" do |sp|
24 | sp.source_files = "capillaryslack/*.{swift,m,h}"
25 | end
26 | end
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack/EncryptedData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EncryptedData.swift
3 | // capillaryslack
4 | //
5 | // Created by Anmol Verma on 02/12/22.
6 | //
7 |
8 | import Foundation
9 |
10 | @objc public class EncryptedData: NSObject {
11 |
12 | @objc public private(set) var first: String?
13 | @objc public private(set) var second: String?
14 |
15 | private init(_ first: String?, _ second: String?) {
16 | super.init()
17 | self.first = first
18 | self.second = second
19 | }
20 |
21 | @objc public func firstItem() -> String? {
22 | return first
23 | }
24 |
25 | @objc public func secondItem() -> String? {
26 | return second
27 | }
28 |
29 | public convenience init(first: String?,second:String?) {
30 | self.init(first, second)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack/Info-tvOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.7.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 | UIRequiredDeviceCapabilities
26 |
27 | arm64
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.7.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 | SupportedPlatform
26 | ios
27 | SupportedPlatformVariant
28 | maccatalyst
29 |
30 |
31 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack/Message.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Message.swift
3 | // SwiftyRSA
4 | //
5 | // Created by Loïs Di Qual on 9/19/16.
6 | // Copyright © 2016 Scoop. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol Message {
12 | var data: Data { get }
13 | var base64String: String { get }
14 | init(data: Data)
15 | init(base64Encoded base64String: String) throws
16 | }
17 |
18 | public extension Message {
19 |
20 | /// Base64-encoded string of the message data
21 | var base64String: String {
22 | return data.base64EncodedString()
23 | }
24 |
25 | /// Creates an encrypted message with a base64-encoded string.
26 | ///
27 | /// - Parameter base64String: Base64-encoded data of the encrypted message
28 | /// - Throws: SwiftyRSAError
29 | init(base64Encoded base64String: String) throws {
30 | guard let data = Data(base64Encoded: base64String) else {
31 | throw SwiftyRSAError.invalidBase64String
32 | }
33 | self.init(data: data)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack/capillaryios-Bridging-Header.h:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/slack_capillary_ios/capillaryslack/capillaryslack.h:
--------------------------------------------------------------------------------
1 | //
2 | // capillaryslack.h
3 | // capillaryslack
4 | //
5 | // Created by Anmol Verma on 23/11/22.
6 | //
7 |
8 | #import
9 |
10 | //! Project version number for capillaryslack.
11 | FOUNDATION_EXPORT double capillaryslackVersionNumber;
12 |
13 | //! Project version string for capillaryslack.
14 | FOUNDATION_EXPORT const unsigned char capillaryslackVersionString[];
15 |
16 | // In this header, you should import all the public headers of your framework using statements like #import
17 |
18 |
19 |
--------------------------------------------------------------------------------
/slack_capillary_ios/sampleapp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/slack_capillary_ios/sampleapp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/slack_capillary_ios/sampleapp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/slack_capillary_ios/sampleapp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/slack_capillary_ios/sampleapp/sampleappApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // sampleappApp.swift
3 | // sampleapp
4 | //
5 | // Created by Anmol Verma on 02/12/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct sampleappApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/slack_capillary_ios/sampleappTests/sampleappTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // sampleappTests.swift
3 | // sampleappTests
4 | //
5 | // Created by Anmol Verma on 02/12/22.
6 | //
7 |
8 | import XCTest
9 | @testable import sampleapp
10 |
11 | final class sampleappTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | // Any test you write for XCTest can be annotated as throws and async.
25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
27 | }
28 |
29 | func testPerformanceExample() throws {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/slack_capillary_ios/sampleappUITests/sampleappUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // sampleappUITestsLaunchTests.swift
3 | // sampleappUITests
4 | //
5 | // Created by Anmol Verma on 02/12/22.
6 | //
7 |
8 | import XCTest
9 |
10 | final class sampleappUITestsLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | func testLaunch() throws {
21 | let app = XCUIApplication()
22 | app.launch()
23 |
24 | // Insert steps here to perform after app launch but before taking a screenshot,
25 | // such as logging into a test account or navigating somewhere in the app
26 |
27 | let attachment = XCTAttachment(screenshot: app.screenshot())
28 | attachment.name = "Launch Screen"
29 | attachment.lifetime = .keepAlways
30 | add(attachment)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/slack_protos/.gitignore:
--------------------------------------------------------------------------------
1 | ### Java template
2 | # Compiled class file
3 | *.class
4 | .idea
5 |
6 | # Log file
7 | *.log
8 |
9 | # BlueJ files
10 | *.ctxt
11 |
12 | # Mobile Tools for Java (J2ME)
13 | .mtj.tmp/
14 |
15 | # Package Files #
16 | *.jar
17 | *.war
18 | *.nar
19 | *.ear
20 | *.zip
21 | *.tar.gz
22 | *.rar
23 |
24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
25 | hs_err_pid*
26 |
27 | ### Gradle template
28 | .gradle
29 | **/build/
30 | !src/**/build/
31 |
32 | # Ignore Gradle GUI config
33 | gradle-app.setting
34 |
35 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
36 | !gradle-wrapper.jar
37 |
38 | # Cache of project
39 | .gradletasknamecache
40 |
41 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
42 | # gradle/wrapper/gradle-wrapper.properties
43 |
44 | ### Kotlin template
45 | # Compiled class file
46 | *.class
47 |
48 | # Log file
49 | *.log
50 |
51 | # BlueJ files
52 | *.ctxt
53 |
54 | # Mobile Tools for Java (J2ME)
55 | .mtj.tmp/
56 |
57 | # Package Files #
58 | *.jar
59 | *.war
60 | *.nar
61 | *.ear
62 | *.zip
63 | *.tar.gz
64 | *.rar
65 |
66 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
67 | hs_err_pid*
68 |
69 | !/gradlew
70 | /gradlew
71 | /gradlew.bat
72 | /gradle/
73 |
--------------------------------------------------------------------------------
/slack_protos/build.gradle.kts:
--------------------------------------------------------------------------------
1 | version = "unspecified"
2 | plugins {
3 | `java-library`
4 | }
5 |
6 | java {
7 | sourceSets.getByName("main").resources.srcDir("src/main/proto")
8 | }
9 |
--------------------------------------------------------------------------------
/slack_protos/src/main/proto/common.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package dev.baseio.slackdata.common;
4 |
5 | option java_multiple_files = true;
6 |
7 | message SKByteArrayElement{
8 | int32 byte = 1;
9 | }
10 |
11 | message Empty {
12 | string nothing = 1;
13 | }
--------------------------------------------------------------------------------