├── demokmp.jks ├── readmeFiles ├── KMP_logo.png ├── en │ └── screenshots │ │ ├── pc │ │ ├── screenshot_1.png │ │ ├── screenshot_2.png │ │ ├── screenshot_3.png │ │ ├── screenshot_4.png │ │ ├── screenshot_5.png │ │ ├── screenshot_6.png │ │ ├── screenshot_7.png │ │ ├── screenshot_8.png │ │ ├── screenshot_9.png │ │ ├── screenshot_10.png │ │ ├── screenshot_11.png │ │ ├── screenshot_12.png │ │ └── screenshot_13.png │ │ └── android │ │ ├── screenshot_1.jpg │ │ ├── screenshot_2.jpg │ │ ├── screenshot_3.jpg │ │ ├── screenshot_4.jpg │ │ ├── screenshot_5.jpg │ │ ├── screenshot_6.jpg │ │ ├── screenshot_7.jpg │ │ └── screenshot_8.jpg └── ru │ └── screenshots │ ├── pc │ ├── screenshot_1.png │ ├── screenshot_2.png │ ├── screenshot_3.png │ ├── screenshot_4.png │ ├── screenshot_5.png │ ├── screenshot_6.png │ ├── screenshot_7.png │ ├── screenshot_8.png │ ├── screenshot_9.png │ ├── screenshot_10.png │ ├── screenshot_11.png │ ├── screenshot_12.png │ └── screenshot_13.png │ └── android │ ├── screenshot_1.jpg │ ├── screenshot_2.jpg │ ├── screenshot_3.jpg │ ├── screenshot_4.jpg │ ├── screenshot_5.jpg │ ├── screenshot_6.jpg │ ├── screenshot_7.jpg │ └── screenshot_8.jpg ├── runScripts ├── linux │ ├── icon.png │ ├── create_desktop_shortcut.sh │ └── run.sh └── windows │ ├── icon.ico │ ├── run.vbs │ ├── create_desktop_shortcut.vbs │ └── launcher_script.ps1 ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── composeApp └── src │ ├── androidMain │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── values-ru │ │ │ └── strings.xml │ │ ├── xml │ │ │ └── locales_config.xml │ │ ├── values-night │ │ │ └── colors.xml │ │ └── mipmap-anydpi │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ ├── kotlin │ │ └── ru │ │ │ └── rznnike │ │ │ └── demokmp │ │ │ ├── app │ │ │ ├── utils │ │ │ │ ├── AppConstants.kt │ │ │ │ ├── ClipboardUtils.android.kt │ │ │ │ ├── ViewUtils.android.kt │ │ │ │ ├── PlatformUtils.android.kt │ │ │ │ ├── WindowViewModel.android.kt │ │ │ │ └── ActivityUtils.kt │ │ │ ├── ui │ │ │ │ └── screen │ │ │ │ │ ├── home │ │ │ │ │ └── HomeFlow.kt │ │ │ │ │ ├── splash │ │ │ │ │ └── SplashFlow.kt │ │ │ │ │ ├── customui │ │ │ │ │ └── CustomUIFlow.kt │ │ │ │ │ ├── settings │ │ │ │ │ └── SettingsFlow.kt │ │ │ │ │ ├── dbexample │ │ │ │ │ └── DBExampleFlow.kt │ │ │ │ │ ├── httpexample │ │ │ │ │ └── HTTPExampleFlow.kt │ │ │ │ │ ├── chartexample │ │ │ │ │ └── ChartExampleFlow.kt │ │ │ │ │ ├── wsexample │ │ │ │ │ └── WebSocketsExampleFlow.kt │ │ │ │ │ └── navigation │ │ │ │ │ └── NavigationExampleFlow.kt │ │ │ ├── di │ │ │ │ ├── NetworkModule.android.kt │ │ │ │ ├── PreferenceModule.android.kt │ │ │ │ └── DatabaseModule.android.kt │ │ │ ├── navigation │ │ │ │ └── NavigationUtils.android.kt │ │ │ └── viewmodel │ │ │ │ └── app │ │ │ │ └── ActivityViewModel.kt │ │ │ ├── data │ │ │ └── shell │ │ │ │ └── ShellManager.android.kt │ │ │ └── domain │ │ │ └── log │ │ │ └── Logger.android.kt │ └── AndroidManifest.xml │ ├── commonMain │ ├── composeResources │ │ ├── font │ │ │ ├── consolas.ttf │ │ │ ├── ubuntu_bold.ttf │ │ │ ├── ubuntu_italic.ttf │ │ │ ├── ubuntu_light.ttf │ │ │ ├── ubuntu_medium.ttf │ │ │ ├── ubuntu_regular.ttf │ │ │ ├── ubuntu_bold_italic.ttf │ │ │ ├── ubuntu_light_italic.ttf │ │ │ └── ubuntu_medium_italic.ttf │ │ └── drawable │ │ │ ├── icon_linux.png │ │ │ ├── icon_windows.ico │ │ │ ├── ic_minus.xml │ │ │ ├── ic_plus.xml │ │ │ ├── ic_back.xml │ │ │ ├── ic_expand.xml │ │ │ ├── ic_arrow_down.xml │ │ │ ├── ic_checkbox_off.xml │ │ │ ├── ic_copy.xml │ │ │ ├── ic_checkbox_on.xml │ │ │ ├── ic_save.xml │ │ │ ├── ic_menu.xml │ │ │ ├── ic_refresh.xml │ │ │ ├── ic_zoom.xml │ │ │ ├── ic_delete.xml │ │ │ ├── ic_calendar.xml │ │ │ ├── ic_print.xml │ │ │ ├── ic_logger.xml │ │ │ └── ic_keyboard.xml │ └── kotlin │ │ └── ru │ │ └── rznnike │ │ └── demokmp │ │ ├── domain │ │ ├── log │ │ │ ├── LogType.kt │ │ │ ├── NetworkRequestState.kt │ │ │ ├── LogData.kt │ │ │ ├── LogLevel.kt │ │ │ ├── NetworkLogData.kt │ │ │ ├── LogEvent.kt │ │ │ ├── LogMessage.kt │ │ │ ├── NetworkLogMessage.kt │ │ │ └── extension │ │ │ │ └── ConsoleLoggerExtension.kt │ │ ├── model │ │ │ ├── chart │ │ │ │ └── ChartPoint.kt │ │ │ ├── db │ │ │ │ └── DBExampleData.kt │ │ │ ├── websocket │ │ │ │ ├── WebSocketMessage.kt │ │ │ │ ├── WebSocketConnectionState.kt │ │ │ │ └── WebSocketSessionData.kt │ │ │ ├── print │ │ │ │ ├── PrintSettings.kt │ │ │ │ └── TwoSidedPrint.kt │ │ │ └── common │ │ │ │ ├── Theme.kt │ │ │ │ ├── UiScale.kt │ │ │ │ ├── Language.kt │ │ │ │ └── LauncherConfig.kt │ │ ├── gateway │ │ │ ├── HTTPExampleGateway.kt │ │ │ ├── FileGateway.kt │ │ │ ├── AppGateway.kt │ │ │ ├── ChartExampleGateway.kt │ │ │ ├── PdfExampleGateway.kt │ │ │ ├── ComObjectExampleGateway.kt │ │ │ ├── WebSocketExampleGateway.kt │ │ │ ├── DBExampleGateway.kt │ │ │ ├── PreferencesGateway.kt │ │ │ └── LogGateway.kt │ │ ├── common │ │ │ ├── CoroutineScopeProvider.kt │ │ │ ├── DispatcherProvider.kt │ │ │ └── interactor │ │ │ │ ├── FlowUseCase.kt │ │ │ │ ├── FlowUseCaseWithParams.kt │ │ │ │ ├── UseCase.kt │ │ │ │ ├── UseCaseWithParams.kt │ │ │ │ └── UseCaseResult.kt │ │ ├── utils │ │ │ ├── GlobalConstants.kt │ │ │ ├── CollectionUtils.kt │ │ │ ├── OperatingSystem.kt │ │ │ └── StringUtils.kt │ │ └── interactor │ │ │ ├── log │ │ │ ├── ClearLogUseCase.kt │ │ │ ├── ClearNetworkLogUseCase.kt │ │ │ ├── GetLogUseCase.kt │ │ │ ├── GetNetworkLogUseCase.kt │ │ │ ├── SaveLogToFileUseCase.kt │ │ │ ├── GetNewLogUseCase.kt │ │ │ ├── AddLogMessageToDBUseCase.kt │ │ │ ├── GetNewNetworkLogUseCase.kt │ │ │ ├── AddLogNetworkMessageToDBUseCase.kt │ │ │ ├── DeleteOldLogsUseCase.kt │ │ │ ├── GetLogNetworkMessageAsFlowUseCase.kt │ │ │ ├── GetLogNetworkMessageUseCase.kt │ │ │ └── SaveNetworkLogMessageToFileUseCase.kt │ │ │ ├── dbexample │ │ │ ├── CloseDBUseCase.kt │ │ │ ├── DeleteAllDBExampleDataUseCase.kt │ │ │ ├── GetDBExampleDataListUseCase.kt │ │ │ ├── AddDBExampleDataUseCase.kt │ │ │ ├── DeleteDBExampleDataUseCase.kt │ │ │ └── GetDBExampleDataUseCase.kt │ │ │ ├── app │ │ │ ├── CheckIfAppIsAlreadyRunningUseCase.kt │ │ │ └── CloseAppSingleInstanceSocketUseCase.kt │ │ │ ├── preferences │ │ │ ├── GetTestCounterUseCase.kt │ │ │ ├── GetThemeUseCase.kt │ │ │ ├── GetUiScaleUseCase.kt │ │ │ ├── SetTestCounterUseCase.kt │ │ │ ├── GetLanguageUseCase.kt │ │ │ ├── GetPrintSettingsUseCase.kt │ │ │ ├── SetThemeUseCase.kt │ │ │ ├── SetUiScaleUseCase.kt │ │ │ ├── SetLanguageUseCase.kt │ │ │ └── SetPrintSettingsUseCase.kt │ │ │ ├── comobjectexample │ │ │ ├── GetPCDataUseCase.kt │ │ │ ├── InitShellWrapperUseCase.kt │ │ │ ├── MinimizeAllWindowsUseCase.kt │ │ │ ├── DestroyShellWrapperUseCase.kt │ │ │ └── OpenFolderOrFileUseCase.kt │ │ │ ├── pdfexample │ │ │ └── GetSamplePdfUseCase.kt │ │ │ ├── wsexample │ │ │ ├── CloseAppWSSessionUseCase.kt │ │ │ ├── SendAppWSMessageUseCase.kt │ │ │ └── GetAppWSSessionUseCase.kt │ │ │ ├── chartexample │ │ │ └── GetChartSampleDataUseCase.kt │ │ │ ├── httpexample │ │ │ └── GetRandomImageLinksUseCase.kt │ │ │ └── file │ │ │ └── CopyFileUseCase.kt │ │ ├── app │ │ ├── di │ │ │ ├── DatabaseModule.kt │ │ │ ├── PreferenceModule.kt │ │ │ ├── AppComponent.kt │ │ │ ├── ViewModelModule.kt │ │ │ ├── GatewayModule.kt │ │ │ └── AppModule.kt │ │ ├── ui │ │ │ ├── dialog │ │ │ │ └── common │ │ │ │ │ ├── AlertDialogType.kt │ │ │ │ │ └── AlertDialogAction.kt │ │ │ ├── theme │ │ │ │ └── Shapes.kt │ │ │ └── view │ │ │ │ ├── CustomCheckbox.kt │ │ │ │ ├── TabText.kt │ │ │ │ ├── CustomCheckboxWithText.kt │ │ │ │ ├── OutlinedRoundedButton.kt │ │ │ │ └── FilledButton.kt │ │ ├── navigation │ │ │ ├── NavigationFlow.kt │ │ │ ├── NavigationScreen.kt │ │ │ └── navtype │ │ │ │ ├── NavTypes.kt │ │ │ │ └── BasicNavType.kt │ │ ├── utils │ │ │ ├── ClipboardUtils.kt │ │ │ ├── PlatformUtils.kt │ │ │ ├── ChartUtils.kt │ │ │ ├── WindowViewModel.kt │ │ │ ├── ViewUtils.kt │ │ │ └── StringUtils.kt │ │ ├── common │ │ │ └── viewmodel │ │ │ │ ├── BaseViewModel.kt │ │ │ │ └── BaseUiViewModel.kt │ │ ├── dispatcher │ │ │ ├── event │ │ │ │ ├── AppEvent.kt │ │ │ │ └── EventDispatcher.kt │ │ │ └── notifier │ │ │ │ └── SystemMessage.kt │ │ └── viewmodel │ │ │ ├── global │ │ │ ├── profile │ │ │ │ └── ProfileViewModel.kt │ │ │ └── configuration │ │ │ │ └── WindowConfigurationViewModel.kt │ │ │ ├── navigation │ │ │ └── NavigationExampleViewModel.kt │ │ │ ├── chartexample │ │ │ └── ChartExampleViewModel.kt │ │ │ ├── home │ │ │ └── HomeViewModel.kt │ │ │ └── pdfexample │ │ │ └── PdfExampleViewModel.kt │ │ └── data │ │ ├── network │ │ ├── error │ │ │ └── CustomServerException.kt │ │ ├── model │ │ │ ├── RandomImageLinksModel.kt │ │ │ └── WebSocketMessageModel.kt │ │ ├── AppApi.kt │ │ └── interceptor │ │ │ ├── HttpErrorResponseInterceptor.kt │ │ │ ├── HttpBaseUrlInterceptor.kt │ │ │ └── HttpHeaderInterceptor.kt │ │ ├── shell │ │ └── ShellManager.kt │ │ ├── utils │ │ ├── HttpCode.kt │ │ ├── json │ │ │ ├── JsonUtils.kt │ │ │ └── UUIDSerializable.kt │ │ └── DataConstants.kt │ │ ├── gateway │ │ ├── FileGatewayImpl.kt │ │ ├── HTTPExampleGatewayImpl.kt │ │ ├── ChartExampleGatewayImpl.kt │ │ ├── PdfExampleGatewayImpl.kt │ │ ├── WebSocketExampleGatewayImpl.kt │ │ ├── DBExampleGatewayImpl.kt │ │ ├── ComObjectExampleGatewayImpl.kt │ │ └── AppGatewayImpl.kt │ │ ├── storage │ │ ├── entity │ │ │ ├── DBExampleDataEntity.kt │ │ │ ├── LogMessageEntity.kt │ │ │ └── LogNetworkMessageEntity.kt │ │ ├── dao │ │ │ ├── DBExampleDataDao.kt │ │ │ ├── LogMessageDao.kt │ │ │ └── LogNetworkMessageDao.kt │ │ └── MainDB.kt │ │ └── preference │ │ └── PreferencesManager.kt │ └── desktopMain │ └── kotlin │ └── ru │ └── rznnike │ └── demokmp │ ├── app │ ├── model │ │ └── common │ │ │ └── HotkeyDescription.kt │ ├── di │ │ ├── NetworkModule.desktop.kt │ │ ├── DatabaseModule.desktop.kt │ │ └── PreferenceModule.desktop.kt │ ├── ui │ │ ├── screen │ │ │ ├── home │ │ │ │ └── HomeFlow.kt │ │ │ ├── logger │ │ │ │ └── LoggerFlow.kt │ │ │ ├── splash │ │ │ │ └── SplashFlow.kt │ │ │ ├── customui │ │ │ │ └── CustomUIFlow.kt │ │ │ ├── settings │ │ │ │ └── SettingsFlow.kt │ │ │ ├── dbexample │ │ │ │ └── DBExampleFlow.kt │ │ │ ├── pdfexample │ │ │ │ └── PdfExampleFlow.kt │ │ │ ├── httpexample │ │ │ │ └── HTTPExampleFlow.kt │ │ │ ├── chartexample │ │ │ │ └── ChartExampleFlow.kt │ │ │ ├── wsexample │ │ │ │ └── WebSocketsExampleFlow.kt │ │ │ ├── navigation │ │ │ │ └── NavigationExampleFlow.kt │ │ │ └── comobjectexample │ │ │ │ └── ComObjectExampleFlow.kt │ │ ├── viewmodel │ │ │ └── global │ │ │ │ └── hotkeys │ │ │ │ └── HotKeysViewModel.kt │ │ └── view │ │ │ └── DropdownSelectorItem.kt │ ├── utils │ │ ├── ClipboardUtils.desktop.kt │ │ ├── ViewUtils.desktop.kt │ │ ├── PlatformUtils.desktop.kt │ │ ├── FileDialogUtils.kt │ │ └── WindowViewModel.desktop.kt │ └── navigation │ │ ├── DesktopNavigationScreen.kt │ │ └── NavigationUtils.desktop.kt │ ├── domain │ └── log │ │ └── Logger.desktop.kt │ └── data │ └── shell │ └── PowerShellWrapper.kt ├── gradle.properties ├── .gitignore ├── settings.gradle.kts ├── LICENSE └── .run ├── archive [release].run.xml ├── archive [staging].run.xml ├── run [debug].run.xml ├── run [release].run.xml ├── run [staging].run.xml └── run [debug +args].run.xml /demokmp.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/demokmp.jks -------------------------------------------------------------------------------- /readmeFiles/KMP_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/KMP_logo.png -------------------------------------------------------------------------------- /runScripts/linux/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/runScripts/linux/icon.png -------------------------------------------------------------------------------- /runScripts/windows/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/runScripts/windows/icon.ico -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Demo KMP app 3 | -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_1.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_2.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_3.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_4.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_5.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_6.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_7.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_8.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_9.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_1.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_2.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_3.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_4.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_5.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_6.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_7.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_8.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_9.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Демо приложение KMP 3 | -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_10.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_11.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_12.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/pc/screenshot_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/pc/screenshot_13.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_10.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_11.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_12.png -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/pc/screenshot_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/pc/screenshot_13.png -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/android/screenshot_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/android/screenshot_1.jpg -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/android/screenshot_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/android/screenshot_2.jpg -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/android/screenshot_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/android/screenshot_3.jpg -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/android/screenshot_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/android/screenshot_4.jpg -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/android/screenshot_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/android/screenshot_5.jpg -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/android/screenshot_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/android/screenshot_6.jpg -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/android/screenshot_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/android/screenshot_7.jpg -------------------------------------------------------------------------------- /readmeFiles/en/screenshots/android/screenshot_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/en/screenshots/android/screenshot_8.jpg -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/android/screenshot_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/android/screenshot_1.jpg -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/android/screenshot_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/android/screenshot_2.jpg -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/android/screenshot_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/android/screenshot_3.jpg -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/android/screenshot_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/android/screenshot_4.jpg -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/android/screenshot_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/android/screenshot_5.jpg -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/android/screenshot_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/android/screenshot_6.jpg -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/android/screenshot_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/android/screenshot_7.jpg -------------------------------------------------------------------------------- /readmeFiles/ru/screenshots/android/screenshot_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/readmeFiles/ru/screenshots/android/screenshot_8.jpg -------------------------------------------------------------------------------- /runScripts/windows/run.vbs: -------------------------------------------------------------------------------- 1 | CreateObject("WScript.Shell").Run "powershell -NoProfile -WindowStyle Hidden -ExecutionPolicy ByPass -File launcher_script.ps1", 0 -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/consolas.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/consolas.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ubuntu_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/ubuntu_bold.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/icon_linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/drawable/icon_linux.png -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ubuntu_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/ubuntu_italic.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ubuntu_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/ubuntu_light.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ubuntu_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/ubuntu_medium.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ubuntu_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/ubuntu_regular.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/icon_windows.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/drawable/icon_windows.ico -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ubuntu_bold_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/ubuntu_bold_italic.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ubuntu_light_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/ubuntu_light_italic.ttf -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/utils/AppConstants.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | object AppConstants { 4 | const val APP_EXIT_DURATION_MS = 2500L 5 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/font/ubuntu_medium_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RznNike/DemoKMP/HEAD/composeApp/src/commonMain/composeResources/font/ubuntu_medium_italic.ttf -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/LogType.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | enum class LogType { 4 | DEFAULT, 5 | NETWORK, 6 | SESSION_START 7 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import org.koin.core.module.Module 4 | 5 | internal expect fun getDatabaseModule(): Module -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/di/PreferenceModule.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import org.koin.core.module.Module 4 | 5 | internal expect fun getPreferenceModule(): Module -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/chart/ChartPoint.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.chart 2 | 3 | data class ChartPoint( 4 | val x: Double, 5 | val y: Double 6 | ) 7 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/ui/dialog/common/AlertDialogType.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.dialog.common 2 | 3 | enum class AlertDialogType { 4 | HORIZONTAL, 5 | VERTICAL 6 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/db/DBExampleData.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.db 2 | 3 | data class DBExampleData( 4 | val id: Long = 0, 5 | val name: String 6 | ) 7 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/navigation/NavigationFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.navigation 2 | 3 | abstract class NavigationFlow { 4 | abstract val screens: MutableList 5 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/model/common/HotkeyDescription.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.model.common 2 | 3 | data class HotkeyDescription( 4 | val hotkey: String, 5 | val description: String 6 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/HTTPExampleGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | interface HTTPExampleGateway { 4 | suspend fun getRandomImageLinks(count: Int): List 5 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/FileGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | import java.io.File 4 | 5 | interface FileGateway { 6 | suspend fun copyFile(original: File, copy: File) 7 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/NetworkRequestState.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | enum class NetworkRequestState { 4 | SENT, 5 | SUCCESS, 6 | ERROR, 7 | TIMEOUT, 8 | CANCELLED 9 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/websocket/WebSocketMessage.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.websocket 2 | 3 | data class WebSocketMessage( 4 | val text: String, 5 | val isIncoming: Boolean 6 | ) 7 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/xml/locales_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/LogData.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | data class LogData( 6 | val log: List, 7 | val eventsFlow: Flow 8 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/AppGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | interface AppGateway { 4 | suspend fun checkIfAppIsAlreadyRunning(): Boolean 5 | 6 | suspend fun closeAppSingleInstanceSocket() 7 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/LogLevel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | enum class LogLevel( 4 | val label: String 5 | ) { 6 | DEBUG("D"), 7 | INFO("I"), 8 | WARNING("W"), 9 | ERROR("E") 10 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/print/PrintSettings.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.print 2 | 3 | data class PrintSettings( 4 | val printerName: String = "", 5 | val twoSidedPrint: TwoSidedPrint = TwoSidedPrint.default 6 | ) 7 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/websocket/WebSocketConnectionState.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.websocket 2 | 3 | enum class WebSocketConnectionState { 4 | CONNECTING, 5 | CONNECTED, 6 | DISCONNECTED, 7 | CLOSED 8 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/di/NetworkModule.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import okhttp3.Interceptor 4 | import org.koin.core.scope.Scope 5 | 6 | actual fun Scope.getPlatformHttpInterceptors(): List = emptyList() -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/ui/dialog/common/AlertDialogAction.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.dialog.common 2 | 3 | data class AlertDialogAction( 4 | val text: String, 5 | val accent: Boolean = true, 6 | val callback: () -> Unit 7 | ) 8 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/NetworkLogData.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | data class NetworkLogData( 6 | val log: List, 7 | val eventsFlow: Flow 8 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/utils/ClipboardUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.ui.platform.Clipboard 4 | import kotlinx.coroutines.CoroutineScope 5 | 6 | expect fun Clipboard.setText(text: String, scope: CoroutineScope) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/common/viewmodel/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.common.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | import org.koin.core.component.KoinComponent 5 | 6 | abstract class BaseViewModel : ViewModel(), KoinComponent -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/ChartExampleGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | import ru.rznnike.demokmp.domain.model.chart.ChartPoint 4 | 5 | interface ChartExampleGateway { 6 | suspend fun getSampleData(): List 7 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/PdfExampleGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | import java.io.File 4 | 5 | interface PdfExampleGateway { 6 | suspend fun getSamplePdf(): File 7 | 8 | suspend fun savePdfToFile(tempPdfFile: File, saveFile: File) 9 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/network/error/CustomServerException.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.network.error 2 | 3 | import java.io.IOException 4 | 5 | class CustomServerException( 6 | val httpCode: Int 7 | ) : IOException() { 8 | val isServerSide: Boolean = httpCode >= 500 9 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/LogEvent.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | sealed class LogEvent { 4 | data object NewMessage : LogEvent() 5 | 6 | data class NewNetworkMessage(val message: NetworkLogMessage) : LogEvent() 7 | 8 | data object Cleanup : LogEvent() 9 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/di/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | val appComponent = listOf( 4 | appModule, 5 | getPreferenceModule(), 6 | gatewayModule, 7 | interactorModule, 8 | viewModelModule, 9 | networkModule, 10 | getDatabaseModule() 11 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/di/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import org.koin.dsl.module 4 | import ru.rznnike.demokmp.app.viewmodel.global.configuration.AppConfigurationViewModel 5 | 6 | internal val viewModelModule = module { 7 | single { AppConfigurationViewModel() } 8 | } -------------------------------------------------------------------------------- /runScripts/linux/create_desktop_shortcut.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | name="Demo KMP" 4 | desktopFolder=$(xdg-user-dir DESKTOP) 5 | shortcut="$desktopFolder/$name.desktop" 6 | 7 | echo "[Desktop Entry] 8 | Type=Application 9 | Name=$name 10 | Icon=$PWD/icon.png 11 | Exec=\"$PWD/run.sh\" 12 | Path=$PWD" > "$shortcut" 13 | chmod +x "$shortcut" 14 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/network/model/RandomImageLinksModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.network.model 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class RandomImageLinksModel( 8 | @SerialName("message") 9 | val links: List 10 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/common/CoroutineScopeProvider.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.common 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | 5 | interface CoroutineScopeProvider { 6 | val ui: CoroutineScope 7 | val default: CoroutineScope 8 | val io: CoroutineScope 9 | val unconfined: CoroutineScope 10 | } 11 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_minus.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/home/HomeFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.home 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class HomeFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(HomeScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/common/DispatcherProvider.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.common 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | 5 | interface DispatcherProvider { 6 | val ui: CoroutineDispatcher 7 | val default: CoroutineDispatcher 8 | val io: CoroutineDispatcher 9 | val unconfined: CoroutineDispatcher 10 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/home/HomeFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.home 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class HomeFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(HomeScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #006600 4 | #006600 5 | #006600 6 | #000000 7 | #01000000 8 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/utils/PlatformUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import org.koin.core.component.KoinComponent 4 | 5 | expect val platformName: String 6 | 7 | expect fun getMacAddress(): String? 8 | 9 | expect fun KoinComponent.openLink(link: String) 10 | 11 | expect fun initCOMLibrary() 12 | 13 | expect fun destroyCOMLibrary() -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/splash/SplashFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.splash 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class SplashFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(SplashScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/logger/LoggerFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.logger 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class LoggerFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(LoggerScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/splash/SplashFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.splash 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class SplashFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(SplashScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_plus.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/customui/CustomUIFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.customui 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class CustomUIFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(CustomUIScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/settings/SettingsFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.settings 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class SettingsFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(SettingsScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/navigation/NavigationScreen.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | abstract class NavigationScreen { 8 | @Composable 9 | open fun Content() = Layout() 10 | 11 | @Composable 12 | abstract fun Layout() 13 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/customui/CustomUIFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.customui 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class CustomUIFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(CustomUIScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/settings/SettingsFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.settings 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class SettingsFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(SettingsScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/dbexample/DBExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.dbexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class DBExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(DBExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-anydpi/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/websocket/WebSocketSessionData.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.websocket 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.StateFlow 5 | 6 | data class WebSocketSessionData( 7 | val url: String, 8 | val messages: Flow, 9 | val connectionState: StateFlow 10 | ) -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/dbexample/DBExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.dbexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class DBExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(DBExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/pdfexample/PdfExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.pdfexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class PdfExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(PdfExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/httpexample/HTTPExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.httpexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class HTTPExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(HTTPExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/ComObjectExampleGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | interface ComObjectExampleGateway { 4 | suspend fun initShellWrapper() 5 | 6 | suspend fun destroyShellWrapper() 7 | 8 | suspend fun getPCData(): String 9 | 10 | suspend fun openFolderOrFile(path: String) 11 | 12 | suspend fun minimizeAllWindows() 13 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/httpexample/HTTPExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.httpexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class HTTPExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(HTTPExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/chartexample/ChartExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.chartexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class ChartExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(ChartExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/chartexample/ChartExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.chartexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class ChartExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(ChartExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/wsexample/WebSocketsExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.wsexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class WebSocketsExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(WebSocketsExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_expand.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/shell/ShellManager.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.shell 2 | 3 | interface ShellManager { 4 | suspend fun initWrapper() 5 | 6 | suspend fun destroyWrapper() 7 | 8 | suspend fun getPCData(): String 9 | 10 | suspend fun openFolderOrFile(path: String) 11 | 12 | suspend fun minimizeAllWindows() 13 | } 14 | 15 | expect fun getShellManager(): ShellManager -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/wsexample/WebSocketsExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.wsexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class WebSocketsExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(WebSocketsExampleScreen()) 8 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_arrow_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/ui/screen/navigation/NavigationExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.navigation 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class NavigationExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(NavigationExampleScreen(screenNumber = 1)) 8 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/navigation/NavigationExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.navigation 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | 6 | class NavigationExampleFlow : NavigationFlow() { 7 | override val screens: MutableList = mutableListOf(NavigationExampleScreen(screenNumber = 1)) 8 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/dispatcher/event/AppEvent.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.dispatcher.event 2 | 3 | sealed class AppEvent { 4 | data object RestartRequested : AppEvent() 5 | 6 | data object ActivityRestartRequested : AppEvent() 7 | 8 | data class BottomStatusBarRequested( 9 | val show: Boolean 10 | ) : AppEvent() 11 | 12 | data object LoggerWindowRequested : AppEvent() 13 | } 14 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #009900 4 | #009900 5 | #009900 6 | #EDEFF0 7 | #01FFFFFF 8 | 9 | #EDEFF0 10 | -------------------------------------------------------------------------------- /runScripts/windows/create_desktop_shortcut.vbs: -------------------------------------------------------------------------------- 1 | Set shell = CreateObject("WScript.Shell") 2 | desktopPath = shell.SpecialFolders("Desktop") 3 | appPath = CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) 4 | Set link = shell.CreateShortcut(desktopPath & "\Demo KMP.lnk") 5 | link.IconLocation = appPath & "\icon.ico" 6 | link.TargetPath = appPath & "\run.vbs" 7 | link.WindowStyle = 3 8 | link.WorkingDirectory = appPath 9 | link.Save -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_checkbox_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/utils/ChartUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import com.patrykandpatrick.vico.multiplatform.common.vicoTheme 6 | 7 | @Composable 8 | fun getCustomVicoTheme() = vicoTheme.copy( 9 | lineColor = MaterialTheme.colorScheme.outline, 10 | textColor = MaterialTheme.colorScheme.onBackground 11 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/print/TwoSidedPrint.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.print 2 | 3 | enum class TwoSidedPrint( 4 | val id: Int 5 | ) { 6 | DISABLED(id = 0), 7 | TWO_SIDED_LONG_EDGE(id = 1), 8 | TWO_SIDED_SHORT_EDGE(id = 2); 9 | 10 | companion object { 11 | val default = DISABLED 12 | 13 | operator fun get(id: Int?) = entries.find { it.id == id } ?: default 14 | } 15 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/network/AppApi.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.network 2 | 3 | import de.jensklingenberg.ktorfit.http.GET 4 | import de.jensklingenberg.ktorfit.http.Path 5 | import ru.rznnike.demokmp.data.network.model.RandomImageLinksModel 6 | 7 | interface AppApi { 8 | @GET("api/breeds/image/random/{count}") 9 | suspend fun getRandomImages( 10 | @Path("count") count: Int 11 | ): RandomImageLinksModel 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/common/Theme.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.common 2 | 3 | enum class Theme( 4 | val id: Int 5 | ) { 6 | AUTO(id = 0), 7 | LIGHT(id = 1), 8 | LIGHT_CONTRAST(id = 2), 9 | DARK(id = 3), 10 | DARK_CONTRAST(id = 4); 11 | 12 | companion object { 13 | val default = AUTO 14 | 15 | operator fun get(id: Int) = entries.find { it.id == id } ?: default 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | # Gradle 4 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" 5 | 6 | # Android 7 | android.nonTransitiveRClass=true 8 | android.useAndroidX=true 9 | 10 | # BuildKonfig custom field for dynamic build type resolution (Android) 11 | buildkonfig.flavor= 12 | 13 | # Change to your own .jks key passwords (Android) 14 | PROJECT_KEY_PASSWORD=demopass 15 | PROJECT_KEYSTORE_PASSWORD=demopass -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_checkbox_on.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/WebSocketExampleGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | import ru.rznnike.demokmp.domain.model.websocket.WebSocketMessage 4 | import ru.rznnike.demokmp.domain.model.websocket.WebSocketSessionData 5 | 6 | interface WebSocketExampleGateway { 7 | suspend fun getSession(): WebSocketSessionData 8 | 9 | suspend fun closeSession() 10 | 11 | suspend fun sendMessage(message: WebSocketMessage) 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/LogMessage.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class LogMessage( 7 | val id: Long = 0, 8 | val type: LogType, 9 | val level: LogLevel, 10 | val timestamp: Long, 11 | val tag: String, 12 | val message: String, 13 | val isCurrentSession: Boolean = true 14 | ) { 15 | fun getFormattedMessage() = message.trim().replace("\t", " ") 16 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/DBExampleGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | import ru.rznnike.demokmp.domain.model.db.DBExampleData 4 | 5 | interface DBExampleGateway { 6 | suspend fun get(id: Long): DBExampleData? 7 | 8 | suspend fun getAll(): List 9 | 10 | suspend fun add(data: DBExampleData) 11 | 12 | suspend fun delete(data: DBExampleData) 13 | 14 | suspend fun deleteAll() 15 | 16 | suspend fun closeDB() 17 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/screen/comobjectexample/ComObjectExampleFlow.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.screen.comobjectexample 2 | 3 | import ru.rznnike.demokmp.app.navigation.NavigationFlow 4 | import ru.rznnike.demokmp.app.navigation.NavigationScreen 5 | import ru.rznnike.demokmp.app.ui.screen.navigation.NavigationExampleScreen 6 | 7 | class ComObjectExampleFlow : NavigationFlow() { 8 | override val screens: MutableList = mutableListOf(ComObjectExampleScreen()) 9 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/utils/GlobalConstants.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.utils 2 | 3 | object GlobalConstants { 4 | const val DATE_PATTERN_SIMPLE = "dd.MM.yyyy" 5 | const val DATE_PATTERN_SIMPLE_WITH_TIME = "dd.MM.yyyy HH:mm" 6 | const val DATE_PATTERN_TEXT_MONTH_YEAR = "LLLL yyyy" 7 | const val DATE_PATTERN_TIME_MS = "HH:mm:ss.SSS" 8 | const val DATE_PATTERN_FILE_NAME_DAY = "yyyy.MM.dd" 9 | const val DATE_PATTERN_FILE_NAME_MS = "yyyy.MM.dd_HH.mm.ss.SSS" 10 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/utils/WindowViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewmodel.CreationExtras 6 | 7 | @Composable 8 | expect inline fun windowViewModel( 9 | key: String? = null, 10 | noinline initializer: CreationExtras.() -> VM 11 | ): VM 12 | 13 | @Composable 14 | expect inline fun windowViewModel(): VM -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .kotlin 3 | .gradle 4 | **/build/ 5 | xcuserdata 6 | !src/**/build/ 7 | local.properties 8 | .idea 9 | .DS_Store 10 | captures 11 | .externalNativeBuild 12 | .cxx 13 | *.xcodeproj/* 14 | !*.xcodeproj/project.pbxproj 15 | !*.xcodeproj/xcshareddata/ 16 | !*.xcodeproj/project.xcworkspace/ 17 | !*.xcworkspace/contents.xcworkspacedata 18 | **/xcshareddata/WorkspaceSettings.xcsettings 19 | 20 | composeApp/workDirectory/ 21 | distributableOutput/ 22 | distributableArchive/ 23 | 24 | composeApp/release/ 25 | composeApp/staging/ 26 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/data/shell/ShellManager.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.shell 2 | 3 | class ShellManagerImpl : ShellManager { 4 | override suspend fun initWrapper() = Unit 5 | 6 | override suspend fun destroyWrapper() = Unit 7 | 8 | override suspend fun getPCData() = "" 9 | 10 | override suspend fun openFolderOrFile(path: String) = Unit 11 | 12 | override suspend fun minimizeAllWindows() = Unit 13 | } 14 | 15 | actual fun getShellManager(): ShellManager = ShellManagerImpl() -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/common/UiScale.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.common 2 | 3 | enum class UiScale( 4 | val value: Int 5 | ) { 6 | S70(value = 70), 7 | S80(value = 80), 8 | S90(value = 90), 9 | S100(value = 100), 10 | S110(value = 110), 11 | S120(value = 120), 12 | S130(value = 130); 13 | 14 | companion object { 15 | val default = S100 16 | 17 | operator fun get(value: Int) = entries.find { it.value == value } ?: default 18 | } 19 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_save.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/utils/ClipboardUtils.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import android.content.ClipData 4 | import androidx.compose.ui.platform.ClipEntry 5 | import androidx.compose.ui.platform.Clipboard 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.launch 8 | 9 | actual fun Clipboard.setText( 10 | text: String, 11 | scope: CoroutineScope 12 | ) { 13 | scope.launch { 14 | setClipEntry(ClipEntry(ClipData.newPlainText("", text))) 15 | } 16 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/utils/ClipboardUtils.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.ui.platform.ClipEntry 4 | import androidx.compose.ui.platform.Clipboard 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.launch 7 | import java.awt.datatransfer.StringSelection 8 | 9 | actual fun Clipboard.setText( 10 | text: String, 11 | scope: CoroutineScope 12 | ) { 13 | scope.launch { 14 | setClipEntry(ClipEntry(StringSelection(text))) 15 | } 16 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/ClearLogUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | 7 | class ClearLogUseCase( 8 | private val logGateway: LogGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = logGateway.clearLog() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/dispatcher/notifier/SystemMessage.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.dispatcher.notifier 2 | 3 | import org.jetbrains.compose.resources.StringResource 4 | 5 | data class SystemMessage( 6 | val textRes: StringResource? = null, 7 | var text: String? = null, 8 | val actionTextRes: StringResource? = null, 9 | var actionText: String? = null, 10 | val actionCallback: (() -> Unit)? = null, 11 | val type: Type 12 | ) { 13 | enum class Type { 14 | ALERT, 15 | BAR 16 | } 17 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_zoom.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/ui/theme/Shapes.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val extraSmallCorners = 8.dp 8 | val appShapes = Shapes( 9 | extraSmall = RoundedCornerShape(extraSmallCorners), 10 | small = RoundedCornerShape(8.dp), 11 | medium = RoundedCornerShape(8.dp), 12 | large = RoundedCornerShape(16.dp), 13 | extraLarge = RoundedCornerShape(32.dp) 14 | ) 15 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/ClearNetworkLogUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | 7 | class ClearNetworkLogUseCase( 8 | private val logGateway: LogGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = logGateway.clearNetworkLog() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/utils/HttpCode.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.utils 2 | 3 | enum class HttpCode(val code: Int) { 4 | OK(200), 5 | NO_CONTENT(204), 6 | BAD_REQUEST(400), 7 | UNAUTHORIZED(401), 8 | FORBIDDEN(403), 9 | NOT_FOUND(404), 10 | CONFLICT(409), 11 | INTERNAL_SERVER_ERROR(500), 12 | BAD_GATEWAY(502), 13 | SERVICE_UNAVAILABLE(503), 14 | CUSTOM_NO_CONTENT(2040); 15 | 16 | companion object { 17 | operator fun get(code: Int?) = entries.find { it.code == code } ?: OK 18 | } 19 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/viewmodel/global/profile/ProfileViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.viewmodel.global.profile 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import ru.rznnike.demokmp.app.common.viewmodel.BaseViewModel 7 | 8 | class ProfileViewModel : BaseViewModel() { 9 | var nameInput by mutableStateOf("John Wick") 10 | private set 11 | 12 | fun onNameInput(newValue: String) { 13 | nameInput = newValue 14 | } 15 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/dbexample/CloseDBUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.dbexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.DBExampleGateway 6 | 7 | class CloseDBUseCase( 8 | private val dbExampleGateway: DBExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = dbExampleGateway.closeDB() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/navigation/navtype/NavTypes.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.navigation.navtype 2 | 3 | import ru.rznnike.demokmp.domain.log.NetworkLogMessage 4 | import kotlin.reflect.typeOf 5 | 6 | val networkLogMessageNavType = typeOf() to jsonNavTypeOf() 7 | //val someNullableNavType = typeOf() to jsonNavTypeOf(isNullableAllowed = true) 8 | //val someEnumNavType = typeOf() to enumNavTypeOf() 9 | //val someListNavType = typeOf>() to jsonNavTypeOf>() -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/utils/ViewUtils.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.interaction.MutableInteractionSource 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.Modifier 8 | 9 | @Composable 10 | actual fun Modifier.onClick(action: () -> Unit): Modifier = clickable( 11 | interactionSource = remember { MutableInteractionSource() }, 12 | indication = null, 13 | onClick = action 14 | ) -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/domain/log/Logger.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | import android.util.Log 4 | 5 | actual fun formatLogMessage(message: LogMessage) = message.message 6 | 7 | actual fun printLog(message: LogMessage) { 8 | val tag = message.tag.ifBlank { null } 9 | when (message.level) { 10 | LogLevel.DEBUG -> Log.d(tag, message.message) 11 | LogLevel.INFO -> Log.i(tag, message.message) 12 | LogLevel.WARNING -> Log.w(tag, message.message) 13 | LogLevel.ERROR -> Log.e(tag, message.message) 14 | } 15 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/GetLogUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.LogData 7 | 8 | class GetLogUseCase( 9 | private val logGateway: LogGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase(dispatcherProvider) { 12 | override suspend fun execute() = logGateway.getLog() 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/app/CheckIfAppIsAlreadyRunningUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.app 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.AppGateway 6 | 7 | class CheckIfAppIsAlreadyRunningUseCase( 8 | private val appGateway: AppGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = appGateway.checkIfAppIsAlreadyRunning() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_calendar.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/common/interactor/FlowUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.common.interactor 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.flowOn 5 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 6 | 7 | abstract class FlowUseCase(private val dispatcherProvider: DispatcherProvider) { 8 | suspend operator fun invoke(): Flow { 9 | return execute().flowOn(dispatcherProvider.default) 10 | } 11 | 12 | @Throws(RuntimeException::class) 13 | protected abstract suspend fun execute(): Flow 14 | } 15 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/app/CloseAppSingleInstanceSocketUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.app 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.AppGateway 6 | 7 | class CloseAppSingleInstanceSocketUseCase( 8 | private val appGateway: AppGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = appGateway.closeAppSingleInstanceSocket() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/dbexample/DeleteAllDBExampleDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.dbexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.DBExampleGateway 6 | 7 | class DeleteAllDBExampleDataUseCase( 8 | private val dbExampleGateway: DBExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = dbExampleGateway.deleteAll() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/GetTestCounterUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | 7 | class GetTestCounterUseCase( 8 | private val preferencesGateway: PreferencesGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = preferencesGateway.getTestCounter() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/comobjectexample/GetPCDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.comobjectexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.ComObjectExampleGateway 6 | 7 | class GetPCDataUseCase( 8 | private val comObjectExampleGateway: ComObjectExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = comObjectExampleGateway.getPCData() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/GetNetworkLogUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.NetworkLogData 7 | 8 | class GetNetworkLogUseCase( 9 | private val logGateway: LogGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase(dispatcherProvider) { 12 | override suspend fun execute() = logGateway.getNetworkLog() 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/pdfexample/GetSamplePdfUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.pdfexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.PdfExampleGateway 6 | import java.io.File 7 | 8 | class GetSamplePdfUseCase( 9 | private val pdfExampleGateway: PdfExampleGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase(dispatcherProvider) { 12 | override suspend fun execute() = pdfExampleGateway.getSamplePdf() 13 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/di/NetworkModule.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import com.chuckerteam.chucker.api.ChuckerInterceptor 4 | import okhttp3.Interceptor 5 | import org.koin.android.ext.koin.androidApplication 6 | import org.koin.core.scope.Scope 7 | 8 | private const val MAX_CONTENT_LENGTH = 500_000L // default is 250_000L 9 | 10 | actual fun Scope.getPlatformHttpInterceptors(): List = listOf( 11 | ChuckerInterceptor.Builder(androidApplication()) 12 | .maxContentLength(MAX_CONTENT_LENGTH) 13 | .alwaysReadResponseBody(true) 14 | .build() 15 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/wsexample/CloseAppWSSessionUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.wsexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.WebSocketExampleGateway 6 | 7 | class CloseAppWSSessionUseCase( 8 | private val webSocketExampleGateway: WebSocketExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = webSocketExampleGateway.closeSession() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/utils/CollectionUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.utils 2 | 3 | fun List.smartFilter( 4 | query: String, 5 | stringRetrievers: List<(T) -> String> 6 | ): List { 7 | val preparedQuery = query.prepareTextForSearch() 8 | 9 | return filter { item -> 10 | stringRetrievers.any { retriever -> 11 | retriever(item) 12 | .prepareTextForSearch() 13 | .contains(preparedQuery) 14 | } 15 | } 16 | } 17 | 18 | fun String.prepareTextForSearch() = trim() 19 | .lowercase() 20 | .replace("ё", "е") -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/SaveLogToFileUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import java.io.File 7 | 8 | class SaveLogToFileUseCase( 9 | private val logGateway: LogGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: File) = logGateway.saveLogToFile(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_print.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/gateway/FileGatewayImpl.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.gateway 2 | 3 | import kotlinx.coroutines.withContext 4 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 5 | import ru.rznnike.demokmp.domain.gateway.FileGateway 6 | import java.io.File 7 | 8 | class FileGatewayImpl( 9 | private val dispatcherProvider: DispatcherProvider 10 | ) : FileGateway { 11 | override suspend fun copyFile(original: File, copy: File): Unit = withContext(dispatcherProvider.io) { 12 | original.copyTo( 13 | target = copy, 14 | overwrite = true 15 | ) 16 | } 17 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/storage/entity/DBExampleDataEntity.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.storage.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import ru.rznnike.demokmp.domain.model.db.DBExampleData 6 | 7 | @Entity 8 | data class DBExampleDataEntity( 9 | @PrimaryKey(autoGenerate = true) 10 | val id: Long = 0, 11 | val name: String 12 | ) 13 | 14 | fun DBExampleDataEntity.toDBExampleData() = DBExampleData( 15 | id = id, 16 | name = name 17 | ) 18 | 19 | fun DBExampleData.toDBExampleDataEntity() = DBExampleDataEntity( 20 | id = id, 21 | name = name 22 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/comobjectexample/InitShellWrapperUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.comobjectexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.ComObjectExampleGateway 6 | 7 | class InitShellWrapperUseCase( 8 | private val comObjectExampleGateway: ComObjectExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = comObjectExampleGateway.initShellWrapper() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/network/model/WebSocketMessageModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.network.model 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | import ru.rznnike.demokmp.domain.model.websocket.WebSocketMessage 6 | 7 | @Serializable 8 | data class WebSocketMessageModel( 9 | @SerialName("text") 10 | val text: String 11 | ) 12 | 13 | fun WebSocketMessage.toWebSocketMessageModel() = WebSocketMessageModel( 14 | text = text 15 | ) 16 | 17 | fun WebSocketMessageModel.toWebSocketMessage() = WebSocketMessage( 18 | text = text, 19 | isIncoming = true 20 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/comobjectexample/MinimizeAllWindowsUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.comobjectexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.ComObjectExampleGateway 6 | 7 | class MinimizeAllWindowsUseCase( 8 | private val comObjectExampleGateway: ComObjectExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = comObjectExampleGateway.minimizeAllWindows() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/GetThemeUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | import ru.rznnike.demokmp.domain.model.common.Theme 7 | 8 | class GetThemeUseCase( 9 | private val preferencesGateway: PreferencesGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase(dispatcherProvider) { 12 | override suspend fun execute() = preferencesGateway.getTheme() 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/comobjectexample/DestroyShellWrapperUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.comobjectexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.ComObjectExampleGateway 6 | 7 | class DestroyShellWrapperUseCase( 8 | private val comObjectExampleGateway: ComObjectExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCase(dispatcherProvider) { 11 | override suspend fun execute() = comObjectExampleGateway.destroyShellWrapper() 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/GetUiScaleUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | import ru.rznnike.demokmp.domain.model.common.UiScale 7 | 8 | class GetUiScaleUseCase( 9 | private val preferencesGateway: PreferencesGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase(dispatcherProvider) { 12 | override suspend fun execute() = preferencesGateway.getUiScale() 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/SetTestCounterUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | 7 | class SetTestCounterUseCase( 8 | private val preferencesGateway: PreferencesGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCaseWithParams(dispatcherProvider) { 11 | override suspend fun execute(parameters: Int) = preferencesGateway.setTestCounter(parameters) 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_logger.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/GetNewLogUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.LogMessage 7 | 8 | class GetNewLogUseCase( 9 | private val logGateway: LogGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams>(dispatcherProvider) { 12 | override suspend fun execute(parameters: Long) = logGateway.getNewLog(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/GetLanguageUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | import ru.rznnike.demokmp.domain.model.common.Language 7 | 8 | class GetLanguageUseCase( 9 | private val preferencesGateway: PreferencesGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase(dispatcherProvider) { 12 | override suspend fun execute() = preferencesGateway.getLanguage() 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/common/interactor/FlowUseCaseWithParams.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.common.interactor 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.flowOn 5 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 6 | 7 | abstract class FlowUseCaseWithParams(private val dispatcherProvider: DispatcherProvider) { 8 | suspend operator fun invoke(parameters: P): Flow { 9 | return execute(parameters).flowOn(dispatcherProvider.default) 10 | } 11 | 12 | @Throws(RuntimeException::class) 13 | protected abstract suspend fun execute(parameters: P): Flow 14 | } 15 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/dbexample/GetDBExampleDataListUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.dbexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.DBExampleGateway 6 | import ru.rznnike.demokmp.domain.model.db.DBExampleData 7 | 8 | class GetDBExampleDataListUseCase( 9 | private val dbExampleGateway: DBExampleGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase>(dispatcherProvider) { 12 | override suspend fun execute() = dbExampleGateway.getAll() 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/AddLogMessageToDBUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.LogMessage 7 | 8 | class AddLogMessageToDBUseCase( 9 | private val logGateway: LogGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: LogMessage) = logGateway.addLogMessageToDB(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/GetPrintSettingsUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | import ru.rznnike.demokmp.domain.model.print.PrintSettings 7 | 8 | class GetPrintSettingsUseCase( 9 | private val preferencesGateway: PreferencesGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase(dispatcherProvider) { 12 | override suspend fun execute() = preferencesGateway.getPrintSettings() 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/utils/OperatingSystem.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.utils 2 | 3 | import ru.rznnike.demokmp.BuildKonfig 4 | 5 | enum class OperatingSystem( 6 | val tag: String 7 | ) { 8 | WINDOWS("windows"), 9 | LINUX("linux"), 10 | ANDROID("android"); 11 | 12 | companion object { 13 | val current = OperatingSystem[BuildKonfig.OS] 14 | val isWindows = current == WINDOWS 15 | val isLinux = current == LINUX 16 | val isDesktop = current != ANDROID 17 | val isAndroid = current == ANDROID 18 | 19 | operator fun get(tag: String) = entries.find { it.tag == tag } ?: WINDOWS 20 | } 21 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/utils/json/JsonUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.utils.json 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.json.Json 5 | 6 | val defaultJson = Json { 7 | isLenient = true 8 | explicitNulls = false 9 | ignoreUnknownKeys = true 10 | } 11 | 12 | @OptIn(ExperimentalSerializationApi::class) 13 | val formatterJson = Json { 14 | isLenient = true 15 | prettyPrint = true 16 | prettyPrintIndent = " " 17 | } 18 | 19 | fun String.prettifyJson() = try { 20 | formatterJson.encodeToString(formatterJson.parseToJsonElement(this)) 21 | } catch (_: Exception) { 22 | this 23 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/chartexample/GetChartSampleDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.chartexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.ChartExampleGateway 6 | import ru.rznnike.demokmp.domain.model.chart.ChartPoint 7 | 8 | class GetChartSampleDataUseCase( 9 | private val chartExampleGateway: ChartExampleGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCase>(dispatcherProvider) { 12 | override suspend fun execute() = chartExampleGateway.getSampleData() 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/comobjectexample/OpenFolderOrFileUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.comobjectexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.ComObjectExampleGateway 6 | 7 | class OpenFolderOrFileUseCase( 8 | private val comObjectExampleGateway: ComObjectExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCaseWithParams(dispatcherProvider) { 11 | override suspend fun execute(parameters: String) = comObjectExampleGateway.openFolderOrFile(parameters) 12 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/GetNewNetworkLogUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.NetworkLogMessage 7 | 8 | class GetNewNetworkLogUseCase( 9 | private val logGateway: LogGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams>(dispatcherProvider) { 12 | override suspend fun execute(parameters: Long) = logGateway.getNewNetworkLog(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/SetThemeUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | import ru.rznnike.demokmp.domain.model.common.Theme 7 | 8 | class SetThemeUseCase( 9 | private val preferencesGateway: PreferencesGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: Theme) = preferencesGateway.setTheme(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/SetUiScaleUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | import ru.rznnike.demokmp.domain.model.common.UiScale 7 | 8 | class SetUiScaleUseCase( 9 | private val preferencesGateway: PreferencesGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: UiScale) = preferencesGateway.setUiScale(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/dbexample/AddDBExampleDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.dbexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.DBExampleGateway 6 | import ru.rznnike.demokmp.domain.model.db.DBExampleData 7 | 8 | class AddDBExampleDataUseCase( 9 | private val dbExampleGateway: DBExampleGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: DBExampleData) = dbExampleGateway.add(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/SetLanguageUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | import ru.rznnike.demokmp.domain.model.common.Language 7 | 8 | class SetLanguageUseCase( 9 | private val preferencesGateway: PreferencesGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: Language) = preferencesGateway.setLanguage(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/dbexample/DeleteDBExampleDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.dbexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.DBExampleGateway 6 | import ru.rznnike.demokmp.domain.model.db.DBExampleData 7 | 8 | class DeleteDBExampleDataUseCase( 9 | private val dbExampleGateway: DBExampleGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: DBExampleData) = dbExampleGateway.delete(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/AddLogNetworkMessageToDBUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.NetworkLogMessage 7 | 8 | class AddLogNetworkMessageToDBUseCase( 9 | private val logGateway: LogGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: NetworkLogMessage) = logGateway.addLogNetworkMessageToDB(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/DeleteOldLogsUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.extension.DatabaseLoggerExtension.LogsRetentionMode 7 | 8 | class DeleteOldLogsUseCase( 9 | private val logGateway: LogGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: LogsRetentionMode) = logGateway.deleteOldLogs(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_keyboard.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/gateway/HTTPExampleGatewayImpl.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.gateway 2 | 3 | import kotlinx.coroutines.withContext 4 | import ru.rznnike.demokmp.data.network.AppApi 5 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 6 | import ru.rznnike.demokmp.domain.gateway.HTTPExampleGateway 7 | 8 | class HTTPExampleGatewayImpl( 9 | private val dispatcherProvider: DispatcherProvider, 10 | private val appApi: AppApi 11 | ) : HTTPExampleGateway { 12 | override suspend fun getRandomImageLinks( 13 | count: Int 14 | ): List = withContext(dispatcherProvider.io) { 15 | appApi.getRandomImages( 16 | count = count 17 | ).links 18 | } 19 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/common/interactor/UseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.common.interactor 2 | 3 | import kotlinx.coroutines.withContext 4 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 5 | 6 | abstract class UseCase(private val dispatcherProvider: DispatcherProvider) { 7 | suspend operator fun invoke(): UseCaseResult { 8 | return try { 9 | withContext(dispatcherProvider.default) { 10 | UseCaseResult(data = execute()) 11 | } 12 | } catch (e: Exception) { 13 | UseCaseResult(error = e) 14 | } 15 | } 16 | 17 | @Throws(RuntimeException::class) 18 | protected abstract suspend fun execute(): R 19 | } 20 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/preferences/SetPrintSettingsUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.preferences 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.PreferencesGateway 6 | import ru.rznnike.demokmp.domain.model.print.PrintSettings 7 | 8 | class SetPrintSettingsUseCase( 9 | private val preferencesGateway: PreferencesGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: PrintSettings) = preferencesGateway.setPrintSettings(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/GetLogNetworkMessageAsFlowUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.FlowUseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.NetworkLogMessage 7 | import java.util.* 8 | 9 | class GetLogNetworkMessageAsFlowUseCase( 10 | private val logGateway: LogGateway, 11 | dispatcherProvider: DispatcherProvider 12 | ) : FlowUseCaseWithParams(dispatcherProvider) { 13 | override suspend fun execute(parameters: UUID) = logGateway.getLogNetworkMessageAsFlow(parameters) 14 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/common/Language.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.common 2 | 3 | enum class Language( 4 | val shortTag: String, 5 | val fullTag: String, 6 | val localizedName: String 7 | ) { 8 | RU( 9 | shortTag = "ru", 10 | fullTag = "ru-RU", 11 | localizedName = "Русский" 12 | ), 13 | EN( 14 | shortTag = "en", 15 | fullTag = "en-US", 16 | localizedName = "English" 17 | ); 18 | 19 | companion object { 20 | val default = EN 21 | 22 | operator fun get(tag: String) = entries.find { it.fullTag == tag } ?: default 23 | 24 | fun getByShortTag(tag: String) = entries.find { it.shortTag == tag } ?: default 25 | } 26 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/wsexample/SendAppWSMessageUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.wsexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.WebSocketExampleGateway 6 | import ru.rznnike.demokmp.domain.model.websocket.WebSocketMessage 7 | 8 | class SendAppWSMessageUseCase( 9 | private val webSocketExampleGateway: WebSocketExampleGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: WebSocketMessage) = webSocketExampleGateway.sendMessage(parameters) 13 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/wsexample/GetAppWSSessionUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.wsexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCase 5 | import ru.rznnike.demokmp.domain.gateway.WebSocketExampleGateway 6 | import ru.rznnike.demokmp.domain.model.websocket.WebSocketMessage 7 | import ru.rznnike.demokmp.domain.model.websocket.WebSocketSessionData 8 | 9 | class GetAppWSSessionUseCase( 10 | private val webSocketExampleGateway: WebSocketExampleGateway, 11 | dispatcherProvider: DispatcherProvider 12 | ) : UseCase>(dispatcherProvider) { 13 | override suspend fun execute() = webSocketExampleGateway.getSession() 14 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/common/interactor/UseCaseWithParams.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.common.interactor 2 | 3 | import kotlinx.coroutines.withContext 4 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 5 | 6 | abstract class UseCaseWithParams(private val dispatcherProvider: DispatcherProvider) { 7 | suspend operator fun invoke(parameters: P): UseCaseResult { 8 | return try { 9 | withContext(dispatcherProvider.default) { 10 | UseCaseResult(data = execute(parameters)) 11 | } 12 | } catch (e: Exception) { 13 | UseCaseResult(error = e) 14 | } 15 | } 16 | 17 | @Throws(RuntimeException::class) 18 | protected abstract suspend fun execute(parameters: P): R 19 | } 20 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/domain/log/Logger.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | import ru.rznnike.demokmp.domain.utils.GlobalConstants 4 | import ru.rznnike.demokmp.domain.utils.toDateString 5 | 6 | actual fun formatLogMessage(message: LogMessage) = 7 | "%s | %s%s | %s".format( 8 | message.timestamp.toDateString(GlobalConstants.DATE_PATTERN_TIME_MS), 9 | message.level.label, 10 | if (message.tag.isNotBlank()) " | ${message.tag}" else "", 11 | message.message 12 | ) 13 | 14 | actual fun printLog(message: LogMessage) { 15 | val formattedMessage = formatLogMessage(message) 16 | if (message.level == LogLevel.ERROR) { 17 | System.err.println(formattedMessage) 18 | } else { 19 | println(formattedMessage) 20 | } 21 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/gateway/ChartExampleGatewayImpl.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.gateway 2 | 3 | import kotlinx.coroutines.withContext 4 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 5 | import ru.rznnike.demokmp.domain.gateway.ChartExampleGateway 6 | import ru.rznnike.demokmp.domain.model.chart.ChartPoint 7 | import kotlin.math.sin 8 | 9 | class ChartExampleGatewayImpl( 10 | private val dispatcherProvider: DispatcherProvider 11 | ) : ChartExampleGateway { 12 | override suspend fun getSampleData(): List = withContext(dispatcherProvider.io) { 13 | (0..300).map { index -> 14 | val x = index.toDouble() / 10 15 | ChartPoint( 16 | x = x, 17 | y = sin(x) 18 | ) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/httpexample/GetRandomImageLinksUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.httpexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.HTTPExampleGateway 6 | 7 | class GetRandomImageLinksUseCase( 8 | private val httpExampleGateway: HTTPExampleGateway, 9 | dispatcherProvider: DispatcherProvider 10 | ) : UseCaseWithParams>(dispatcherProvider) { 11 | override suspend fun execute(parameters: Parameters) = httpExampleGateway.getRandomImageLinks( 12 | count = parameters.count 13 | ) 14 | 15 | data class Parameters( 16 | val count: Int 17 | ) 18 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/di/PreferenceModule.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import android.content.SharedPreferences 4 | import androidx.preference.PreferenceManager 5 | import com.russhwolf.settings.Settings 6 | import com.russhwolf.settings.SharedPreferencesSettings 7 | import org.koin.android.ext.koin.androidApplication 8 | import org.koin.dsl.module 9 | import ru.rznnike.demokmp.data.preference.PreferencesManager 10 | 11 | internal actual fun getPreferenceModule() = module { 12 | factory { PreferenceManager.getDefaultSharedPreferences(androidApplication()) } 13 | single { createSettings(get()) } 14 | single { PreferencesManager(get()) } 15 | } 16 | 17 | private fun createSettings(preferences: SharedPreferences): Settings { 18 | return SharedPreferencesSettings(preferences) 19 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/utils/PlatformUtils.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Build 6 | import androidx.core.net.toUri 7 | import org.koin.core.component.KoinComponent 8 | import org.koin.core.component.inject 9 | 10 | actual val platformName: String = "Android ${Build.VERSION.RELEASE}" 11 | 12 | actual fun getMacAddress(): String? = null 13 | 14 | actual fun KoinComponent.openLink(link: String) { 15 | val context: Context by inject() 16 | val intent = Intent(Intent.ACTION_VIEW) 17 | .setData(link.toUri()) 18 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 19 | context.startActivity(intent) 20 | } 21 | 22 | actual fun initCOMLibrary() = Unit 23 | 24 | actual fun destroyCOMLibrary() = Unit -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/file/CopyFileUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.file 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.FileGateway 6 | import java.io.File 7 | 8 | class CopyFileUseCase( 9 | private val fileGateway: FileGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: Parameters) = fileGateway.copyFile( 13 | original = parameters.original, 14 | copy = parameters.copy 15 | ) 16 | 17 | data class Parameters( 18 | val original: File, 19 | val copy: File 20 | ) 21 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/viewmodel/navigation/NavigationExampleViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.viewmodel.navigation 2 | 3 | import kotlinx.coroutines.flow.update 4 | import ru.rznnike.demokmp.app.common.viewmodel.BaseUiViewModel 5 | 6 | class NavigationExampleViewModel( 7 | private val screenNumber: Int 8 | ) : BaseUiViewModel() { 9 | override fun provideDefaultUIState() = UiState( 10 | screenNumber = screenNumber 11 | ) 12 | 13 | fun increaseCounter() { 14 | mutableUiState.update { currentState -> 15 | currentState.copy( 16 | counter = currentState.counter + 1 17 | ) 18 | } 19 | } 20 | 21 | data class UiState( 22 | val screenNumber: Int, 23 | val counter: Int = 0 24 | ) 25 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/GetLogNetworkMessageUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.NetworkLogMessage 7 | import java.util.UUID 8 | 9 | class GetLogNetworkMessageUseCase( 10 | private val logGateway: LogGateway, 11 | dispatcherProvider: DispatcherProvider 12 | ) : UseCaseWithParams(dispatcherProvider) { 13 | override suspend fun execute(parameters: UUID) = Result( 14 | message = logGateway.getLogNetworkMessage(parameters) 15 | ) 16 | 17 | data class Result( 18 | val message: NetworkLogMessage? 19 | ) 20 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/model/common/LauncherConfig.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.model.common 2 | 3 | private const val JAVA_PATH_KEY = "java_path" 4 | private const val SINGLE_INSTANCE_PORT_KEY = "single_instance_port" 5 | 6 | private const val SINGLE_INSTANCE_PORT_DEFAULT = 62740 7 | 8 | data class LauncherConfig( 9 | val javaPath: String = "", 10 | val singleInstancePort: Int = SINGLE_INSTANCE_PORT_DEFAULT 11 | ) 12 | 13 | fun String.toLauncherConfig(): LauncherConfig { 14 | val configMap = split("\n").associate { line -> 15 | val parts = line.split("=") 16 | (parts.getOrNull(0) ?: "") to (parts.getOrNull(1) ?: "") 17 | } 18 | return LauncherConfig( 19 | javaPath = configMap[JAVA_PATH_KEY] ?: "", 20 | singleInstancePort = configMap[SINGLE_INSTANCE_PORT_KEY]?.toIntOrNull() ?: SINGLE_INSTANCE_PORT_DEFAULT 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/utils/WindowViewModel.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.activity.ComponentActivity 4 | import androidx.activity.compose.LocalActivity 5 | import androidx.compose.runtime.Composable 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewmodel.CreationExtras 8 | import androidx.lifecycle.viewmodel.compose.viewModel 9 | 10 | @Composable 11 | actual inline fun windowViewModel( 12 | key: String?, 13 | noinline initializer: CreationExtras.() -> VM 14 | ): VM = viewModel( 15 | viewModelStoreOwner = LocalActivity.current as ComponentActivity, 16 | key = key, 17 | initializer = initializer 18 | ) 19 | 20 | @Composable 21 | actual inline fun windowViewModel(): VM = viewModel( 22 | viewModelStoreOwner = LocalActivity.current as ComponentActivity 23 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/storage/dao/DBExampleDataDao.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.storage.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import ru.rznnike.demokmp.data.storage.entity.DBExampleDataEntity 8 | 9 | @Dao 10 | interface DBExampleDataDao { 11 | @Insert(onConflict = OnConflictStrategy.REPLACE) 12 | suspend fun add(item: DBExampleDataEntity) 13 | 14 | @Query("DELETE FROM DBExampleDataEntity WHERE id == :id") 15 | suspend fun delete(id: Long) 16 | 17 | @Query("DELETE FROM DBExampleDataEntity") 18 | suspend fun deleteAll() 19 | 20 | @Query("SELECT * FROM DBExampleDataEntity WHERE id == :id") 21 | suspend fun get(id: Long): DBExampleDataEntity? 22 | 23 | @Query("SELECT * FROM DBExampleDataEntity") 24 | suspend fun getAll(): List 25 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/utils/json/UUIDSerializable.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.utils.json 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.Serializable 5 | import kotlinx.serialization.descriptors.PrimitiveKind 6 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 7 | import kotlinx.serialization.encoding.Decoder 8 | import kotlinx.serialization.encoding.Encoder 9 | import java.util.* 10 | 11 | class UUIDSerializer : KSerializer { 12 | override val descriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) 13 | 14 | override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString()) 15 | 16 | override fun serialize(encoder: Encoder, value: UUID) { 17 | encoder.encodeString(value.toString()) 18 | } 19 | } 20 | 21 | typealias UUIDSerializable = @Serializable(with = UUIDSerializer::class) UUID -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | rootProject.name = "DemoKMP" 4 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 5 | 6 | pluginManagement { 7 | repositories { 8 | google { 9 | mavenContent { 10 | includeGroupAndSubgroups("androidx") 11 | includeGroupAndSubgroups("com.android") 12 | includeGroupAndSubgroups("com.google") 13 | } 14 | } 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | dependencyResolutionManagement { 21 | repositories { 22 | google { 23 | mavenContent { 24 | includeGroupAndSubgroups("androidx") 25 | includeGroupAndSubgroups("com.android") 26 | includeGroupAndSubgroups("com.google") 27 | } 28 | } 29 | mavenCentral() 30 | } 31 | } 32 | 33 | include(":composeApp") -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/PreferencesGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | import ru.rznnike.demokmp.domain.model.common.Language 4 | import ru.rznnike.demokmp.domain.model.common.Theme 5 | import ru.rznnike.demokmp.domain.model.common.UiScale 6 | import ru.rznnike.demokmp.domain.model.print.PrintSettings 7 | 8 | interface PreferencesGateway { 9 | suspend fun getTestCounter(): Int 10 | 11 | suspend fun setTestCounter(newValue: Int) 12 | 13 | suspend fun getLanguage(): Language 14 | 15 | suspend fun setLanguage(newValue: Language) 16 | 17 | suspend fun getTheme(): Theme 18 | 19 | suspend fun setTheme(newValue: Theme) 20 | 21 | suspend fun getPrintSettings(): PrintSettings 22 | 23 | suspend fun setPrintSettings(newValue: PrintSettings) 24 | 25 | suspend fun getUiScale(): UiScale 26 | 27 | suspend fun setUiScale(newValue: UiScale) 28 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/utils/ActivityUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import androidx.appcompat.app.AppCompatDelegate 6 | import androidx.core.os.LocaleListCompat 7 | import ru.rznnike.demokmp.domain.model.common.Language 8 | import java.util.* 9 | import kotlin.system.exitProcess 10 | 11 | fun Activity.restartApp() { 12 | val intent = Intent(this, this::class.java) 13 | val restartIntent = Intent.makeRestartActivityTask(intent.component) 14 | startActivity(restartIntent) 15 | exitProcess(0) 16 | } 17 | 18 | fun getSelectedLanguage() = Language.getByShortTag( 19 | (AppCompatDelegate.getApplicationLocales()[0] ?: Locale.getDefault()).language 20 | ) 21 | 22 | fun setSelectedLanguage(language: Language) = AppCompatDelegate.setApplicationLocales( 23 | LocaleListCompat.forLanguageTags(language.shortTag) 24 | ) 25 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/utils/ViewUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.CompositionLocalProvider 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.platform.LocalDensity 7 | import androidx.compose.ui.unit.Density 8 | import ru.rznnike.demokmp.domain.model.common.UiScale 9 | 10 | @Composable 11 | expect fun Modifier.onClick(action: () -> Unit): Modifier 12 | 13 | @Composable 14 | fun CustomUiScale( 15 | scale: UiScale, 16 | content: @Composable () -> Unit 17 | ) { 18 | val defaultDensity = LocalDensity.current 19 | val customDensity = Density( 20 | density = defaultDensity.density * scale.value / 100f, 21 | fontScale = defaultDensity.fontScale 22 | ) 23 | CompositionLocalProvider( 24 | LocalDensity provides customDensity 25 | ) { 26 | content() 27 | } 28 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/dbexample/GetDBExampleDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.dbexample 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.DBExampleGateway 6 | import ru.rznnike.demokmp.domain.model.db.DBExampleData 7 | 8 | class GetDBExampleDataUseCase( 9 | private val dbExampleGateway: DBExampleGateway, 10 | dispatcherProvider: DispatcherProvider 11 | ) : UseCaseWithParams(dispatcherProvider) { 12 | override suspend fun execute(parameters: Parameters) = Result( 13 | data = dbExampleGateway.get(id = parameters.id) 14 | ) 15 | 16 | data class Parameters( 17 | val id: Long 18 | ) 19 | 20 | data class Result( 21 | val data: DBExampleData? 22 | ) 23 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/interactor/log/SaveNetworkLogMessageToFileUseCase.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.interactor.log 2 | 3 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 4 | import ru.rznnike.demokmp.domain.common.interactor.UseCaseWithParams 5 | import ru.rznnike.demokmp.domain.gateway.LogGateway 6 | import ru.rznnike.demokmp.domain.log.NetworkLogMessage 7 | import java.io.File 8 | 9 | class SaveNetworkLogMessageToFileUseCase( 10 | private val logGateway: LogGateway, 11 | dispatcherProvider: DispatcherProvider 12 | ) : UseCaseWithParams(dispatcherProvider) { 13 | override suspend fun execute(parameters: Parameters) = logGateway.saveNetworkLogMessageToFile( 14 | file = parameters.file, 15 | message = parameters.message 16 | ) 17 | 18 | data class Parameters( 19 | val file: File, 20 | val message: NetworkLogMessage 21 | ) 22 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/di/GatewayModule.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import org.koin.dsl.module 4 | import ru.rznnike.demokmp.data.gateway.* 5 | import ru.rznnike.demokmp.domain.gateway.* 6 | 7 | internal val gatewayModule = module { 8 | single { AppGatewayImpl(get()) } 9 | single { PreferencesGatewayImpl(get(), get()) } 10 | single { HTTPExampleGatewayImpl(get(), get()) } 11 | single { WebSocketExampleGatewayImpl(get(), get()) } 12 | single { DBExampleGatewayImpl(get(), get(), get()) } 13 | single { PdfExampleGatewayImpl(get()) } 14 | single { ChartExampleGatewayImpl(get()) } 15 | single { LogGatewayImpl(get(), get(), get(), get()) } 16 | single { ComObjectExampleGatewayImpl(get(), get()) } 17 | single { FileGatewayImpl(get()) } 18 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/gateway/PdfExampleGatewayImpl.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.gateway 2 | 3 | import kotlinx.coroutines.withContext 4 | import ru.rznnike.demokmp.data.utils.DataConstants 5 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 6 | import ru.rznnike.demokmp.domain.gateway.PdfExampleGateway 7 | import java.io.File 8 | 9 | class PdfExampleGatewayImpl( 10 | private val dispatcherProvider: DispatcherProvider 11 | ) : PdfExampleGateway { 12 | override suspend fun getSamplePdf(): File = withContext(dispatcherProvider.io) { 13 | val result = File(DataConstants.TEST_PDF_PATH) 14 | if (!result.exists()) { 15 | throw NoSuchFileException(result) 16 | } 17 | result 18 | } 19 | 20 | override suspend fun savePdfToFile(tempPdfFile: File, saveFile: File): Unit = withContext(dispatcherProvider.io) { 21 | tempPdfFile.copyTo( 22 | target = saveFile, 23 | overwrite = true 24 | ) 25 | } 26 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/di/DatabaseModule.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import androidx.room.Room 4 | import org.koin.android.ext.koin.androidApplication 5 | import org.koin.dsl.module 6 | import ru.rznnike.demokmp.data.storage.MainDB 7 | import ru.rznnike.demokmp.data.utils.DataConstants 8 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 9 | 10 | internal actual fun getDatabaseModule() = module { 11 | single { 12 | Room 13 | .databaseBuilder( 14 | context = androidApplication(), 15 | name = DataConstants.DB_NAME 16 | ) 17 | .fallbackToDestructiveMigration( 18 | dropAllTables = true 19 | ) 20 | .setQueryCoroutineContext(get().io) 21 | .build() 22 | } 23 | 24 | factory { get().getLogMessageDao() } 25 | factory { get().getLogNetworkMessageDao() } 26 | factory { get().getDBExampleDataDao() } 27 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/di/DatabaseModule.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import androidx.room.Room 4 | import androidx.sqlite.driver.bundled.BundledSQLiteDriver 5 | import org.koin.dsl.module 6 | import ru.rznnike.demokmp.data.storage.MainDB 7 | import ru.rznnike.demokmp.data.utils.DataConstants 8 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 9 | 10 | internal actual fun getDatabaseModule() = module { 11 | single { 12 | Room 13 | .databaseBuilder( 14 | name = DataConstants.DB_PATH 15 | ) 16 | .fallbackToDestructiveMigration( 17 | dropAllTables = true 18 | ) 19 | .setDriver(BundledSQLiteDriver()) 20 | .setQueryCoroutineContext(get().io) 21 | .build() 22 | } 23 | 24 | factory { get().getLogMessageDao() } 25 | factory { get().getLogNetworkMessageDao() } 26 | factory { get().getDBExampleDataDao() } 27 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/storage/MainDB.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.storage 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import ru.rznnike.demokmp.data.storage.dao.DBExampleDataDao 6 | import ru.rznnike.demokmp.data.storage.dao.LogMessageDao 7 | import ru.rznnike.demokmp.data.storage.dao.LogNetworkMessageDao 8 | import ru.rznnike.demokmp.data.storage.entity.DBExampleDataEntity 9 | import ru.rznnike.demokmp.data.storage.entity.LogMessageEntity 10 | import ru.rznnike.demokmp.data.storage.entity.LogNetworkMessageEntity 11 | 12 | @Database( 13 | entities = [ 14 | LogMessageEntity::class, 15 | LogNetworkMessageEntity::class, 16 | DBExampleDataEntity::class 17 | ], 18 | version = 1, 19 | exportSchema = true 20 | ) 21 | abstract class MainDB : RoomDatabase() { 22 | abstract fun getDBExampleDataDao(): DBExampleDataDao 23 | 24 | abstract fun getLogMessageDao(): LogMessageDao 25 | 26 | abstract fun getLogNetworkMessageDao(): LogNetworkMessageDao 27 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/common/interactor/UseCaseResult.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.common.interactor 2 | 3 | class UseCaseResult( 4 | val data: R? = null, 5 | val error: Exception? = null 6 | ) { 7 | override fun toString(): String { 8 | return error?.let { 9 | "Error[exception=$error]" 10 | } ?: run { 11 | "Success[data=$data]" 12 | } 13 | } 14 | 15 | suspend fun process( 16 | onSuccessCallback: suspend (R) -> Unit, 17 | onErrorCallback: (suspend (Exception) -> Unit)? = null 18 | ): UseCaseResult { 19 | error?.let { 20 | onErrorCallback?.invoke(it) 21 | } ?: run { 22 | data?.let { 23 | onSuccessCallback(it) 24 | } ?: run { 25 | onErrorCallback?.invoke( 26 | Exception("Unexpected exception in UseCaseResult: data=null and error=null") 27 | ) 28 | } 29 | } 30 | 31 | return this 32 | } 33 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/network/interceptor/HttpErrorResponseInterceptor.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.network.interceptor 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | import ru.rznnike.demokmp.data.network.error.CustomServerException 6 | import ru.rznnike.demokmp.data.utils.HttpCode 7 | 8 | class HttpErrorResponseInterceptor : Interceptor { 9 | override fun intercept(chain: Interceptor.Chain): Response { 10 | val response = chain 11 | .proceed(chain.request()) 12 | .run { 13 | if (code == HttpCode.NO_CONTENT.code) { 14 | newBuilder() 15 | .code(HttpCode.CUSTOM_NO_CONTENT.code) 16 | .build() 17 | } else { 18 | this 19 | } 20 | } 21 | if (!response.isSuccessful) { 22 | throw CustomServerException( 23 | httpCode = response.code 24 | ) 25 | } 26 | return response 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/preference/PreferencesManager.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.preference 2 | 3 | import com.russhwolf.settings.Settings 4 | import ru.rznnike.demokmp.domain.model.common.Language 5 | import ru.rznnike.demokmp.domain.model.common.Theme 6 | import ru.rznnike.demokmp.domain.model.common.UiScale 7 | import ru.rznnike.demokmp.domain.model.print.TwoSidedPrint 8 | 9 | class PreferencesManager( 10 | private val settings: Settings 11 | ) { 12 | val testCounter = Preference.IntPreference(settings, "testCounter") 13 | val language = Preference.LanguagePreference(settings, "language", Language.default) 14 | val theme = Preference.ThemePreference(settings, "theme", Theme.default) 15 | val printerName = Preference.StringPreference(settings, "printerName") 16 | val twoSidedPrint = Preference.TwoSidedPrintPreference(settings, "twoSidedPrint", TwoSidedPrint.default) 17 | val uiScale = Preference.UiScalePreference(settings, "uiScale", UiScale.default) 18 | 19 | @Suppress("unused") 20 | fun clearAll() = settings.clear() 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Nick Tiart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/ui/view/CustomCheckbox.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.view 2 | 3 | import androidx.compose.material3.Icon 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import org.jetbrains.compose.resources.painterResource 8 | import ru.rznnike.demokmp.app.ui.theme.LocalCustomColorScheme 9 | import ru.rznnike.demokmp.generated.resources.Res 10 | import ru.rznnike.demokmp.generated.resources.ic_checkbox_off 11 | import ru.rznnike.demokmp.generated.resources.ic_checkbox_on 12 | 13 | @Composable 14 | fun CustomCheckbox( 15 | modifier: Modifier = Modifier, 16 | checked: Boolean, 17 | enabled: Boolean = true 18 | ) = Icon( 19 | modifier = modifier, 20 | painter = painterResource( 21 | if (checked) Res.drawable.ic_checkbox_on else Res.drawable.ic_checkbox_off 22 | ), 23 | tint = when { 24 | !enabled -> LocalCustomColorScheme.current.disabledText 25 | checked -> MaterialTheme.colorScheme.primary 26 | else -> MaterialTheme.colorScheme.outline 27 | }, 28 | contentDescription = null 29 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/storage/entity/LogMessageEntity.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.storage.entity 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import ru.rznnike.demokmp.domain.log.LogLevel 6 | import ru.rznnike.demokmp.domain.log.LogMessage 7 | import ru.rznnike.demokmp.domain.log.LogType 8 | 9 | @Entity 10 | data class LogMessageEntity( 11 | @PrimaryKey(autoGenerate = true) 12 | val id: Long = 0, 13 | val type: LogType, 14 | val level: LogLevel, 15 | val timestamp: Long, 16 | val tag: String, 17 | val message: String, 18 | val sessionId: Long 19 | ) 20 | 21 | fun LogMessageEntity.toLogMessage(currentSessionId: Long) = LogMessage( 22 | id = id, 23 | type = type, 24 | level = level, 25 | timestamp = timestamp, 26 | tag = tag, 27 | message = message, 28 | isCurrentSession = currentSessionId == sessionId 29 | ) 30 | 31 | fun LogMessage.toLogMessageEntity(currentSessionId: Long) = LogMessageEntity( 32 | id = id, 33 | type = type, 34 | level = level, 35 | timestamp = timestamp, 36 | tag = tag, 37 | message = message, 38 | sessionId = currentSessionId 39 | ) -------------------------------------------------------------------------------- /.run/archive [release].run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 21 | 23 | true 24 | true 25 | false 26 | false 27 | 28 | 29 | -------------------------------------------------------------------------------- /.run/archive [staging].run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 21 | 23 | true 24 | true 25 | false 26 | false 27 | 28 | 29 | -------------------------------------------------------------------------------- /.run/run [debug].run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 17 | 22 | 24 | true 25 | true 26 | false 27 | false 28 | 29 | 30 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/di/PreferenceModule.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import com.russhwolf.settings.PropertiesSettings 4 | import com.russhwolf.settings.Settings 5 | import org.koin.dsl.module 6 | import ru.rznnike.demokmp.data.preference.PreferencesManager 7 | import ru.rznnike.demokmp.data.utils.DataConstants 8 | import java.io.File 9 | import java.util.* 10 | 11 | internal actual fun getPreferenceModule() = module { 12 | single { createSettings() } 13 | single { PreferencesManager(get()) } 14 | } 15 | 16 | private fun createSettings(): Settings { 17 | val propertiesFile = File(DataConstants.PREFERENCES_PATH) 18 | val properties = Properties().also { properties -> 19 | if (propertiesFile.exists()) { 20 | try { 21 | propertiesFile.inputStream().use { 22 | properties.load(it) 23 | } 24 | } catch (_: Exception) {} 25 | } else { 26 | File(DataConstants.PREFERENCES_FOLDER_PATH).mkdirs() 27 | } 28 | } 29 | return PropertiesSettings(properties) { props -> 30 | propertiesFile.outputStream().use { 31 | props.store(it, null) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /.run/run [release].run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 17 | 22 | 24 | true 25 | true 26 | false 27 | false 28 | 29 | 30 | -------------------------------------------------------------------------------- /.run/run [staging].run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 17 | 22 | 24 | true 25 | true 26 | false 27 | false 28 | 29 | 30 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/navigation/DesktopNavigationScreen.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.input.key.KeyEvent 5 | import kotlinx.serialization.Serializable 6 | import kotlinx.serialization.Transient 7 | import ru.rznnike.demokmp.app.model.common.HotkeyDescription 8 | import ru.rznnike.demokmp.app.utils.windowViewModel 9 | import ru.rznnike.demokmp.app.ui.viewmodel.global.hotkeys.HotKeysViewModel 10 | 11 | @Serializable 12 | abstract class DesktopNavigationScreen : NavigationScreen() { 13 | @Transient 14 | var screenKeyEventCallback: ((event: KeyEvent) -> Unit)? = null 15 | @Transient 16 | private val keyEventListener = HotKeysViewModel.EventListener { event -> 17 | screenKeyEventCallback?.invoke(event) 18 | } 19 | 20 | @Composable 21 | final override fun Content() { 22 | windowViewModel().apply { 23 | setScreenEventListener(keyEventListener) 24 | setScreenHotkeysDescription(getHotkeysDescription()) 25 | } 26 | super.Content() 27 | } 28 | 29 | @Composable 30 | open fun getHotkeysDescription(): List = emptyList() 31 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/NetworkLogMessage.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log 2 | 3 | import kotlinx.serialization.Serializable 4 | import ru.rznnike.demokmp.data.utils.json.UUIDSerializable 5 | import ru.rznnike.demokmp.domain.utils.GlobalConstants 6 | import ru.rznnike.demokmp.domain.utils.toDateString 7 | 8 | @Serializable 9 | data class NetworkLogMessage( 10 | val id: Long = 0, 11 | val uuid: UUIDSerializable, 12 | val request: LogMessage, 13 | val response: LogMessage? = null, 14 | val state: NetworkRequestState = NetworkRequestState.SENT, 15 | val isCurrentSession: Boolean = true 16 | ) { 17 | fun getFullText(): String { 18 | val stringBuilder = StringBuilder() 19 | .appendLine(request.timestamp.toDateString(GlobalConstants.DATE_PATTERN_TIME_MS)) 20 | .append(request.getFormattedMessage()) 21 | response?.let { response -> 22 | stringBuilder 23 | .appendLine() 24 | .appendLine() 25 | .appendLine(response.timestamp.toDateString(GlobalConstants.DATE_PATTERN_TIME_MS)) 26 | .append(response.getFormattedMessage()) 27 | } 28 | 29 | return stringBuilder.toString() 30 | } 31 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/data/shell/PowerShellWrapper.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.shell 2 | 3 | import com.sun.jna.platform.win32.Variant 4 | import ru.rznnike.demokmp.data.comobject.COMObjectWrapper 5 | 6 | class PowerShellWrapper : COMObjectWrapper("Shell.Application") { 7 | fun minimizeAll() { 8 | invoke("MinimizeAll") 9 | } 10 | 11 | private fun getSystemInformation(infoName: String): Variant.VARIANT { 12 | return invoke("GetSystemInformation", Variant.VARIANT(infoName)) 13 | } 14 | 15 | fun getCPUSpeedMhz(): Int { 16 | return getSystemInformation("ProcessorSpeed").intValue() 17 | } 18 | 19 | fun getCPUArchitecture(): String { 20 | return when (getSystemInformation("ProcessorArchitecture").intValue()) { 21 | 0 -> "x86" 22 | 5 -> "ARM" 23 | 6 -> "Intel Itanium" 24 | 9 -> "x64" 25 | 12 -> "ARM64" 26 | else -> "unknown" 27 | } 28 | } 29 | 30 | fun getRAMAmountGb(): Double { 31 | return getSystemInformation("PhysicalMemoryInstalled").longValue() / 1073741824.0 32 | } 33 | 34 | fun openFolderOrFile(path: String) { 35 | invoke("Open", Variant.VARIANT(path)) 36 | } 37 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/viewmodel/global/configuration/WindowConfigurationViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.viewmodel.global.configuration 2 | 3 | import androidx.lifecycle.viewModelScope 4 | import kotlinx.coroutines.flow.update 5 | import kotlinx.coroutines.launch 6 | import ru.rznnike.demokmp.app.common.viewmodel.BaseUiViewModel 7 | 8 | class WindowConfigurationViewModel : BaseUiViewModel() { 9 | override fun provideDefaultUIState() = UiState() 10 | 11 | fun setWindowTitle(newValue: String) { 12 | viewModelScope.launch { 13 | mutableUiState.update { currentState -> 14 | currentState.copy( 15 | windowTitle = newValue 16 | ) 17 | } 18 | } 19 | } 20 | 21 | fun setCloseWindowCallback(newValue: (() -> Unit)) { 22 | if (mutableUiState.value.closeWindowCallback == newValue) return 23 | 24 | mutableUiState.update { currentState -> 25 | currentState.copy( 26 | closeWindowCallback = newValue 27 | ) 28 | } 29 | } 30 | 31 | data class UiState( 32 | val windowTitle: String = "", 33 | val closeWindowCallback: (() -> Unit) = { } 34 | ) 35 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/network/interceptor/HttpBaseUrlInterceptor.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.network.interceptor 2 | 3 | import okhttp3.HttpUrl 4 | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull 5 | import okhttp3.Interceptor 6 | import okhttp3.Response 7 | import ru.rznnike.demokmp.BuildKonfig 8 | import ru.rznnike.demokmp.data.preference.PreferencesManager 9 | import java.io.IOException 10 | 11 | class HttpBaseUrlInterceptor( 12 | private val preferencesManager: PreferencesManager 13 | ) : Interceptor { 14 | @Throws(IOException::class) 15 | override fun intercept(chain: Interceptor.Chain): Response { 16 | var request = chain.request() 17 | // val baseUrl = preferencesManager.httpServerUrl.get() 18 | val baseUrl = BuildKonfig.API_MAIN 19 | baseUrl.toHttpUrlOrNull()?.let { 20 | val newUrlBuilder: HttpUrl.Builder = it.newBuilder() 21 | for (segment in request.url.pathSegments) { 22 | newUrlBuilder.addPathSegment(segment) 23 | } 24 | newUrlBuilder.query(request.url.query) 25 | request = request.newBuilder() 26 | .url(newUrlBuilder.build()) 27 | .build() 28 | } 29 | return chain.proceed(request) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/ui/view/TabText.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.view 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.unit.dp 11 | import ru.rznnike.demokmp.app.ui.theme.bodyLargeBold 12 | 13 | @Composable 14 | fun TabText( 15 | modifier: Modifier = Modifier, 16 | text: String, 17 | selected: Boolean = false 18 | ) = Box( 19 | modifier = modifier.width(IntrinsicSize.Max) 20 | ) { 21 | Text( 22 | modifier = Modifier.padding(vertical = 4.dp), 23 | text = text, 24 | style = MaterialTheme.typography.bodyLargeBold 25 | ) 26 | if (selected) { 27 | Spacer( 28 | modifier = Modifier 29 | .fillMaxWidth() 30 | .height(2.dp) 31 | .background( 32 | color = MaterialTheme.colorScheme.primary, 33 | shape = MaterialTheme.shapes.extraSmall 34 | ) 35 | .align(Alignment.BottomCenter), 36 | ) 37 | } 38 | } -------------------------------------------------------------------------------- /.run/run [debug +args].run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 17 | 23 | 25 | true 26 | true 27 | false 28 | false 29 | 30 | 31 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/navigation/NavigationUtils.android.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.navigation 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import ru.rznnike.demokmp.app.ui.screen.chartexample.ChartExampleScreen 5 | import ru.rznnike.demokmp.app.ui.screen.customui.CustomUIScreen 6 | import ru.rznnike.demokmp.app.ui.screen.dbexample.DBExampleScreen 7 | import ru.rznnike.demokmp.app.ui.screen.home.HomeScreen 8 | import ru.rznnike.demokmp.app.ui.screen.httpexample.HTTPExampleScreen 9 | import ru.rznnike.demokmp.app.ui.screen.navigation.NavigationExampleScreen 10 | import ru.rznnike.demokmp.app.ui.screen.settings.NestedSettingsScreen 11 | import ru.rznnike.demokmp.app.ui.screen.settings.SettingsScreen 12 | import ru.rznnike.demokmp.app.ui.screen.splash.SplashScreen 13 | import ru.rznnike.demokmp.app.ui.screen.wsexample.WebSocketsExampleScreen 14 | 15 | actual fun NavGraphBuilder.buildNavGraph() { 16 | addToNavGraph() 17 | addToNavGraph() 18 | addToNavGraph() 19 | addToNavGraph() 20 | addToNavGraph() 21 | addToNavGraph() 22 | addToNavGraph() 23 | addToNavGraph() 24 | addToNavGraph() 25 | addToNavGraph() 26 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/ru/rznnike/demokmp/app/viewmodel/app/ActivityViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.viewmodel.app 2 | 3 | import org.koin.core.component.inject 4 | import ru.rznnike.demokmp.app.common.viewmodel.BaseViewModel 5 | import ru.rznnike.demokmp.app.dispatcher.event.AppEvent 6 | import ru.rznnike.demokmp.app.dispatcher.event.EventDispatcher 7 | 8 | class ActivityViewModel( 9 | private val restartCallback: () -> Unit 10 | ) : BaseViewModel() { 11 | private val eventDispatcher: EventDispatcher by inject() 12 | 13 | private val eventListener = object : EventDispatcher.EventListener { 14 | override fun onEvent(event: AppEvent) { 15 | when (event) { 16 | is AppEvent.ActivityRestartRequested -> { 17 | restartCallback() 18 | } 19 | else -> Unit 20 | } 21 | } 22 | } 23 | 24 | init { 25 | subscribeToEvents() 26 | } 27 | 28 | override fun onCleared() { 29 | eventDispatcher.removeEventListener(eventListener) 30 | } 31 | 32 | private fun subscribeToEvents() { 33 | eventDispatcher.addEventListener( 34 | appEventClasses = listOf( 35 | AppEvent.ActivityRestartRequested::class 36 | ), 37 | listener = eventListener 38 | ) 39 | } 40 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/utils/ViewUtils.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.gestures.detectTapGestures 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.input.key.* 8 | import androidx.compose.ui.input.pointer.pointerInput 9 | import androidx.compose.ui.platform.LocalFocusManager 10 | import androidx.compose.foundation.onClick as onClickDesktop 11 | 12 | @OptIn(ExperimentalFoundationApi::class) 13 | @Composable 14 | actual fun Modifier.onClick(action: () -> Unit): Modifier = onClickDesktop(onClick = action) 15 | 16 | @Composable 17 | fun Modifier.clearFocusOnTap(): Modifier { 18 | val focusManager = LocalFocusManager.current 19 | return pointerInput(Unit) { 20 | detectTapGestures( 21 | onTap = { 22 | focusManager.clearFocus() 23 | } 24 | ) 25 | } 26 | } 27 | 28 | fun Modifier.onEnterKey(action: () -> Unit): Modifier = 29 | onKeyEvent { keyEvent -> 30 | when { 31 | (keyEvent.key.nativeKeyCode == Key.Enter.nativeKeyCode) && (keyEvent.type == KeyEventType.KeyUp) -> { 32 | action() 33 | true 34 | } 35 | else -> false 36 | } 37 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/utils/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.utils 2 | 3 | import java.math.BigDecimal 4 | import java.text.DecimalFormat 5 | import java.text.DecimalFormatSymbols 6 | 7 | val decimal2Format = DecimalFormatSymbols().let { 8 | it.groupingSeparator = ' ' 9 | it.decimalSeparator = '.' 10 | DecimalFormat("0.##", it) 11 | } 12 | 13 | val moneyDecimal2Format = getNumberFormat(2) 14 | 15 | fun getNumberFormat(precision: Int, grouping: Boolean = true): DecimalFormat { 16 | val pattern = StringBuilder( 17 | if (grouping) "###,###,###,##0." else "0." 18 | ).apply { 19 | if (precision > 0) { 20 | for (i in 1..precision) { 21 | append("0") 22 | } 23 | } else { 24 | append("##") 25 | } 26 | }.toString() 27 | return DecimalFormatSymbols().let { 28 | it.groupingSeparator = ' ' 29 | it.decimalSeparator = '.' 30 | DecimalFormat(pattern, it) 31 | } 32 | } 33 | 34 | fun String.toBigDecimalOrNullSmart() = 35 | replace(" ", "") 36 | .replace(",", ".") 37 | .toBigDecimalOrNull() 38 | 39 | fun String.toBigDecimalSmart(): BigDecimal = toBigDecimalOrNullSmart() ?: BigDecimal.ZERO 40 | 41 | fun String.toDoubleOrNullSmart() = 42 | replace(" ", "") 43 | .replace(",", ".") 44 | .toDoubleOrNull() 45 | -------------------------------------------------------------------------------- /runScripts/windows/launcher_script.ps1: -------------------------------------------------------------------------------- 1 | Add-Type -AssemblyName PresentationFramework 2 | 3 | function ShowErrorMessage { 4 | [CmdletBinding()] 5 | param ( 6 | [Parameter()] 7 | [string] 8 | $Message 9 | ) 10 | [System.Windows.MessageBox]::Show($Message, "Launch error", "OK", "Error") 11 | } 12 | 13 | Start-Sleep -s 1 14 | 15 | $configuration_path = "launcher_configuration.ini" 16 | if ([System.IO.File]::Exists($configuration_path)) { 17 | $configuration = Get-Content $configuration_path -Raw | ConvertFrom-StringData 18 | 19 | $java_command = if ([string]$configuration.java_path -ne "") { $configuration.java_path } else { "java" } 20 | $java_version = (Get-Command $java_command | Select-Object -ExpandProperty Version).Major 21 | if ([int]$java_version -lt 17) { 22 | $displayed_version = if ([string]$java_version -ne "") { $java_version } else { "none" } 23 | ShowErrorMessage("Java 17+ required for launch, current version - $($displayed_version)") 24 | } else { 25 | $jar_path = "application/app.jar" 26 | if ([System.IO.File]::Exists($jar_path)) { 27 | & $java_command -jar $jar_path launchedFromScript 28 | } else { 29 | ShowErrorMessage("Executable file not found") 30 | } 31 | } 32 | } else { 33 | ShowErrorMessage("Launch configuration not found") 34 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/gateway/LogGateway.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.gateway 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import ru.rznnike.demokmp.domain.log.LogData 5 | import ru.rznnike.demokmp.domain.log.LogMessage 6 | import ru.rznnike.demokmp.domain.log.NetworkLogMessage 7 | import ru.rznnike.demokmp.domain.log.NetworkLogData 8 | import ru.rznnike.demokmp.domain.log.extension.DatabaseLoggerExtension 9 | import java.io.File 10 | import java.util.UUID 11 | 12 | interface LogGateway { 13 | suspend fun addLogMessageToDB(message: LogMessage) 14 | 15 | suspend fun addLogNetworkMessageToDB(message: NetworkLogMessage) 16 | 17 | suspend fun getLogNetworkMessage(uuid: UUID): NetworkLogMessage? 18 | 19 | suspend fun getLogNetworkMessageAsFlow(uuid: UUID): Flow 20 | 21 | suspend fun getLog(): LogData 22 | 23 | suspend fun getNewLog(lastId: Long): List 24 | 25 | suspend fun getNetworkLog(): NetworkLogData 26 | 27 | suspend fun getNewNetworkLog(lastId: Long): List 28 | 29 | suspend fun clearLog() 30 | 31 | suspend fun clearNetworkLog() 32 | 33 | suspend fun deleteOldLogs(logsRetentionMode: DatabaseLoggerExtension.LogsRetentionMode) 34 | 35 | suspend fun saveLogToFile(file: File) 36 | 37 | suspend fun saveNetworkLogMessageToFile(file: File, message: NetworkLogMessage) 38 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/utils/DataConstants.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.utils 2 | 3 | import ru.rznnike.demokmp.BuildKonfig 4 | import ru.rznnike.demokmp.domain.utils.OperatingSystem 5 | 6 | object DataConstants { 7 | val ROOT_DIR = if (BuildKonfig.RUN_FROM_IDE) "workDirectory" else "." 8 | private val APP_DATA_DIR = "$ROOT_DIR/data" 9 | 10 | val NETWORK_CACHE_PATH = "$APP_DATA_DIR/networkCache" 11 | val PREFERENCES_FOLDER_PATH = "$APP_DATA_DIR/preferences" 12 | val PREFERENCES_PATH = "$PREFERENCES_FOLDER_PATH/settings.properties" 13 | val DB_NAME = "demokmp_main.db" 14 | val DB_PATH = "$APP_DATA_DIR/database/$DB_NAME" 15 | 16 | private const val LAUNCHER_CONFIGURATION_NAME = "launcher_configuration.ini" 17 | val LAUNCHER_CONFIGURATION_PATH = "$ROOT_DIR/$LAUNCHER_CONFIGURATION_NAME" 18 | 19 | val LOGS_PATH = "$ROOT_DIR/logs" 20 | const val LOG_FILE_NAME_TEMPLATE = "log_%s" 21 | const val LOG_FILE_NAME_EXTENSION = "txt" 22 | const val LOGS_RETENTION_TIME_MS = 172800000L // 2 days 23 | const val LOGS_CLEARING_PERIOD_MS = 3600000L // 1 hour 24 | 25 | val RUN_SCRIPT_NAME = if (OperatingSystem.isWindows) "run.vbs" else "run.sh" 26 | 27 | const val HEADER_TEST = "Test" 28 | 29 | const val PDF_FILE_NAME_EXTENSION = "pdf" 30 | val TEST_PDF_PATH = "$ROOT_DIR/sample.pdf" 31 | val EXAMPLE_PDF_SAVE_NAME = "saved_pdf_file.pdf" 32 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/common/viewmodel/BaseUiViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.common.viewmodel 2 | 3 | import kotlinx.coroutines.flow.MutableStateFlow 4 | import kotlinx.coroutines.flow.StateFlow 5 | import kotlinx.coroutines.flow.asStateFlow 6 | import java.util.* 7 | import kotlin.concurrent.schedule 8 | 9 | private const val DEFAULT_PROGRESS_DELAY_MS = 250L 10 | 11 | abstract class BaseUiViewModel : BaseViewModel() { 12 | protected val mutableUiState by lazy { MutableStateFlow(provideDefaultUIState()) } 13 | val uiState: StateFlow by lazy { mutableUiState.asStateFlow() } 14 | 15 | protected open var progressDelayMs: Long = DEFAULT_PROGRESS_DELAY_MS 16 | private var progressTask: TimerTask? = null 17 | private var goalProgressState = false 18 | 19 | protected fun setProgress(show: Boolean, immediately: Boolean = false) { 20 | if ((goalProgressState == show) && (progressTask != null) && (!immediately)) return 21 | 22 | goalProgressState = show 23 | progressTask?.cancel() 24 | if (immediately || (progressDelayMs <= 0)) { 25 | onProgressStateChanged(show) 26 | } else { 27 | progressTask = Timer().schedule(progressDelayMs) { 28 | onProgressStateChanged(show) 29 | } 30 | } 31 | } 32 | 33 | protected open fun onProgressStateChanged(show: Boolean) = Unit 34 | 35 | protected abstract fun provideDefaultUIState(): State 36 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/utils/PlatformUtils.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import org.koin.core.component.KoinComponent 4 | import ru.rznnike.demokmp.data.comobject.COMObjectWrapper 5 | import ru.rznnike.demokmp.domain.utils.OperatingSystem 6 | import java.awt.Desktop 7 | import java.net.NetworkInterface 8 | import java.net.URI 9 | 10 | actual val platformName: String = "Java ${System.getProperty("java.version")} | ${System.getProperty("java.vm.name")} | ${System.getProperty("java.vm.vendor")}" 11 | 12 | actual fun getMacAddress(): String? = 13 | NetworkInterface 14 | .getNetworkInterfaces() 15 | .toList() 16 | .filter { it.hardwareAddress != null } 17 | .sortedWith( 18 | compareBy( 19 | { !it.displayName.contains(Regex("Ethernet|Wi-Fi", RegexOption.IGNORE_CASE)) }, 20 | { it.displayName.contains(Regex("Virtual", RegexOption.IGNORE_CASE)) } 21 | ) 22 | ) 23 | .firstNotNullOfOrNull { element -> 24 | element.hardwareAddress?.joinToString(separator = "-") { "%02X".format(it) } 25 | } 26 | 27 | actual fun KoinComponent.openLink(link: String) { 28 | Desktop.getDesktop().browse(URI(link)) 29 | } 30 | 31 | actual fun initCOMLibrary() { 32 | if (OperatingSystem.isWindows) { 33 | COMObjectWrapper.initialize() 34 | } 35 | } 36 | 37 | actual fun destroyCOMLibrary() { 38 | if (OperatingSystem.isWindows) { 39 | COMObjectWrapper.uninitialize() 40 | } 41 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/utils/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.text.AnnotatedString 5 | import androidx.compose.ui.text.SpanStyle 6 | import androidx.compose.ui.text.buildAnnotatedString 7 | import ru.rznnike.demokmp.BuildKonfig 8 | import ru.rznnike.demokmp.app.ui.theme.LocalCustomColorScheme 9 | import java.util.regex.Pattern 10 | 11 | fun getFormattedAppVersion(): String { 12 | val builder = StringBuilder() 13 | .append("v ") 14 | .append(BuildKonfig.VERSION_NAME) 15 | .append(".${BuildKonfig.VERSION_CODE}") 16 | if (BuildKonfig.BUILD_TYPE != "release") builder.append(" ${BuildKonfig.BUILD_TYPE}") 17 | return builder.toString() 18 | } 19 | 20 | @Composable 21 | fun String.highlightSubstrings(substring: String): AnnotatedString { 22 | return buildAnnotatedString { 23 | val matcher = substring 24 | .toPattern(Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.LITERAL) 25 | .matcher(this@highlightSubstrings) 26 | append(this@highlightSubstrings) 27 | if (substring.isNotEmpty()) { 28 | while (matcher.find()) { 29 | addStyle( 30 | style = SpanStyle( 31 | background = LocalCustomColorScheme.current.searchSelection 32 | ), 33 | start = matcher.start(), 34 | end = matcher.end() 35 | ) 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.di 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.MainScope 6 | import org.koin.dsl.module 7 | import ru.rznnike.demokmp.app.dispatcher.event.EventDispatcher 8 | import ru.rznnike.demokmp.app.dispatcher.notifier.Notifier 9 | import ru.rznnike.demokmp.app.error.ErrorHandler 10 | import ru.rznnike.demokmp.data.shell.getShellManager 11 | import ru.rznnike.demokmp.domain.common.CoroutineScopeProvider 12 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 13 | import java.time.Clock 14 | 15 | internal val appModule = module { 16 | single { Clock.systemUTC() } 17 | single { ErrorHandler() } 18 | single { Notifier(get()) } 19 | single { EventDispatcher(get()) } 20 | single { getShellManager() } 21 | 22 | single { 23 | object : CoroutineScopeProvider { 24 | override val ui = MainScope() 25 | override val default = CoroutineScope(Dispatchers.Default) 26 | override val io = CoroutineScope(Dispatchers.IO) 27 | override val unconfined = CoroutineScope(Dispatchers.Unconfined) 28 | } 29 | } 30 | 31 | single { 32 | object : DispatcherProvider { 33 | override val ui = Dispatchers.Main 34 | override val default = Dispatchers.Default 35 | override val io = Dispatchers.IO 36 | override val unconfined = Dispatchers.Unconfined 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/network/interceptor/HttpHeaderInterceptor.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.network.interceptor 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Request 5 | import okhttp3.Response 6 | import ru.rznnike.demokmp.data.preference.PreferencesManager 7 | import ru.rznnike.demokmp.data.utils.DataConstants 8 | import java.io.IOException 9 | 10 | class HttpHeaderInterceptor( 11 | private val preferencesManager: PreferencesManager 12 | ) : Interceptor { 13 | @Throws(IOException::class) 14 | override fun intercept(chain: Interceptor.Chain): Response { 15 | fun Request.Builder.setHeader(name: String, value: String): Request.Builder { 16 | removeHeader(name) 17 | addHeader(name, value) 18 | return this 19 | } 20 | 21 | // val authToken: String 22 | // runBlocking { 23 | // authToken = preferencesManager.authToken.get() 24 | // } 25 | val request = chain.request() 26 | .newBuilder() 27 | .apply { 28 | // if (authToken.isNotBlank()) { 29 | // setHeader( 30 | // DataConstants.HEADER_AUTHORIZATION, 31 | // DataConstants.BEARER_AUTH_TEMPLATE.format(authToken) 32 | // ) 33 | // } 34 | setHeader( 35 | DataConstants.HEADER_TEST, 36 | "test header content" 37 | ) 38 | } 39 | .build() 40 | return chain.proceed(request) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/navigation/navtype/BasicNavType.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.navigation.navtype 2 | 3 | import androidx.navigation.NavType 4 | import androidx.savedstate.SavedState 5 | import androidx.savedstate.read 6 | import androidx.savedstate.write 7 | import kotlinx.serialization.json.Json 8 | import kotlin.io.encoding.Base64 9 | import kotlin.io.encoding.ExperimentalEncodingApi 10 | 11 | abstract class BasicNavType( 12 | isNullableAllowed: Boolean = false 13 | ) : NavType(isNullableAllowed = isNullableAllowed) { 14 | override fun put(bundle: SavedState, key: String, value: T) = bundle.write { putString(key, serializeAsValue(value)) } 15 | 16 | override fun get(bundle: SavedState, key: String): T? = bundle.read { parseValue(getString(key)) } 17 | } 18 | 19 | @OptIn(ExperimentalEncodingApi::class) 20 | inline fun jsonNavTypeOf(isNullableAllowed: Boolean = false) = object : BasicNavType( 21 | isNullableAllowed = isNullableAllowed 22 | ) { 23 | override fun parseValue(value: String): T = 24 | Json.decodeFromString(String(Base64.UrlSafe.decode(value))) 25 | 26 | override fun serializeAsValue(value: T): String = 27 | Base64.UrlSafe.encode(Json.encodeToString(value).toByteArray()) 28 | } 29 | 30 | inline fun > enumNavTypeOf(isNullableAllowed: Boolean = false) = object : BasicNavType( 31 | isNullableAllowed = isNullableAllowed 32 | ) { 33 | override fun parseValue(value: String): T = enumValueOf(value) 34 | 35 | override fun serializeAsValue(value: T): String = value.toString() 36 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/gateway/WebSocketExampleGatewayImpl.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.gateway 2 | 3 | import kotlinx.coroutines.flow.map 4 | import kotlinx.coroutines.withContext 5 | import ru.rznnike.demokmp.data.network.AppWebSocketManager 6 | import ru.rznnike.demokmp.data.network.model.toWebSocketMessage 7 | import ru.rznnike.demokmp.data.network.model.toWebSocketMessageModel 8 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 9 | import ru.rznnike.demokmp.domain.gateway.WebSocketExampleGateway 10 | import ru.rznnike.demokmp.domain.model.websocket.WebSocketMessage 11 | import ru.rznnike.demokmp.domain.model.websocket.WebSocketSessionData 12 | 13 | class WebSocketExampleGatewayImpl( 14 | private val dispatcherProvider: DispatcherProvider, 15 | private val manager: AppWebSocketManager 16 | ) : WebSocketExampleGateway { 17 | override suspend fun getSession(): WebSocketSessionData = withContext(dispatcherProvider.io) { 18 | manager.getSession().let { data -> 19 | WebSocketSessionData( 20 | url = data.url, 21 | messages = data.messages.map { it.toWebSocketMessage() }, 22 | connectionState = data.connectionState 23 | ) 24 | } 25 | } 26 | 27 | override suspend fun closeSession() = withContext(dispatcherProvider.io) { 28 | manager.closeSession() 29 | } 30 | 31 | override suspend fun sendMessage(message: WebSocketMessage) = withContext(dispatcherProvider.io) { 32 | manager.sendMessage(message.toWebSocketMessageModel()) 33 | } 34 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/domain/log/extension/ConsoleLoggerExtension.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.domain.log.extension 2 | 3 | import kotlinx.coroutines.sync.Semaphore 4 | import kotlinx.coroutines.sync.withPermit 5 | import kotlinx.coroutines.withContext 6 | import ru.rznnike.demokmp.domain.log.LogLevel 7 | import ru.rznnike.demokmp.domain.log.LogMessage 8 | import ru.rznnike.demokmp.domain.log.LogType 9 | import ru.rznnike.demokmp.domain.log.printLog 10 | 11 | class ConsoleLoggerExtension( 12 | stopAfterOneError: Boolean = false 13 | ) : LoggerExtension(stopAfterOneError = stopAfterOneError) { 14 | private val outputLock = Semaphore(1) 15 | 16 | override suspend fun addMessage( 17 | tag: String, 18 | message: String, 19 | level: LogLevel, 20 | type: LogType, 21 | callback: suspend (LogMessage) -> Unit 22 | ) { 23 | if ((!stopAfterOneError) || (!isErrorDetected)) { 24 | val logMessage = LogMessage( 25 | type = type, 26 | level = level, 27 | timestamp = clock.millis(), 28 | tag = tag, 29 | message = message 30 | ) 31 | 32 | try { 33 | withContext(coroutineDispatcher) { 34 | outputLock.withPermit { 35 | printLog(logMessage) 36 | callback(logMessage) 37 | } 38 | } 39 | } catch (exception: Exception) { 40 | isErrorDetected = true 41 | throw exception 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /runScripts/linux/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | showErrorMessage() { 4 | zenity --title "Launch error" --error --text "$1" || # alter os 5 | kdialog --title "Launch error" --error "$1" || # kde 6 | xdialog --title "Launch error" --msgbox "$1" || # xorg 7 | fly-dialog --title "Launch error" --error "$1" # astra 8 | } 9 | 10 | sleep 1 11 | 12 | configuration_path="launcher_configuration.ini" 13 | if [[ -f "$configuration_path" ]]; then 14 | declare -A configuration 15 | while read line; do 16 | clean_line="${line//[$'\t\r\n']}" 17 | key="${clean_line%=*}" 18 | value="${clean_line#*=}" 19 | if [[ ! -z $key ]]; then 20 | configuration[$key]=$value 21 | fi 22 | done < "$configuration_path" 23 | 24 | java_command=${configuration["java_path"]} 25 | if [[ -z $java_command ]]; then 26 | java_command="java" 27 | fi 28 | java_version=$($java_command -version 2>&1 | grep -oP 'version "?(1\.)?\K\d+' || true) 29 | if [[ $java_version -lt 17 ]]; then 30 | if [[ -z $java_version ]]; then 31 | displayed_version="none" 32 | else 33 | displayed_version=$java_version 34 | fi 35 | showErrorMessage "Java 17+ required for launch, current version - $displayed_version" 36 | else 37 | jar_path="application/app.jar" 38 | if [[ -f "$jar_path" ]]; then 39 | nohup $java_command -jar $jar_path & 40 | sleep 1 41 | else 42 | showErrorMessage "Executable file not found" 43 | fi 44 | fi 45 | else 46 | showErrorMessage "Launch configuration not found" 47 | fi -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/viewmodel/global/hotkeys/HotKeysViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.viewmodel.global.hotkeys 2 | 3 | import androidx.compose.ui.input.key.KeyEvent 4 | import kotlinx.coroutines.flow.update 5 | import ru.rznnike.demokmp.app.common.viewmodel.BaseUiViewModel 6 | import ru.rznnike.demokmp.app.model.common.HotkeyDescription 7 | 8 | class HotKeysViewModel : BaseUiViewModel() { 9 | override fun provideDefaultUIState() = UiState() 10 | 11 | fun setScreenHotkeysDescription(newValue: List) { 12 | mutableUiState.update { currentState -> 13 | currentState.copy( 14 | screenHotkeysDescription = newValue 15 | ) 16 | } 17 | } 18 | 19 | fun setCommonHotkeysDescription(newValue: List) { 20 | mutableUiState.update { currentState -> 21 | currentState.copy( 22 | commonHotkeysDescription = newValue 23 | ) 24 | } 25 | } 26 | 27 | fun setScreenEventListener(newValue: EventListener) { 28 | mutableUiState.update { currentState -> 29 | currentState.copy( 30 | screenEventListener = newValue 31 | ) 32 | } 33 | } 34 | 35 | data class UiState( 36 | val screenHotkeysDescription: List = emptyList(), 37 | val commonHotkeysDescription: List = emptyList(), 38 | val screenEventListener: EventListener = EventListener {} 39 | ) 40 | 41 | fun interface EventListener { 42 | operator fun invoke(event: KeyEvent) 43 | } 44 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/storage/dao/LogMessageDao.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.storage.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import ru.rznnike.demokmp.data.storage.entity.LogMessageEntity 8 | 9 | @Dao 10 | interface LogMessageDao { 11 | @Insert(onConflict = OnConflictStrategy.REPLACE) 12 | suspend fun add(item: LogMessageEntity) 13 | 14 | @Query("SELECT * FROM LogMessageEntity WHERE id == :id") 15 | suspend fun get(id: Long): LogMessageEntity? 16 | 17 | @Query("SELECT id FROM LogMessageEntity ORDER BY id DESC LIMIT 1 OFFSET :offset - 1") 18 | suspend fun getNthId(offset: Int): Long? 19 | 20 | @Query("SELECT * FROM LogMessageEntity") 21 | suspend fun getAll(): List 22 | 23 | @Query("SELECT * FROM LogMessageEntity WHERE id > :lastId") 24 | suspend fun getNew(lastId: Long): List 25 | 26 | @Query("SELECT DISTINCT sessionId FROM LogMessageEntity") 27 | suspend fun getSessionIds(): List 28 | 29 | @Query("DELETE FROM LogMessageEntity WHERE id == :id") 30 | suspend fun delete(id: Long) 31 | 32 | @Query("DELETE FROM LogMessageEntity") 33 | suspend fun deleteAll() 34 | 35 | @Query("DELETE FROM LogMessageEntity WHERE timestamp < :borderTimestamp") 36 | suspend fun deleteOldByTimestamp(borderTimestamp: Long) 37 | 38 | @Query("DELETE FROM LogMessageEntity WHERE id < :borderId") 39 | suspend fun deleteOldById(borderId: Long) 40 | 41 | @Query("DELETE FROM LogMessageEntity WHERE sessionId < :borderSessionId") 42 | suspend fun deleteOldBySessionId(borderSessionId: Long) 43 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/storage/entity/LogNetworkMessageEntity.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.storage.entity 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Embedded 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import ru.rznnike.demokmp.domain.log.LogMessage 8 | import ru.rznnike.demokmp.domain.log.NetworkLogMessage 9 | import ru.rznnike.demokmp.domain.log.NetworkRequestState 10 | import java.util.* 11 | 12 | @Entity 13 | data class LogNetworkMessageEntity( 14 | @PrimaryKey(autoGenerate = true) 15 | val id: Long = 0, 16 | @ColumnInfo(index = true) 17 | val uuid: UUID, 18 | @Embedded(prefix = "request_") 19 | val request: LogMessage, 20 | @Embedded(prefix = "response_") 21 | val response: LogMessage? = null, 22 | val state: NetworkRequestState = NetworkRequestState.SENT, 23 | val sessionId: Long 24 | ) 25 | 26 | fun LogNetworkMessageEntity.toLogNetworkMessage(currentSessionId: Long): NetworkLogMessage { 27 | val isCurrentSession = currentSessionId == sessionId 28 | return NetworkLogMessage( 29 | id = id, 30 | uuid = uuid, 31 | request = request.copy( 32 | isCurrentSession = isCurrentSession 33 | ), 34 | response = response?.copy( 35 | isCurrentSession = isCurrentSession 36 | ), 37 | state = state, 38 | isCurrentSession = currentSessionId == sessionId 39 | ) 40 | } 41 | 42 | fun NetworkLogMessage.toLogNetworkMessageEntity(currentSessionId: Long) = LogNetworkMessageEntity( 43 | id = id, 44 | uuid = uuid, 45 | request = request, 46 | response = response, 47 | state = state, 48 | sessionId = currentSessionId 49 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/viewmodel/chartexample/ChartExampleViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.viewmodel.chartexample 2 | 3 | import androidx.lifecycle.viewModelScope 4 | import kotlinx.coroutines.flow.update 5 | import kotlinx.coroutines.launch 6 | import org.koin.core.component.inject 7 | import ru.rznnike.demokmp.app.common.viewmodel.BaseUiViewModel 8 | import ru.rznnike.demokmp.app.dispatcher.notifier.Notifier 9 | import ru.rznnike.demokmp.app.error.ErrorHandler 10 | import ru.rznnike.demokmp.domain.interactor.chartexample.GetChartSampleDataUseCase 11 | import ru.rznnike.demokmp.domain.model.chart.ChartPoint 12 | 13 | class ChartExampleViewModel : BaseUiViewModel() { 14 | private val notifier: Notifier by inject() 15 | private val errorHandler: ErrorHandler by inject() 16 | private val getChartSampleDataUseCase: GetChartSampleDataUseCase by inject() 17 | 18 | init { 19 | loadData() 20 | } 21 | 22 | override fun provideDefaultUIState() = UiState() 23 | 24 | private fun loadData() { 25 | viewModelScope.launch { 26 | getChartSampleDataUseCase().process( 27 | { result -> 28 | mutableUiState.update { currentState -> 29 | currentState.copy( 30 | data = result 31 | ) 32 | } 33 | }, { error -> 34 | errorHandler.proceed(error) { message -> 35 | notifier.sendAlert(message) 36 | } 37 | } 38 | ) 39 | } 40 | } 41 | 42 | data class UiState( 43 | val data: List = emptyList() 44 | ) 45 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/gateway/DBExampleGatewayImpl.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.gateway 2 | 3 | import kotlinx.coroutines.withContext 4 | import ru.rznnike.demokmp.data.storage.MainDB 5 | import ru.rznnike.demokmp.data.storage.dao.DBExampleDataDao 6 | import ru.rznnike.demokmp.data.storage.entity.toDBExampleData 7 | import ru.rznnike.demokmp.data.storage.entity.toDBExampleDataEntity 8 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 9 | import ru.rznnike.demokmp.domain.gateway.DBExampleGateway 10 | import ru.rznnike.demokmp.domain.model.db.DBExampleData 11 | 12 | class DBExampleGatewayImpl( 13 | private val dispatcherProvider: DispatcherProvider, 14 | private val dbExampleDataDao: DBExampleDataDao, 15 | private val mainDB: MainDB 16 | ) : DBExampleGateway { 17 | override suspend fun get(id: Long): DBExampleData? = withContext(dispatcherProvider.io) { 18 | dbExampleDataDao.get(id = id)?.toDBExampleData() 19 | } 20 | 21 | override suspend fun getAll(): List = withContext(dispatcherProvider.io) { 22 | dbExampleDataDao.getAll() 23 | .map { it.toDBExampleData() } 24 | } 25 | 26 | override suspend fun add(data: DBExampleData) = withContext(dispatcherProvider.io) { 27 | dbExampleDataDao.add(data.toDBExampleDataEntity()) 28 | } 29 | 30 | override suspend fun delete(data: DBExampleData) = withContext(dispatcherProvider.io) { 31 | dbExampleDataDao.delete(data.id) 32 | } 33 | 34 | override suspend fun deleteAll() = withContext(dispatcherProvider.io) { 35 | dbExampleDataDao.deleteAll() 36 | } 37 | 38 | override suspend fun closeDB() = withContext(dispatcherProvider.io) { 39 | mainDB.close() 40 | } 41 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/ui/view/CustomCheckboxWithText.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.view 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.foundation.layout.width 7 | import androidx.compose.material3.LocalTextStyle 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.text.TextStyle 13 | import androidx.compose.ui.unit.Dp 14 | import androidx.compose.ui.unit.dp 15 | import org.jetbrains.compose.resources.StringResource 16 | import ru.rznnike.demokmp.app.ui.theme.LocalCustomColorScheme 17 | import ru.rznnike.demokmp.app.utils.onClick 18 | 19 | @Composable 20 | fun CustomCheckboxWithText( 21 | modifier: Modifier = Modifier, 22 | onClick: () -> Unit, 23 | checkboxSize: Dp = 24.dp, 24 | contentPadding: Dp = 8.dp, 25 | textRes: StringResource, 26 | textStyle: TextStyle = LocalTextStyle.current, 27 | textColor: Color = Color.Unspecified, 28 | checked: Boolean, 29 | enabled: Boolean = true 30 | ) = Row( 31 | modifier = modifier.onClick { 32 | if (enabled) onClick() 33 | }, 34 | verticalAlignment = Alignment.CenterVertically 35 | ) { 36 | CustomCheckbox( 37 | modifier = Modifier.size(checkboxSize), 38 | checked = checked, 39 | enabled = enabled 40 | ) 41 | Spacer(Modifier.width(contentPadding)) 42 | TextR( 43 | textRes = textRes, 44 | style = textStyle, 45 | color = if (enabled) textColor else LocalCustomColorScheme.current.disabledText 46 | ) 47 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/viewmodel/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.viewmodel.home 2 | 3 | import androidx.lifecycle.viewModelScope 4 | import kotlinx.coroutines.launch 5 | import org.jetbrains.compose.resources.getString 6 | import org.koin.core.component.inject 7 | import ru.rznnike.demokmp.app.common.viewmodel.BaseViewModel 8 | import ru.rznnike.demokmp.app.dispatcher.event.AppEvent 9 | import ru.rznnike.demokmp.app.dispatcher.event.EventDispatcher 10 | import ru.rznnike.demokmp.app.dispatcher.notifier.Notifier 11 | import ru.rznnike.demokmp.app.error.ErrorHandler 12 | import ru.rznnike.demokmp.app.utils.openLink 13 | import ru.rznnike.demokmp.generated.resources.Res 14 | import ru.rznnike.demokmp.generated.resources.github_repository_link 15 | 16 | class HomeViewModel : BaseViewModel() { 17 | private val notifier: Notifier by inject() 18 | private val errorHandler: ErrorHandler by inject() 19 | private val eventDispatcher: EventDispatcher by inject() 20 | 21 | init { 22 | eventDispatcher.sendEvent( 23 | AppEvent.BottomStatusBarRequested( 24 | show = true 25 | ) 26 | ) 27 | } 28 | 29 | fun restartApp() { 30 | eventDispatcher.sendEvent(AppEvent.RestartRequested) 31 | } 32 | 33 | fun openSourceCodeLink() { 34 | viewModelScope.launch { 35 | try { 36 | openLink(getString(Res.string.github_repository_link)) 37 | } catch (error: Exception) { 38 | onError(error) 39 | } 40 | } 41 | } 42 | 43 | private suspend fun onError(error: Throwable) { 44 | errorHandler.proceed(error) { message -> 45 | notifier.sendAlert(message) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/ui/view/OutlinedRoundedButton.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.view 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.interaction.MutableInteractionSource 5 | import androidx.compose.foundation.layout.PaddingValues 6 | import androidx.compose.foundation.layout.RowScope 7 | import androidx.compose.foundation.shape.RoundedCornerShape 8 | import androidx.compose.material3.ButtonColors 9 | import androidx.compose.material3.ButtonDefaults 10 | import androidx.compose.material3.ButtonElevation 11 | import androidx.compose.material3.OutlinedButton 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Modifier 14 | import ru.rznnike.demokmp.app.ui.theme.LocalCustomColorScheme 15 | import ru.rznnike.demokmp.app.ui.theme.extraSmallCorners 16 | 17 | @Composable 18 | fun OutlinedRoundedButton( 19 | onClick: () -> Unit, 20 | modifier: Modifier = Modifier, 21 | enabled: Boolean = true, 22 | colors: ButtonColors = ButtonDefaults.outlinedButtonColors( 23 | contentColor = LocalCustomColorScheme.current.outlineComponentContent 24 | ), 25 | elevation: ButtonElevation? = null, 26 | border: BorderStroke? = ButtonDefaults.outlinedButtonBorder(enabled), 27 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 28 | interactionSource: MutableInteractionSource? = null, 29 | content: @Composable RowScope.() -> Unit 30 | ) = OutlinedButton( 31 | onClick = onClick, 32 | modifier = modifier, 33 | enabled = enabled, 34 | shape = RoundedCornerShape(extraSmallCorners), 35 | colors = colors, 36 | elevation = elevation, 37 | border = border, 38 | contentPadding = contentPadding, 39 | interactionSource = interactionSource, 40 | content = content 41 | ) -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/ui/view/FilledButton.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.view 2 | 3 | import androidx.compose.foundation.BorderStroke 4 | import androidx.compose.foundation.interaction.MutableInteractionSource 5 | import androidx.compose.foundation.layout.PaddingValues 6 | import androidx.compose.foundation.layout.RowScope 7 | import androidx.compose.foundation.shape.RoundedCornerShape 8 | import androidx.compose.material3.Button 9 | import androidx.compose.material3.ButtonColors 10 | import androidx.compose.material3.ButtonDefaults 11 | import androidx.compose.material3.ButtonElevation 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Modifier 15 | import ru.rznnike.demokmp.app.ui.theme.extraSmallCorners 16 | 17 | @Composable 18 | fun FilledButton( 19 | onClick: () -> Unit, 20 | modifier: Modifier = Modifier, 21 | enabled: Boolean = true, 22 | colors: ButtonColors = ButtonDefaults.buttonColors( 23 | disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant, 24 | disabledContentColor = MaterialTheme.colorScheme.onSurfaceVariant 25 | ), 26 | elevation: ButtonElevation? = ButtonDefaults.buttonElevation(), 27 | border: BorderStroke? = null, 28 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 29 | interactionSource: MutableInteractionSource? = null, 30 | content: @Composable RowScope.() -> Unit 31 | ) = Button( 32 | onClick = onClick, 33 | modifier = modifier, 34 | enabled = enabled, 35 | shape = RoundedCornerShape(extraSmallCorners), 36 | colors = colors, 37 | elevation = elevation, 38 | border = border, 39 | contentPadding = contentPadding, 40 | interactionSource = interactionSource, 41 | content = content 42 | ) -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/ui/view/DropdownSelectorItem.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.ui.view 2 | 3 | import androidx.compose.foundation.border 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.interaction.MutableInteractionSource 6 | import androidx.compose.foundation.interaction.collectIsFocusedAsState 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.material3.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.getValue 13 | import androidx.compose.runtime.remember 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.unit.dp 16 | 17 | @Composable 18 | fun DropdownSelectorItem( 19 | modifier: Modifier = Modifier, 20 | text: String, 21 | onClick: () -> Unit 22 | ) { 23 | val interactionSource = remember { MutableInteractionSource() } 24 | val isFocused by interactionSource.collectIsFocusedAsState() 25 | 26 | Text( 27 | modifier = modifier 28 | .fillMaxWidth() 29 | .clickable( 30 | interactionSource = interactionSource, 31 | indication = null, 32 | onClick = onClick 33 | ) 34 | .run { 35 | if (isFocused) { 36 | border( 37 | width = 1.dp, 38 | color = MaterialTheme.colorScheme.primary, 39 | shape = MaterialTheme.shapes.medium 40 | ) 41 | } else this 42 | } 43 | .padding(horizontal = 16.dp, vertical = 8.dp), 44 | text = text, 45 | style = MaterialTheme.typography.bodyMedium 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/navigation/NavigationUtils.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.navigation 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import ru.rznnike.demokmp.app.ui.screen.chartexample.ChartExampleScreen 5 | import ru.rznnike.demokmp.app.ui.screen.comobjectexample.ComObjectExampleScreen 6 | import ru.rznnike.demokmp.app.ui.screen.customui.CustomUIScreen 7 | import ru.rznnike.demokmp.app.ui.screen.dbexample.DBExampleScreen 8 | import ru.rznnike.demokmp.app.ui.screen.home.HomeScreen 9 | import ru.rznnike.demokmp.app.ui.screen.httpexample.HTTPExampleScreen 10 | import ru.rznnike.demokmp.app.ui.screen.logger.LoggerScreen 11 | import ru.rznnike.demokmp.app.ui.screen.logger.network.NetworkLogDetailsScreen 12 | import ru.rznnike.demokmp.app.ui.screen.navigation.NavigationExampleScreen 13 | import ru.rznnike.demokmp.app.ui.screen.pdfexample.PdfExampleScreen 14 | import ru.rznnike.demokmp.app.ui.screen.settings.NestedSettingsScreen 15 | import ru.rznnike.demokmp.app.ui.screen.settings.SettingsScreen 16 | import ru.rznnike.demokmp.app.ui.screen.splash.SplashScreen 17 | import ru.rznnike.demokmp.app.ui.screen.wsexample.WebSocketsExampleScreen 18 | 19 | actual fun NavGraphBuilder.buildNavGraph() { 20 | addToNavGraph() 21 | addToNavGraph(NetworkLogDetailsScreen.typeMap) 22 | addToNavGraph() 23 | addToNavGraph() 24 | addToNavGraph() 25 | addToNavGraph() 26 | addToNavGraph() 27 | addToNavGraph() 28 | addToNavGraph() 29 | addToNavGraph() 30 | addToNavGraph() 31 | addToNavGraph() 32 | addToNavGraph() 33 | addToNavGraph() 34 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/utils/FileDialogUtils.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import org.apache.pdfbox.Loader 4 | import org.apache.pdfbox.printing.PDFPageable 5 | import ru.rznnike.demokmp.domain.log.Logger 6 | import ru.rznnike.demokmp.domain.model.print.PrintSettings 7 | import ru.rznnike.demokmp.domain.model.print.TwoSidedPrint 8 | import java.awt.print.PrinterException 9 | import java.awt.print.PrinterJob 10 | import java.io.File 11 | import javax.print.attribute.HashPrintRequestAttributeSet 12 | import javax.print.attribute.standard.DialogTypeSelection 13 | import javax.print.attribute.standard.Sides 14 | 15 | fun printDialog( 16 | pdf: File, 17 | printSettings: PrintSettings 18 | ): String { 19 | val printerJob = PrinterJob.getPrinterJob() 20 | 21 | val attributes = HashPrintRequestAttributeSet() 22 | attributes.add( 23 | when (printSettings.twoSidedPrint) { 24 | TwoSidedPrint.DISABLED -> Sides.ONE_SIDED 25 | TwoSidedPrint.TWO_SIDED_LONG_EDGE -> Sides.TWO_SIDED_LONG_EDGE 26 | TwoSidedPrint.TWO_SIDED_SHORT_EDGE -> Sides.TWO_SIDED_SHORT_EDGE 27 | } 28 | ) 29 | attributes.add(DialogTypeSelection.NATIVE) 30 | 31 | val document = Loader.loadPDF(pdf) 32 | printerJob.setPageable(PDFPageable(document)) 33 | 34 | PrinterJob.lookupPrintServices().firstOrNull { it.name == printSettings.printerName }?.let { 35 | printerJob.printService = it 36 | } 37 | 38 | var newPrinterName = printSettings.printerName 39 | if (printerJob.printDialog(attributes)) { 40 | try { 41 | newPrinterName = printerJob.printService.name 42 | printerJob.print(attributes) 43 | } catch (exception: PrinterException) { 44 | Logger.e(exception) 45 | } 46 | } 47 | 48 | return newPrinterName 49 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/gateway/ComObjectExampleGatewayImpl.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.gateway 2 | 3 | import kotlinx.coroutines.withContext 4 | import ru.rznnike.demokmp.app.utils.destroyCOMLibrary 5 | import ru.rznnike.demokmp.app.utils.initCOMLibrary 6 | import ru.rznnike.demokmp.data.shell.ShellManager 7 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 8 | import ru.rznnike.demokmp.domain.gateway.ComObjectExampleGateway 9 | import ru.rznnike.demokmp.domain.utils.OperatingSystem 10 | 11 | class ComObjectExampleGatewayImpl( 12 | private val dispatcherProvider: DispatcherProvider, 13 | private val shellManager: ShellManager 14 | ) : ComObjectExampleGateway { 15 | override suspend fun initShellWrapper(): Unit = withContext(dispatcherProvider.io) { 16 | if (!OperatingSystem.isWindows) return@withContext 17 | 18 | initCOMLibrary() 19 | shellManager.initWrapper() 20 | } 21 | 22 | override suspend fun destroyShellWrapper(): Unit = withContext(dispatcherProvider.io) { 23 | if (!OperatingSystem.isWindows) return@withContext 24 | 25 | shellManager.destroyWrapper() 26 | destroyCOMLibrary() 27 | } 28 | 29 | override suspend fun getPCData(): String = withContext(dispatcherProvider.io) { 30 | if (!OperatingSystem.isWindows) return@withContext "" 31 | 32 | shellManager.getPCData() 33 | } 34 | 35 | override suspend fun openFolderOrFile(path: String): Unit = withContext(dispatcherProvider.io) { 36 | if (!OperatingSystem.isWindows) return@withContext 37 | 38 | shellManager.openFolderOrFile(path) 39 | } 40 | 41 | override suspend fun minimizeAllWindows(): Unit = withContext(dispatcherProvider.io) { 42 | if (!OperatingSystem.isWindows) return@withContext 43 | 44 | shellManager.minimizeAllWindows() 45 | } 46 | } -------------------------------------------------------------------------------- /composeApp/src/desktopMain/kotlin/ru/rznnike/demokmp/app/utils/WindowViewModel.desktop.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.utils 2 | 3 | import androidx.compose.runtime.* 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.ViewModelStore 6 | import androidx.lifecycle.ViewModelStoreOwner 7 | import androidx.lifecycle.viewmodel.CreationExtras 8 | import androidx.lifecycle.viewmodel.compose.viewModel 9 | 10 | val LocalWindowViewModelStoreOwner = staticCompositionLocalOf { null } 11 | 12 | @Composable 13 | actual inline fun windowViewModel( 14 | key: String?, 15 | noinline initializer: CreationExtras.() -> VM 16 | ): VM = viewModel( 17 | viewModelStoreOwner = checkNotNull(LocalWindowViewModelStoreOwner.current) { 18 | "No ViewModelStoreOwner was provided via LocalWindowViewModelStoreOwner" 19 | }, 20 | key = key, 21 | initializer = initializer 22 | ) 23 | 24 | @Composable 25 | actual inline fun windowViewModel(): VM = viewModel( 26 | viewModelStoreOwner = checkNotNull(LocalWindowViewModelStoreOwner.current) { 27 | "No ViewModelStoreOwner was provided via LocalWindowViewModelStoreOwner" 28 | } 29 | ) 30 | 31 | private class WindowViewModelStoreOwner: ViewModelStoreOwner { 32 | override val viewModelStore: ViewModelStore = ViewModelStore() 33 | 34 | fun dispose() = viewModelStore.clear() 35 | } 36 | 37 | @Composable 38 | fun WithWindowViewModelStoreOwner( 39 | content: @Composable () -> Unit 40 | ) { 41 | val viewModelStoreOwner = remember { WindowViewModelStoreOwner() } 42 | DisposableEffect(viewModelStoreOwner) { 43 | onDispose { viewModelStoreOwner.dispose() } 44 | } 45 | 46 | CompositionLocalProvider( 47 | value = LocalWindowViewModelStoreOwner provides viewModelStoreOwner, 48 | content = content 49 | ) 50 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/storage/dao/LogNetworkMessageDao.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.storage.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import kotlinx.coroutines.flow.Flow 8 | import ru.rznnike.demokmp.data.storage.entity.LogNetworkMessageEntity 9 | import java.util.* 10 | 11 | @Dao 12 | interface LogNetworkMessageDao { 13 | @Insert(onConflict = OnConflictStrategy.REPLACE) 14 | suspend fun add(item: LogNetworkMessageEntity) 15 | 16 | @Query("SELECT * FROM LogNetworkMessageEntity WHERE uuid == :uuid") 17 | suspend fun get(uuid: UUID): LogNetworkMessageEntity? 18 | 19 | @Query("SELECT id FROM LogNetworkMessageEntity ORDER BY id DESC LIMIT 1 OFFSET :offset - 1") 20 | suspend fun getNthId(offset: Int): Long? 21 | 22 | @Query("SELECT * FROM LogNetworkMessageEntity WHERE uuid == :uuid") 23 | fun getAsFlow(uuid: UUID): Flow 24 | 25 | @Query("SELECT * FROM LogNetworkMessageEntity") 26 | suspend fun getAll(): List 27 | 28 | @Query("SELECT * FROM LogNetworkMessageEntity WHERE id > :lastId") 29 | suspend fun getNew(lastId: Long): List 30 | 31 | @Query("DELETE FROM LogNetworkMessageEntity WHERE uuid == :uuid") 32 | suspend fun delete(uuid: UUID) 33 | 34 | @Query("DELETE FROM LogNetworkMessageEntity") 35 | suspend fun deleteAll() 36 | 37 | @Query("DELETE FROM LogNetworkMessageEntity WHERE request_timestamp < :borderTimestamp") 38 | suspend fun deleteOldByTimestamp(borderTimestamp: Long) 39 | 40 | @Query("DELETE FROM LogNetworkMessageEntity WHERE id < :borderId") 41 | suspend fun deleteOldById(borderId: Long) 42 | 43 | @Query("DELETE FROM LogNetworkMessageEntity WHERE sessionId < :borderSessionId") 44 | suspend fun deleteOldBySessionId(borderSessionId: Long) 45 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/viewmodel/pdfexample/PdfExampleViewModel.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.viewmodel.pdfexample 2 | 3 | import androidx.lifecycle.viewModelScope 4 | import kotlinx.coroutines.flow.update 5 | import kotlinx.coroutines.launch 6 | import org.koin.core.component.inject 7 | import ru.rznnike.demokmp.app.common.viewmodel.BaseUiViewModel 8 | import ru.rznnike.demokmp.app.error.ErrorHandler 9 | import ru.rznnike.demokmp.data.utils.DataConstants 10 | import ru.rznnike.demokmp.domain.interactor.pdfexample.GetSamplePdfUseCase 11 | import java.io.File 12 | 13 | class PdfExampleViewModel : BaseUiViewModel() { 14 | private val errorHandler: ErrorHandler by inject() 15 | private val getSamplePdfUseCase: GetSamplePdfUseCase by inject() 16 | 17 | init { 18 | loadDocument() 19 | } 20 | 21 | override fun provideDefaultUIState() = UiState() 22 | 23 | private fun loadDocument() { 24 | viewModelScope.launch { 25 | getSamplePdfUseCase().process( 26 | { result -> 27 | mutableUiState.update { currentState -> 28 | currentState.copy( 29 | pdf = result, 30 | isError = false 31 | ) 32 | } 33 | }, { error -> 34 | errorHandler.proceed(error) { 35 | mutableUiState.update { currentState -> 36 | currentState.copy( 37 | isError = true 38 | ) 39 | } 40 | } 41 | } 42 | ) 43 | } 44 | } 45 | 46 | fun getSuggestedSaveFileName() = DataConstants.EXAMPLE_PDF_SAVE_NAME 47 | 48 | data class UiState( 49 | val pdf: File? = null, 50 | val isError: Boolean = false 51 | ) 52 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/data/gateway/AppGatewayImpl.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.data.gateway 2 | 3 | import kotlinx.coroutines.withContext 4 | import okhttp3.internal.closeQuietly 5 | import ru.rznnike.demokmp.BuildKonfig 6 | import ru.rznnike.demokmp.data.utils.DataConstants 7 | import ru.rznnike.demokmp.domain.common.DispatcherProvider 8 | import ru.rznnike.demokmp.domain.gateway.AppGateway 9 | import ru.rznnike.demokmp.domain.log.Logger 10 | import ru.rznnike.demokmp.domain.model.common.LauncherConfig 11 | import ru.rznnike.demokmp.domain.model.common.toLauncherConfig 12 | import ru.rznnike.demokmp.domain.utils.OperatingSystem 13 | import java.io.File 14 | import java.net.ServerSocket 15 | 16 | class AppGatewayImpl( 17 | private val dispatcherProvider: DispatcherProvider 18 | ) : AppGateway { 19 | private var singleInstanceSocket: ServerSocket? = null 20 | 21 | override suspend fun checkIfAppIsAlreadyRunning(): Boolean = withContext(dispatcherProvider.io) { 22 | if (BuildKonfig.RUN_FROM_IDE || OperatingSystem.isAndroid) return@withContext false 23 | 24 | Logger.d("Checking if app is already running") 25 | try { 26 | val port = getLauncherConfig().singleInstancePort 27 | singleInstanceSocket = ServerSocket(port) 28 | false 29 | } catch (_: Exception) { 30 | Logger.e("App is already running!") 31 | true 32 | } 33 | } 34 | 35 | private fun getLauncherConfig(): LauncherConfig { 36 | val configFile = File(DataConstants.LAUNCHER_CONFIGURATION_PATH) 37 | return if (configFile.exists()) { 38 | configFile.readText().toLauncherConfig() 39 | } else LauncherConfig() 40 | } 41 | 42 | override suspend fun closeAppSingleInstanceSocket(): Unit = withContext(dispatcherProvider.io) { 43 | if (BuildKonfig.RUN_FROM_IDE || OperatingSystem.isAndroid) return@withContext 44 | 45 | singleInstanceSocket?.closeQuietly() 46 | } 47 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/ru/rznnike/demokmp/app/dispatcher/event/EventDispatcher.kt: -------------------------------------------------------------------------------- 1 | package ru.rznnike.demokmp.app.dispatcher.event 2 | 3 | import kotlinx.coroutines.launch 4 | import ru.rznnike.demokmp.domain.common.CoroutineScopeProvider 5 | import kotlin.reflect.KClass 6 | 7 | class EventDispatcher( 8 | private val coroutineScopeProvider: CoroutineScopeProvider 9 | ) { 10 | private val eventListeners = mutableMapOf>() 11 | 12 | fun addEventListener(appEventClass: KClass, listener: EventListener) { 13 | val key = appEventClass.java.name 14 | if (eventListeners[key] == null) { 15 | eventListeners[key] = mutableSetOf() 16 | } 17 | 18 | val list = eventListeners[key] 19 | list?.add(listener) 20 | } 21 | 22 | fun addEventListener(appEventClasses: List>, listener: EventListener) = 23 | appEventClasses.forEach { 24 | addEventListener(it, listener) 25 | } 26 | 27 | fun addSingleByTagEventListener(appEventClass: KClass, listener: EventListener) { 28 | val key = appEventClass.java.name 29 | if (eventListeners[key] == null) { 30 | eventListeners[key] = mutableSetOf() 31 | } 32 | 33 | val list = eventListeners[key] 34 | list?.removeAll { it.getTag() == listener.getTag() } 35 | list?.add(listener) 36 | } 37 | 38 | fun removeEventListener(listener: EventListener) = eventListeners 39 | .filter { it.value.size > 0 } 40 | .forEach { it.value.remove(listener) } 41 | 42 | fun sendEvent(appEvent: AppEvent) { 43 | val key = appEvent::class.java.name 44 | eventListeners[key]?.forEach { listener -> 45 | coroutineScopeProvider.ui.launch { 46 | listener.onEvent(appEvent) 47 | } 48 | } 49 | } 50 | 51 | interface EventListener { 52 | fun onEvent(event: AppEvent) 53 | 54 | fun getTag(): String = this::class.java.name 55 | } 56 | } --------------------------------------------------------------------------------