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