├── 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 |
10 |
11 |
12 |
13 |
16 |
21 |
22 |
23 | true
24 | true
25 | false
26 | false
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.run/archive [staging].run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | true
24 | true
25 | false
26 | false
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.run/run [debug].run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | true
25 | true
26 | false
27 | false
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.run/run [staging].run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
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 | }
--------------------------------------------------------------------------------