├── scripts
├── tools
│ └── gentool.jar
└── gen.sh
├── examples
├── exampleIOS
│ ├── Configuration
│ │ └── Config.xcconfig
│ ├── iosApp
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── app-icon-1024.png
│ │ │ │ └── Contents.json
│ │ │ └── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ ├── Preview Content
│ │ │ └── Preview Assets.xcassets
│ │ │ │ └── Contents.json
│ │ ├── iOSApp.swift
│ │ ├── ContentView.swift
│ │ └── Info.plist
│ └── iosApp.xcodeproj
│ │ ├── project.xcworkspace
│ │ ├── xcuserdata
│ │ │ └── sayem.xcuserdatad
│ │ │ │ └── UserInterfaceState.xcuserstate
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ └── sayem.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── exampleApp
│ ├── src
│ │ └── androidMain
│ │ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ └── creds.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ ├── ic_launcher_round.webp
│ │ │ │ └── ic_launcher_foreground.webp
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── ic_launcher-playstore.png
│ │ │ └── AndroidManifest.xml
│ ├── proguard-rules.pro
│ └── build.gradle.kts
└── shared
│ ├── example
│ ├── routes
│ │ └── example.route.kt
│ ├── theme
│ │ ├── shape.example.kt
│ │ ├── theme.example.kt
│ │ └── typography.example.kt
│ ├── di
│ │ └── koin.example.kt
│ └── screens
│ │ ├── screen.def.kt
│ │ └── home.screen.kt
│ ├── exampleIOS
│ ├── main.example.kt
│ └── theme
│ │ └── theme.example.kt
│ └── exampleAndroid
│ ├── theme
│ └── theme.example.kt
│ └── main.example.kt
├── exampleIOS
├── Configuration
│ └── Config.xcconfig
└── iosApp
│ ├── Assets.xcassets
│ ├── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── app-icon-1024.png
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
│ ├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
│ ├── iOSApp.swift
│ ├── ContentView.swift
│ └── Info.plist
├── exampleApp
├── src
│ └── androidMain
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── ic_launcher_background.xml
│ │ │ └── creds.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_round.webp
│ │ │ └── ic_launcher_foreground.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_round.webp
│ │ │ └── ic_launcher_foreground.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_round.webp
│ │ │ └── ic_launcher_foreground.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_round.webp
│ │ │ └── ic_launcher_foreground.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ ├── ic_launcher_round.webp
│ │ │ └── ic_launcher_foreground.webp
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ └── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── ic_launcher-playstore.png
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── library
├── src
│ ├── commonMain
│ │ └── kotlin
│ │ │ ├── utils
│ │ │ ├── expected
│ │ │ │ ├── date.util.kt
│ │ │ │ ├── datastore.koin-module.kt
│ │ │ │ ├── crash-analytics.kt
│ │ │ │ └── platform.util.kt
│ │ │ ├── files.kt
│ │ │ ├── text.util.kt
│ │ │ ├── datastore.kt
│ │ │ ├── utility.kt
│ │ │ ├── logger.kt
│ │ │ ├── time.kt
│ │ │ └── http-utils.kt
│ │ │ ├── filters
│ │ │ └── filters.kt
│ │ │ ├── data
│ │ │ ├── types
│ │ │ │ ├── states.kt
│ │ │ │ ├── types.kt
│ │ │ │ └── Errors.kt
│ │ │ ├── pagination.kt
│ │ │ └── validation
│ │ │ │ └── validation.kt
│ │ │ ├── modules
│ │ │ └── common
│ │ │ │ ├── koin.lib.kt
│ │ │ │ ├── models
│ │ │ │ └── error-responses.kt
│ │ │ │ └── features
│ │ │ │ └── auth
│ │ │ │ └── models
│ │ │ │ └── auth.model.kt
│ │ │ └── configs
│ │ │ ├── web-client.conf.kt
│ │ │ └── websocket.client.kt
│ ├── jvmMain
│ │ └── kotlin
│ │ │ ├── utils
│ │ │ └── expected
│ │ │ │ ├── date.util.kt
│ │ │ │ ├── platform.util.kt
│ │ │ │ ├── crash-analytics.kt
│ │ │ │ └── datastore.koin-module.kt
│ │ │ └── configs
│ │ │ └── web-client.conf.jvm.kt
│ ├── androidMain
│ │ └── kotlin
│ │ │ ├── utils
│ │ │ └── expected
│ │ │ │ ├── date.util.kt
│ │ │ │ ├── datastore.koin-module.kt
│ │ │ │ ├── crash-analytics.kt
│ │ │ │ └── platform.util.kt
│ │ │ └── configs
│ │ │ └── webclient.conf.kt
│ ├── linuxX64Main
│ │ └── kotlin
│ │ │ ├── utils
│ │ │ └── expected
│ │ │ │ ├── date.util.kt
│ │ │ │ ├── platform.util.kt
│ │ │ │ └── datastore.koin-module.kt
│ │ │ └── configs
│ │ │ └── web.client.linux.kt
│ └── iosMain
│ │ └── kotlin
│ │ ├── utils
│ │ └── expected
│ │ │ ├── date.util.kt
│ │ │ ├── platform.util.kt
│ │ │ ├── crash-analytics.kt
│ │ │ └── datastore.koin-module.kt
│ │ └── configs
│ │ └── web-client.conf.kt
└── build.gradle.kts
├── shared
├── src
│ ├── commonMain
│ │ ├── kotlin
│ │ │ ├── configs
│ │ │ │ ├── app-config.kt
│ │ │ │ └── Credentials.kt
│ │ │ ├── modules
│ │ │ │ ├── common
│ │ │ │ │ ├── views
│ │ │ │ │ │ ├── screens
│ │ │ │ │ │ │ └── screens.kt
│ │ │ │ │ │ ├── components
│ │ │ │ │ │ │ ├── expected
│ │ │ │ │ │ │ │ ├── sharable.kt
│ │ │ │ │ │ │ │ └── images.kt
│ │ │ │ │ │ │ ├── resource-components.kt
│ │ │ │ │ │ │ ├── charts.kt
│ │ │ │ │ │ │ ├── header.kt
│ │ │ │ │ │ │ ├── search.view.kt
│ │ │ │ │ │ │ └── tag-scroll.view.kt
│ │ │ │ │ │ ├── dimensions
│ │ │ │ │ │ │ └── size.kt
│ │ │ │ │ │ └── layouts
│ │ │ │ │ │ │ └── generic-layout.kt
│ │ │ │ │ ├── base
│ │ │ │ │ │ ├── ws.kt
│ │ │ │ │ │ ├── ViewModel.kt
│ │ │ │ │ │ └── BaseResponse.kt
│ │ │ │ │ ├── ads
│ │ │ │ │ │ └── ads.kt
│ │ │ │ │ ├── features
│ │ │ │ │ │ ├── preferences
│ │ │ │ │ │ │ ├── pref.model.kt
│ │ │ │ │ │ │ └── pref.vm.kt
│ │ │ │ │ │ ├── auth
│ │ │ │ │ │ │ ├── models
│ │ │ │ │ │ │ │ └── user.model.kt
│ │ │ │ │ │ │ ├── auth.kt
│ │ │ │ │ │ │ ├── AuthService.kt
│ │ │ │ │ │ │ └── AuthRepository.kt
│ │ │ │ │ │ ├── notifications
│ │ │ │ │ │ │ └── models
│ │ │ │ │ │ │ │ └── notifications.kt
│ │ │ │ │ │ └── events
│ │ │ │ │ │ │ └── models
│ │ │ │ │ │ │ └── event.model.kt
│ │ │ │ │ ├── animations
│ │ │ │ │ │ ├── transition-animations.kt
│ │ │ │ │ │ └── transitions.kt
│ │ │ │ │ ├── routes
│ │ │ │ │ │ └── route.kt
│ │ │ │ │ └── di
│ │ │ │ │ │ └── koin.common.kt
│ │ │ │ └── exampleModule
│ │ │ │ │ ├── routes
│ │ │ │ │ └── example.route.kt
│ │ │ │ │ ├── theme
│ │ │ │ │ ├── shape.example.kt
│ │ │ │ │ ├── theme.example.kt
│ │ │ │ │ └── typography.example.kt
│ │ │ │ │ ├── features
│ │ │ │ │ └── todo
│ │ │ │ │ │ ├── dto
│ │ │ │ │ │ └── todo.response.kt
│ │ │ │ │ │ ├── todo.service.kt
│ │ │ │ │ │ ├── todo.vm.kt
│ │ │ │ │ │ └── todo.repository.kt
│ │ │ │ │ ├── screens
│ │ │ │ │ ├── screen.def.kt
│ │ │ │ │ └── home.screen.kt
│ │ │ │ │ └── di
│ │ │ │ │ └── koin.example.kt
│ │ │ └── utils
│ │ │ │ ├── expected
│ │ │ │ ├── ui-utility.kt
│ │ │ │ ├── toast.kt
│ │ │ │ └── analytics.kt
│ │ │ │ └── ui-utils.kt
│ │ └── composeResources
│ │ │ ├── font
│ │ │ ├── solaimanlipi.ttf
│ │ │ ├── charu_chandan_bold.ttf
│ │ │ ├── charu_chandan_light.ttf
│ │ │ └── charu_chandan_regular.ttf
│ │ │ ├── values
│ │ │ ├── preferences.xml
│ │ │ ├── strings.xml
│ │ │ └── cred.xml
│ │ │ ├── values-bn
│ │ │ └── strings.xml
│ │ │ └── drawable
│ │ │ ├── box.xml
│ │ │ └── compose-multiplatform.xml
│ ├── iosMain
│ │ └── kotlin
│ │ │ ├── utils
│ │ │ └── expected
│ │ │ │ ├── analytics.ios.kt
│ │ │ │ ├── toast.ios.kt
│ │ │ │ └── ui-utility.ios.kt
│ │ │ └── modules
│ │ │ ├── exampleModule
│ │ │ ├── main.example.kt
│ │ │ └── theme
│ │ │ │ └── theme.example.kt
│ │ │ └── common
│ │ │ ├── base
│ │ │ └── ViewModel.kt
│ │ │ ├── ads
│ │ │ └── ads.ios.kt
│ │ │ └── views
│ │ │ └── components
│ │ │ └── expected
│ │ │ ├── sharable.kt
│ │ │ └── images.kt
│ └── androidMain
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ ├── modules
│ │ ├── common
│ │ │ ├── base
│ │ │ │ └── ViewModel.kt
│ │ │ ├── views
│ │ │ │ └── components
│ │ │ │ │ └── expected
│ │ │ │ │ ├── sharable.kt
│ │ │ │ │ └── images.kt
│ │ │ └── ads
│ │ │ │ └── ads.android.kt
│ │ └── exampleModule
│ │ │ ├── theme
│ │ │ └── theme.example.kt
│ │ │ └── main.example.kt
│ │ └── utils
│ │ └── expected
│ │ ├── analytics.android.kt
│ │ ├── ui-utility.android.kt
│ │ └── toast.android.kt
├── shared.podspec
└── build.gradle.kts
├── cleanup.sh
├── .gitignore
├── gradle.properties
├── settings.gradle.kts
├── README.md
└── gradlew.bat
/scripts/tools/gentool.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/scripts/tools/gentool.jar
--------------------------------------------------------------------------------
/examples/exampleIOS/Configuration/Config.xcconfig:
--------------------------------------------------------------------------------
1 | TEAM_ID=
2 | BUNDLE_ID=org.noksha.exampleapp
3 | APP_NAME=ExampleApp
4 |
--------------------------------------------------------------------------------
/exampleIOS/Configuration/Config.xcconfig:
--------------------------------------------------------------------------------
1 | TEAM_ID=
2 | BUNDLE_ID=com.myapplication.MyApplication
3 | APP_NAME=My application
4 |
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ExampleApp
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/exampleIOS/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ExampleApp
3 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/expected/date.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | expect fun getDefaultDateInMillis(): Long
4 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/configs/app-config.kt:
--------------------------------------------------------------------------------
1 | package configs
2 |
3 | enum class AppThemes {
4 | DEFAULT, DARK, LIGHT
5 | }
6 |
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/exampleIOS/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/library/src/jvmMain/kotlin/utils/expected/date.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 | actual fun getDefaultDateInMillis(): Long = System.currentTimeMillis()
3 |
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
--------------------------------------------------------------------------------
/library/src/androidMain/kotlin/utils/expected/date.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 | actual fun getDefaultDateInMillis(): Long = System.currentTimeMillis()
3 |
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/expected/datastore.koin-module.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import org.koin.core.module.Module
4 |
5 | expect val datastoreModule: Module
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/font/solaimanlipi.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/shared/src/commonMain/composeResources/font/solaimanlipi.ttf
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4285F4
4 |
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/font/charu_chandan_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/shared/src/commonMain/composeResources/font/charu_chandan_bold.ttf
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/views/screens/screens.kt:
--------------------------------------------------------------------------------
1 | package modules.common.views.screens
2 |
3 | import cafe.adriel.voyager.core.screen.Screen
4 |
5 | interface AppScreen : Screen
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/values/creds.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ca-app-pub-3940256099942544~3347511713
4 |
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/library/src/linuxX64Main/kotlin/utils/expected/date.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import arrow.fx.coroutines.timeInMillis
4 |
5 | actual fun getDefaultDateInMillis(): Long = timeInMillis()
6 |
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/font/charu_chandan_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/shared/src/commonMain/composeResources/font/charu_chandan_light.ttf
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/font/charu_chandan_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/shared/src/commonMain/composeResources/font/charu_chandan_regular.ttf
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/base/ws.kt:
--------------------------------------------------------------------------------
1 | package modules.common.base
2 |
3 | interface WSMessage {
4 | val username: String?
5 | val message: String
6 | val data: Map
7 | }
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/exampleIOS/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleIOS/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4285F4
4 |
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/values/creds.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ca-app-pub-3940256099942544~3347511713
4 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/utils/expected/analytics.ios.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import utils.Tag
4 |
5 | actual object Analytics {
6 | actual fun log(event: Tag.Event, data: Data) {}
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/exampleApp/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Suppress warnings for missing classes
2 | -dontwarn java.net.http.HttpResponse
3 | -dontwarn java.net.http.WebSocketHandshakeException
4 | -dontwarn org.slf4j.impl.StaticLoggerBinder
5 |
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleIOS/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/filters/filters.kt:
--------------------------------------------------------------------------------
1 | package filters
2 |
3 | import io.ktor.client.statement.HttpResponse
4 |
5 | interface HttpFilter {
6 | suspend fun apply(response: HttpResponse): HttpResponse
7 | }
8 |
--------------------------------------------------------------------------------
/library/src/iosMain/kotlin/utils/expected/date.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import platform.QuartzCore.CACurrentMediaTime
4 |
5 | actual fun getDefaultDateInMillis(): Long = CACurrentMediaTime().toLong()
6 |
--------------------------------------------------------------------------------
/examples/exampleApp/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Suppress warnings for missing classes
2 | -dontwarn java.net.http.HttpResponse
3 | -dontwarn java.net.http.WebSocketHandshakeException
4 | -dontwarn org.slf4j.impl.StaticLoggerBinder
5 |
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/examples/shared/example/routes/example.route.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.routes
2 |
3 | object ExampleRoutes {
4 | object Todo{
5 | const val GET_TODO_ITEMS = "https://jsonplaceholder.typicode.com/todos"
6 | }
7 | }
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/expected/crash-analytics.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | expect class CrashAnalytics {
4 | fun init(dsn: String)
5 |
6 | companion object {
7 | fun capture(throwable: Throwable)
8 | }
9 | }
--------------------------------------------------------------------------------
/exampleIOS/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/values/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Profile
4 | user-name
5 |
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/routes/example.route.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.routes
2 |
3 | object ExampleRoutes {
4 | object Todo{
5 | const val GET_TODO_ITEMS = "https://jsonplaceholder.typicode.com/todos"
6 | }
7 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/utils/expected/ui-utility.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | expect class RemoteUrl() {
6 | @Composable
7 | fun create()
8 | fun open(url: String)
9 | }
10 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/base/ViewModel.kt:
--------------------------------------------------------------------------------
1 | package modules.common.base
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 |
5 | expect abstract class ViewModel() {
6 | val viewModelScope: CoroutineScope
7 | protected open fun onCleared()
8 | }
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/expected/platform.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | expect fun uuid(): String
4 |
5 | expect val isDebug: Boolean
6 |
7 | enum class Platforms{
8 | ANDROID, IOS, JVM;
9 | }
10 |
11 | expect fun platform(): Platforms
12 |
--------------------------------------------------------------------------------
/cleanup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | rm -rf .idea
3 | ./gradlew clean
4 | rm -rf .gradle
5 | rm -rf build
6 | rm -rf */build
7 | rm -rf iosApp/iosApp.xcworkspace
8 | rm -rf iosApp/Pods
9 | rm -rf iosApp/iosApp.xcodeproj/project.xcworkspace
10 | rm -rf iosApp/iosApp.xcodeproj/xcuserdata
--------------------------------------------------------------------------------
/library/src/jvmMain/kotlin/utils/expected/platform.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import java.util.UUID
4 |
5 | actual fun uuid(): String = UUID.randomUUID().toString()
6 |
7 | actual val isDebug = true
8 |
9 | actual fun platform(): Platforms = Platforms.JVM
10 |
--------------------------------------------------------------------------------
/exampleIOS/iosApp/iOSApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import shared
3 |
4 | @main
5 | struct iOSApp: App {
6 |
7 | init() {
8 | Koin_exampleKt.doInitKoin()
9 | }
10 |
11 | var body: some Scene {
12 | WindowGroup {
13 | ContentView()
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp.xcodeproj/project.xcworkspace/xcuserdata/sayem.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sayemoid/kmm-booster-template/HEAD/examples/exampleIOS/iosApp.xcodeproj/project.xcworkspace/xcuserdata/sayem.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Aug 12 15:26:48 BDT 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp/iOSApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import shared
3 |
4 | @main
5 | struct iOSApp: App {
6 |
7 | init() {
8 | Koin_exampleKt.doInitKoin()
9 | }
10 |
11 | var body: some Scene {
12 | WindowGroup {
13 | ContentView()
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/ads/ads.kt:
--------------------------------------------------------------------------------
1 | package modules.common.ads
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | expect class Ads {
6 | fun load(adUnitId: String)
7 |
8 | fun show()
9 | }
10 |
11 | @Composable
12 | expect fun interstitialAd(adUnitId: String): Ads
13 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/files.kt:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | fun ByteArray.resizeTo(newSize: Int): ByteArray {
4 | if (newSize > this.size) return this
5 | val newByteArray = ByteArray(newSize)
6 | for (i in 0 until newSize) {
7 | newByteArray[i] = this[i]
8 | }
9 | return newByteArray
10 | }
--------------------------------------------------------------------------------
/library/src/iosMain/kotlin/configs/web-client.conf.kt:
--------------------------------------------------------------------------------
1 | package configs
2 |
3 | import io.ktor.client.engine.HttpClientEngineConfig
4 | import io.ktor.client.engine.HttpClientEngineFactory
5 | import io.ktor.client.engine.darwin.Darwin
6 |
7 | actual fun getEngine(): HttpClientEngineFactory = Darwin
--------------------------------------------------------------------------------
/library/src/linuxX64Main/kotlin/configs/web.client.linux.kt:
--------------------------------------------------------------------------------
1 | package configs
2 |
3 | import io.ktor.client.engine.HttpClientEngineConfig
4 | import io.ktor.client.engine.HttpClientEngineFactory
5 | import io.ktor.client.engine.cio.CIO
6 |
7 | actual fun getEngine(): HttpClientEngineFactory = CIO
--------------------------------------------------------------------------------
/library/src/androidMain/kotlin/configs/webclient.conf.kt:
--------------------------------------------------------------------------------
1 | package configs
2 |
3 | import io.ktor.client.engine.HttpClientEngineConfig
4 | import io.ktor.client.engine.HttpClientEngineFactory
5 | import io.ktor.client.engine.okhttp.OkHttp
6 |
7 | actual fun getEngine(): HttpClientEngineFactory = OkHttp
--------------------------------------------------------------------------------
/library/src/jvmMain/kotlin/configs/web-client.conf.jvm.kt:
--------------------------------------------------------------------------------
1 | package configs
2 |
3 | import io.ktor.client.engine.HttpClientEngineConfig
4 | import io.ktor.client.engine.HttpClientEngineFactory
5 | import io.ktor.client.engine.okhttp.OkHttp
6 |
7 | actual fun getEngine(): HttpClientEngineFactory = OkHttp
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/views/components/expected/sharable.kt:
--------------------------------------------------------------------------------
1 | package modules.common.views.components.expected
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | expect class ShareData {
6 | fun shareContent(text: String)
7 | }
8 |
9 | @Composable
10 | expect fun createShareSheet(): ShareData
--------------------------------------------------------------------------------
/examples/shared/exampleIOS/main.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule
2 |
3 | import androidx.compose.ui.window.ComposeUIViewController
4 | import cafe.adriel.voyager.navigator.Navigator
5 | import modules.exampleModule.screens.MainScreen
6 |
7 | fun MainViewController() = ComposeUIViewController {
8 | Navigator(MainScreen)
9 | }
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/utils/expected/toast.ios.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | actual class Toasty {
4 | actual companion object {
5 | actual fun show() = Unit
6 | actual fun with(
7 | context: Any,
8 | resId: Int,
9 | duration: Duration
10 | ): Companion {
11 | TODO("Not yet implemented")
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/exampleIOS/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "app-icon-1024.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/modules/exampleModule/main.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule
2 |
3 | import androidx.compose.ui.window.ComposeUIViewController
4 | import cafe.adriel.voyager.navigator.Navigator
5 | import modules.exampleModule.screens.MainScreen
6 |
7 | fun MainViewController() = ComposeUIViewController {
8 | Navigator(MainScreen)
9 | }
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "app-icon-1024.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/shared/example/theme/shape.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.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 shapes = Shapes(
8 | extraSmall = RoundedCornerShape(4.dp),
9 | small = RoundedCornerShape(12.dp),
10 | )
11 |
--------------------------------------------------------------------------------
/examples/shared/example/theme/theme.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | expect fun ExampleAppTheme(
8 | useDarkTheme: Boolean = isSystemInDarkTheme(),
9 | dynamicColorScheme: Boolean = false,
10 | content: @Composable () -> Unit
11 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/theme/shape.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.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 shapes = Shapes(
8 | extraSmall = RoundedCornerShape(4.dp),
9 | small = RoundedCornerShape(12.dp),
10 | )
11 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/text.util.kt:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | fun htmlToPlainText(html: String): String {
4 | var text = ""
5 | var inTag = false
6 |
7 | for (char in html) {
8 | if (char == '<') {
9 | inTag = true
10 | } else if (char == '>') {
11 | inTag = false
12 | } else if (!inTag) {
13 | text += char
14 | }
15 | }
16 | return text.trim()
17 | }
18 |
--------------------------------------------------------------------------------
/library/src/iosMain/kotlin/utils/expected/platform.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import platform.Foundation.NSUUID
4 | import kotlin.experimental.ExperimentalNativeApi
5 |
6 | actual fun uuid(): String = NSUUID().UUIDString()
7 |
8 | @OptIn(ExperimentalNativeApi::class)
9 | actual val isDebug = Platform.isDebugBinary
10 |
11 | actual fun platform(): Platforms = Platforms.IOS
12 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/modules/common/base/ViewModel.kt:
--------------------------------------------------------------------------------
1 | package modules.common.base
2 |
3 | import kotlinx.coroutines.MainScope
4 | import kotlinx.coroutines.cancel
5 |
6 | actual abstract class ViewModel {
7 | actual val viewModelScope = MainScope()
8 |
9 | protected actual open fun onCleared() {}
10 |
11 | fun clear() {
12 | onCleared()
13 | viewModelScope.cancel()
14 | }
15 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/theme/theme.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | expect fun ExampleAppTheme(
8 | useDarkTheme: Boolean = isSystemInDarkTheme(),
9 | dynamicColorScheme: Boolean = false,
10 | content: @Composable () -> Unit
11 | )
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | build/
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | /.kotlin
11 |
12 |
13 | exampleIOS/iosApp.xcworkspace/*
14 | exampleIOS/iosApp.xcodeproj/*
15 | !exampleIOS/iosApp.xcodeproj/project.pbxproj
16 |
17 | iosQuizBox/iosApp.xcworkspace/*
18 | iosQuizBox/iosApp.xcodeproj/*
19 | !iosQuizBox/iosApp.xcodeproj/project.pbxproj
20 |
21 |
--------------------------------------------------------------------------------
/library/src/iosMain/kotlin/utils/expected/crash-analytics.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import utils.Tag
4 | import utils.logD
5 |
6 | actual class CrashAnalytics(private val debug: Boolean) {
7 | actual fun init(dsn: String) {
8 | logD(Tag.Crash, "Sentry initialization for IOS (Not yet implemented)!")
9 | }
10 |
11 | actual companion object {
12 | actual fun capture(throwable: Throwable) {}
13 | }
14 | }
--------------------------------------------------------------------------------
/library/src/jvmMain/kotlin/utils/expected/crash-analytics.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import utils.Tag
4 | import utils.logD
5 |
6 | actual class CrashAnalytics(private val debug: Boolean) {
7 | actual fun init(dsn: String) {
8 | logD(Tag.Crash, "Sentry initialization for IOS (Not yet implemented)!")
9 | }
10 |
11 | actual companion object {
12 | actual fun capture(throwable: Throwable) {}
13 | }
14 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/features/todo/dto/todo.response.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.features.todo.dto
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class TodoResponse(
8 | val id: Long,
9 |
10 | @SerialName("userId")
11 | val userId: Long,
12 |
13 | val title: String,
14 |
15 | val completed: Boolean
16 | )
--------------------------------------------------------------------------------
/examples/shared/example/di/koin.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.di
2 |
3 | import modules.common.di.commonModule
4 | import org.koin.core.context.startKoin
5 | import org.koin.dsl.module
6 |
7 | val exampleModule = module {
8 |
9 | /*
10 | Register koin components here
11 | */
12 |
13 | }
14 |
15 | val koinExampleModules = commonModule + exampleModule
16 |
17 | fun initKoin() = startKoin { modules(koinExampleModules) }
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/modules/common/ads/ads.ios.kt:
--------------------------------------------------------------------------------
1 | package modules.common.ads
2 |
3 | import androidx.compose.runtime.Composable
4 | import modules.common.ads.Ads
5 |
6 |
7 | actual class Ads {
8 |
9 |
10 | actual fun load(adUnitId: String) {
11 | }
12 |
13 | actual fun show() {
14 | }
15 | }
16 |
17 | @Composable
18 | actual fun interstitialAd(adUnitId: String): Ads {
19 | val ad = Ads()
20 | ad.load(adUnitId)
21 | return ad
22 | }
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/utils/expected/ui-utility.ios.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import androidx.compose.runtime.Composable
4 | import platform.Foundation.NSURL
5 | import platform.UIKit.UIApplication
6 |
7 | actual class RemoteUrl actual constructor() {
8 |
9 | @Composable
10 | actual fun create() {
11 | }
12 |
13 | actual fun open(url: String) {
14 | NSURL.URLWithString(url)?.let {
15 | UIApplication.sharedApplication.openURL(it)
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp.xcodeproj/xcuserdata/sayem.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | iosApp.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/values-bn/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | প্রস্তুতি
4 |
5 |
6 | হোম
7 | ভোটের সারাংশ
8 | লগইন
9 | সাইন আপ
10 | প্রোফাইল
11 |
12 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/base/BaseResponse.kt:
--------------------------------------------------------------------------------
1 | package modules.common.base
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | open class BaseReq {
8 | val id: Long? = null
9 | }
10 |
11 | @Serializable
12 | open class BaseResponse {
13 | val id: Long = 0
14 |
15 | @SerialName("created_at")
16 | lateinit var createdAt: String
17 |
18 | @SerialName("updated_at")
19 | var updatedAt: String? = null
20 | }
21 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/modules/common/base/ViewModel.kt:
--------------------------------------------------------------------------------
1 | package modules.common.base
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import androidx.lifecycle.ViewModel as AndroidXViewModel
5 | import androidx.lifecycle.viewModelScope as androidXViewModelScope
6 |
7 | actual abstract class ViewModel actual constructor() : AndroidXViewModel() {
8 | actual val viewModelScope: CoroutineScope = androidXViewModelScope
9 |
10 | actual override fun onCleared() {
11 | super.onCleared()
12 | }
13 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/utils/expected/toast.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | sealed interface Duration {
4 | data object Short : Duration
5 | data object Medium : Duration
6 | data object Long : Duration
7 |
8 | fun millisFromDuration(duration: Duration): Int =
9 | when (duration) {
10 | Short -> 500
11 | Medium -> 1000
12 | Long -> 2000
13 | }
14 | }
15 |
16 | expect class Toasty {
17 | companion object {
18 | fun with(context: Any, resId: Int, duration: Duration): Companion
19 | fun show()
20 |
21 | }
22 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Example
4 |
5 |
6 | Home
7 | Home
8 | Poll Summary
9 | Login
10 | Signup
11 | Profile
12 |
13 |
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/values/cred.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | https://8d1741cf46ba474d9539eac057269f51@o4505135996272640.ingest.sentry.io/4505135998828544
5 |
6 | ca-app-pub-3940256099942544/1033173712
7 | ca-app-pub-7944444189407778/1316428355
8 |
9 | client_id
10 | client_secret
11 |
12 |
--------------------------------------------------------------------------------
/examples/shared/exampleIOS/theme/theme.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.theme
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | actual fun ExampleAppTheme(
8 | useDarkTheme: Boolean,
9 | dynamicColorScheme: Boolean,
10 | content: @Composable () -> Unit
11 | ) {
12 | val colors = when {
13 | useDarkTheme -> darkColors
14 | else -> lightColors
15 | }
16 |
17 | MaterialTheme(
18 | colorScheme = colors,
19 | shapes = shapes,
20 | typography = typography,
21 | content = content,
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/data/types/states.kt:
--------------------------------------------------------------------------------
1 | package data.types
2 |
3 | import arrow.core.Either
4 | import data.responses.ErrMessage
5 |
6 | sealed interface State {
7 | data object Init : State
8 | data object Loading : State
9 | data class Result(val result: Either) : State
10 | }
11 |
12 | sealed interface SignUpStates {
13 | data object Init : SignUpStates
14 | data object Loading : SignUpStates
15 | data class Acknowledgement(val result: Either) : SignUpStates
16 | data class Result(val result: Either) : SignUpStates
17 | }
--------------------------------------------------------------------------------
/exampleIOS/iosApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import shared
4 |
5 | struct ComposeView: UIViewControllerRepresentable {
6 | func makeUIViewController(context: Context) -> UIViewController {
7 | Main_exampleKt.MainViewController()
8 | }
9 |
10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
11 | }
12 |
13 | struct ContentView: View {
14 | var body: some View {
15 | ComposeView()
16 | .ignoresSafeArea(.all, edges: .bottom) // Compose has own keyboard handler
17 | }
18 | }
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/datastore.kt:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory
5 | import androidx.datastore.preferences.core.Preferences
6 | import okio.Path.Companion.toPath
7 |
8 | fun createDataStore(
9 | producePath: () -> String,
10 | ): DataStore = PreferenceDataStoreFactory.createWithPath(
11 | corruptionHandler = null,
12 | migrations = emptyList(),
13 | produceFile = { producePath().toPath() },
14 | )
15 |
16 | internal const val dataStoreFileName = "meetings.preferences_pb"
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp/ContentView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftUI
3 | import shared
4 |
5 | struct ComposeView: UIViewControllerRepresentable {
6 | func makeUIViewController(context: Context) -> UIViewController {
7 | Main_exampleKt.MainViewController()
8 | }
9 |
10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
11 | }
12 |
13 | struct ContentView: View {
14 | var body: some View {
15 | ComposeView()
16 | .ignoresSafeArea(.all, edges: .bottom) // Compose has own keyboard handler
17 | }
18 | }
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/modules/exampleModule/theme/theme.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.theme
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | actual fun ExampleAppTheme(
8 | useDarkTheme: Boolean,
9 | dynamicColorScheme: Boolean,
10 | content: @Composable () -> Unit
11 | ) {
12 | val colors = when {
13 | useDarkTheme -> darkColors
14 | else -> lightColors
15 | }
16 |
17 | MaterialTheme(
18 | colorScheme = colors,
19 | shapes = shapes,
20 | typography = typography,
21 | content = content,
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #Gradle
2 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
3 |
4 | #Kotlin
5 | kotlin.code.style=official
6 |
7 | #MPP
8 | kotlin.mpp.stability.nowarn=true
9 | kotlin.mpp.enableCInteropCommonization=true
10 | kotlin.mpp.androidSourceSetLayoutVersion=2
11 |
12 | #Compose
13 | org.jetbrains.compose.experimental.uikit.enabled=true
14 |
15 | #Android
16 | android.useAndroidX=true
17 | android.targetSdk=34
18 | android.compileSdk=34
19 | android.minSdk=24
20 |
21 | #Versions
22 | kotlin.compiler.extensionVersion=1.5.3
23 | kotlin.version=1.9.10
24 | agp.version=8.0.2
25 | compose.version=1.5.3
--------------------------------------------------------------------------------
/library/src/androidMain/kotlin/utils/expected/datastore.koin-module.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import android.content.Context
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.preferences.core.Preferences
6 | import org.koin.core.module.Module
7 | import org.koin.dsl.module
8 | import utils.createDataStore
9 | import utils.dataStoreFileName
10 |
11 |
12 | actual val datastoreModule: Module = module {
13 | single { dataStore(get())}
14 | }
15 |
16 | fun dataStore(context: Context): DataStore =
17 | createDataStore(
18 | producePath = { context.filesDir.resolve(dataStoreFileName).absolutePath }
19 | )
20 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/utility.kt:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import kotlinx.datetime.Instant
4 |
5 | fun List.qPop(): List =
6 | if (this.isEmpty()) this
7 | else this.subList(1, lastIndex + 1)
8 |
9 | fun List.qPush(element: T): List = this + listOf(element)
10 |
11 | fun List.qPeek(): T? = this.firstOrNull()
12 |
13 | fun Map.toParamString() =
14 | if (this.isEmpty()) ""
15 | else {
16 | this.map {
17 | val value = when (val p = it.value) {
18 | is Instant -> p.toString()
19 | null -> ""
20 | else -> p.toString()
21 | }
22 | "${it.key}=${value}"
23 | }.joinToString("&")
24 | }
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/data/types/types.kt:
--------------------------------------------------------------------------------
1 | package data.types
2 |
3 | import arrow.core.Either
4 | import arrow.core.Option
5 | import arrow.core.left
6 | import data.Page
7 | import data.responses.ErrMessage
8 | import data.responses.toMessage
9 |
10 | typealias RemoteListData = Option>>
11 | typealias RemoteDataPaginated = Option>>
12 | typealias RemoteData = Option>
13 |
14 | fun RemoteData.flatten() = this.fold(
15 | { Err.NotExistsError.toMessage().left() },
16 | {
17 | it
18 | }
19 | )
20 |
21 | fun Option.flatten() = this.fold({ false }, { it })
--------------------------------------------------------------------------------
/library/src/androidMain/kotlin/utils/expected/crash-analytics.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import android.content.Context
4 | import io.sentry.kotlin.multiplatform.OptionsConfiguration
5 | import io.sentry.kotlin.multiplatform.Sentry
6 |
7 | actual class CrashAnalytics(private val context: Context) {
8 |
9 | private fun optionsConfiguration(dsn: String): OptionsConfiguration = {
10 | it.dsn = dsn
11 | }
12 |
13 | actual fun init(dsn: String) {
14 | Sentry.init(context, optionsConfiguration(dsn))
15 | }
16 |
17 | actual companion object {
18 | actual fun capture(throwable: Throwable) {
19 | Sentry.captureException(throwable)
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/features/todo/todo.service.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.features.todo
2 |
3 | import arrow.core.toOption
4 | import data.responses.toMessage
5 | import data.types.RemoteListData
6 | import modules.exampleModule.features.todo.dto.TodoResponse
7 |
8 | interface TodoService {
9 | suspend fun getTodos(): RemoteListData
10 | }
11 |
12 | class TodoServiceImpl(
13 | private val todoRepository: TodoRepository
14 | ) : TodoService {
15 |
16 | override suspend fun getTodos(): RemoteListData =
17 | this.todoRepository.getTodos()
18 | .mapLeft { it.toMessage() }
19 | .toOption()
20 |
21 | }
--------------------------------------------------------------------------------
/library/src/jvmMain/kotlin/utils/expected/datastore.koin-module.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.Preferences
5 | import org.koin.core.module.Module
6 | import org.koin.dsl.module
7 | import utils.createDataStore
8 | import utils.dataStoreFileName
9 | import java.io.File
10 |
11 | actual val datastoreModule: Module = module {
12 | single { dataStore() }
13 | }
14 |
15 | fun dataStore(): DataStore = createDataStore(
16 | producePath = {
17 | // Define the path to the local file
18 | val file = File("/tmp/", dataStoreFileName)
19 | file.absolutePath
20 | }
21 | )
22 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/modules/common/koin.lib.kt:
--------------------------------------------------------------------------------
1 | package modules.common
2 |
3 | import configs.ktorClient
4 | import org.koin.core.component.KoinComponent
5 | import org.koin.core.component.inject
6 | import org.koin.dsl.module
7 | import utils.expected.datastoreModule
8 |
9 | fun libModule(authCredentials: AuthCredentials) = module {
10 |
11 | single { ktorClient(authCredentials) }
12 |
13 | } + datastoreModule
14 |
15 | inline fun getKoinInstance(): T {
16 | return object : KoinComponent {
17 | val value: T by inject()
18 | }.value
19 | }
20 |
21 | data class AuthCredentials(
22 | val tokenUrl: String,
23 | val clientId: String,
24 | val clientSecret: String
25 | )
26 |
27 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/utils/expected/analytics.android.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import com.google.firebase.analytics.FirebaseAnalytics
4 | import com.google.firebase.analytics.ktx.analytics
5 | import com.google.firebase.analytics.logEvent
6 | import com.google.firebase.ktx.Firebase
7 | import utils.Tag
8 |
9 | actual object Analytics {
10 | private val fa = Firebase.analytics
11 |
12 | actual fun log(event: Tag.Event, data: Data) {
13 | fa.logEvent(event.name) {
14 | data.id?.let { param(FirebaseAnalytics.Param.ITEM_ID, it) }
15 | param(FirebaseAnalytics.Param.ITEM_NAME, data.name)
16 | param(FirebaseAnalytics.Param.CONTENT_TYPE, data.contentType.name)
17 | }
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/features/todo/todo.vm.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.features.todo
2 |
3 | import arrow.core.none
4 | import cafe.adriel.voyager.core.model.ScreenModel
5 | import data.types.RemoteListData
6 | import kotlinx.coroutines.flow.MutableStateFlow
7 | import kotlinx.coroutines.flow.asStateFlow
8 | import modules.exampleModule.features.todo.dto.TodoResponse
9 |
10 | class TodoVM(
11 | private val todoService: TodoService
12 | ) : ScreenModel {
13 | private val _todos = MutableStateFlow>(none())
14 | val todos = _todos.asStateFlow()
15 |
16 |
17 | suspend fun fetchTodos() {
18 | this._todos.value = this.todoService.getTodos()
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/utils/expected/ui-utility.android.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.platform.LocalContext
8 | import androidx.core.content.ContextCompat
9 |
10 | actual class RemoteUrl actual constructor() {
11 | var context: Context? = null
12 |
13 | @Composable
14 | actual fun create() {
15 | context = LocalContext.current
16 | }
17 |
18 | actual fun open(url: String) {
19 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
20 | context?.let {
21 | ContextCompat.startActivity(it, intent, null)
22 | }
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "cognito-kmm-template"
2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
3 |
4 | pluginManagement {
5 | repositories {
6 | gradlePluginPortal()
7 | mavenCentral()
8 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
9 | google()
10 | }
11 | }
12 |
13 | plugins {
14 | id("org.gradle.toolchains.foojay-resolver-convention") version("0.4.0")
15 | }
16 |
17 | dependencyResolutionManagement {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
22 | }
23 | }
24 |
25 |
26 | include(":library")
27 | include(":shared")
28 |
29 | include(":exampleApp")
--------------------------------------------------------------------------------
/examples/shared/example/screens/screen.def.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.screens
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.outlined.Home
5 | import androidx.compose.material.icons.outlined.Menu
6 | import androidx.compose.material.icons.outlined.Task
7 | import androidx.compose.ui.graphics.vector.ImageVector
8 | import modules.common.views.screens.AppScreen
9 |
10 | sealed class Screens(val screen: AppScreen, val name: String, val iconRes: ImageVector) {
11 |
12 | data object Main : Screens(MainScreen, "HOME", Icons.Outlined.Home)
13 | data object Todo : Screens(MainScreen, "TODO", Icons.Outlined.Task)
14 | data object Settings : Screens(SettingsScreen, "SETTINGS", Icons.Outlined.Menu)
15 | }
16 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/screens/screen.def.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.screens
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.outlined.Home
5 | import androidx.compose.material.icons.outlined.Menu
6 | import androidx.compose.material.icons.outlined.Task
7 | import androidx.compose.ui.graphics.vector.ImageVector
8 | import modules.common.views.screens.AppScreen
9 |
10 | sealed class Screens(val screen: AppScreen, val name: String, val iconRes: ImageVector) {
11 |
12 | data object Main : Screens(MainScreen, "HOME", Icons.Outlined.Home)
13 | data object Todo : Screens(MainScreen, "TODO", Icons.Outlined.Task)
14 | data object Settings : Screens(SettingsScreen, "SETTINGS", Icons.Outlined.Menu)
15 | }
16 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/views/components/expected/images.kt:
--------------------------------------------------------------------------------
1 | package modules.common.views.components.expected
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.graphics.painter.Painter
6 | import androidx.compose.ui.layout.ContentScale
7 |
8 | @Composable
9 | expect fun ImageLoader(
10 | url: String,
11 | contentDescription: String? = null,
12 | modifier: Modifier = Modifier,
13 | placeholder: Painter? = null,
14 | scale: ContentScale = ContentScale.Crop,
15 | content: @Composable () -> Unit = {}
16 | )
17 |
18 | expect class ImagePicker {
19 | @Composable
20 | fun registerPicker(onImagePicked: (ByteArray) -> Unit)
21 |
22 | fun pickImage()
23 | }
24 |
25 | @Composable
26 | expect fun createPicker(): ImagePicker
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/utils/expected/analytics.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import utils.Tag
4 |
5 | expect object Analytics {
6 | fun log(event: Tag.Event, data: Data)
7 | }
8 |
9 | data class Data(
10 | val name: String,
11 | val id: String? = null,
12 | val contentType: CType = CType.Button,
13 | val extra: Map = mapOf()
14 | )
15 |
16 | sealed class CType(val name: String) {
17 | data object Button : CType("Button")
18 | data object Label : CType("Label")
19 | data object Questionnaire : CType("Questionnaire")
20 | data object Screen : CType("Screen")
21 | data object Page : CType("Page")
22 | data object Pref : CType("Pref")
23 | data object Registration : CType("Registration")
24 | }
25 |
26 | fun anal(data: Data, event: Tag.Event = Tag.Event.Select) = Analytics.log(event, data)
27 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/di/koin.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.di
2 |
3 | import modules.common.di.commonModule
4 | import modules.exampleModule.features.todo.TodoRepository
5 | import modules.exampleModule.features.todo.TodoRepositoryImpl
6 | import modules.exampleModule.features.todo.TodoService
7 | import modules.exampleModule.features.todo.TodoServiceImpl
8 | import modules.exampleModule.features.todo.TodoVM
9 | import org.koin.core.context.startKoin
10 | import org.koin.dsl.module
11 |
12 | val exampleModule = module {
13 |
14 | /*
15 | To Do
16 | */
17 | single { TodoRepositoryImpl(get()) }
18 | single { TodoServiceImpl(get()) }
19 |
20 | factory { TodoVM(get()) }
21 |
22 | }
23 |
24 | val koinExampleModules = commonModule + exampleModule
25 |
26 | fun initKoin() = startKoin { modules(koinExampleModules) }
--------------------------------------------------------------------------------
/library/src/linuxX64Main/kotlin/utils/expected/platform.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import kotlin.random.Random
4 |
5 |
6 | actual fun uuid(): String = generateUuid()
7 |
8 | actual val isDebug = true
9 |
10 | actual fun platform(): Platforms = Platforms.JVM
11 |
12 | fun generateUuid(): String {
13 | // Generate random 128-bit value by combining two random 64-bit long values
14 | val mostSigBits = Random.nextLong()
15 | val leastSigBits = Random.nextLong()
16 |
17 | return "${digits(mostSigBits shr 32, 8)}-" +
18 | "${digits(mostSigBits shr 16, 4)}-" +
19 | "${digits(mostSigBits, 4)}-" +
20 | "${digits(leastSigBits shr 48, 4)}-" +
21 | digits(leastSigBits, 12)
22 | }
23 |
24 | // Helper function to format the UUID parts
25 | private fun digits(value: Long, digits: Int): String {
26 | val hexValue = value.toString(16)
27 | return hexValue.padStart(digits, '0')
28 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/features/preferences/pref.model.kt:
--------------------------------------------------------------------------------
1 | package modules.common.features.preferences
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.vector.ImageVector
5 | import androidx.datastore.preferences.core.Preferences
6 | import androidx.datastore.preferences.core.booleanPreferencesKey
7 | import androidx.datastore.preferences.core.stringPreferencesKey
8 |
9 | data class PrefItem(
10 | val title: String,
11 | val icon: ImageVector,
12 | val key: Preferences.Key,
13 | val value: T,
14 | val color: Color
15 | )
16 |
17 | object PrefKeys {
18 | val theme = stringPreferencesKey("pref:app-theme")
19 | val dynamicColorScheme = booleanPreferencesKey("pref:dynamic-color-scheme")
20 | val firebaseTokenKey = stringPreferencesKey("firebase-token-key")
21 | val appIdentifierKey = stringPreferencesKey("app-identifier")
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/modules/common/views/components/expected/sharable.kt:
--------------------------------------------------------------------------------
1 | package modules.common.views.components.expected
2 |
3 | import android.content.Intent
4 | import androidx.activity.ComponentActivity
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.platform.LocalContext
8 |
9 |
10 | actual class ShareData(
11 | private val activity: ComponentActivity
12 | ) {
13 | actual fun shareContent(text: String) {
14 | val sendIntent: Intent = Intent().apply {
15 | action = Intent.ACTION_SEND
16 | putExtra(Intent.EXTRA_TEXT, text)
17 | type = "text/plain"
18 | }
19 | activity.startActivity(Intent.createChooser(sendIntent, null))
20 | }
21 | }
22 |
23 | @Composable
24 | actual fun createShareSheet(): ShareData {
25 | val activity = LocalContext.current as ComponentActivity
26 | return remember(activity) { ShareData(activity) }
27 | }
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/modules/common/models/error-responses.kt:
--------------------------------------------------------------------------------
1 | package modules.common.models
2 |
3 | import data.responses.ErrActions
4 | import kotlinx.datetime.Clock
5 | import kotlinx.datetime.Instant
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | data class ErrResponse(
10 | val code: Int,
11 | val status: String,
12 | val message: String
13 | ) {
14 | override fun toString(): String = message
15 | }
16 |
17 | @Serializable
18 | data class ErrResponseV2(
19 | val type: ResponseType,
20 | val status: HttpStatus,
21 | val code: Int = status.value,
22 | val time: Instant = Clock.System.now(),
23 | val error: ErrData
24 | ) {
25 | override fun toString(): String = error.message
26 | }
27 |
28 | @Serializable
29 | data class ErrData(
30 | val type: String,
31 | val message: String,
32 | val status: HttpStatus,
33 | val description: String = "",
34 | val actions: Set = setOf(),
35 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/features/auth/models/user.model.kt:
--------------------------------------------------------------------------------
1 | package modules.common.features.auth.models
2 |
3 | import kotlinx.datetime.Instant
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class UserBriefResponse(
9 | val id: Long = 0,
10 |
11 | @SerialName("created_at")
12 | val createdAt: Instant,
13 |
14 | @SerialName("updated_at")
15 | val updatedAt: Instant? = null,
16 |
17 | val name: String,
18 |
19 | val username: String,
20 |
21 | var phone: String? = null,
22 |
23 | var email: String? = null,
24 |
25 | val gender: Genders,
26 |
27 | val roles: List,
28 |
29 | val avatar: String? = null,
30 |
31 | val label: String,
32 | )
33 |
34 |
35 | enum class Genders(@SerialName("label") val label: String) {
36 | MALE("Male"),
37 | FEMALE("Female"),
38 | OTHER("Other"),
39 | NOT_SPECIFIED("Not Specified")
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/utils/expected/toast.android.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import utils.Tag
6 | import utils.expected.Duration.Long.millisFromDuration
7 | import utils.logW
8 |
9 | actual class Toasty private constructor() {
10 |
11 | actual companion object {
12 | private lateinit var toast: Toast
13 | actual fun with(context: Any, resId: Int, duration: Duration): Companion {
14 | toast = Toast.makeText(
15 | context as Context, resId, when (millisFromDuration(duration)) {
16 | in 500..1000 -> Toast.LENGTH_SHORT
17 | else -> Toast.LENGTH_LONG
18 | }
19 | )
20 | return this
21 | }
22 |
23 | actual fun show() = if (this::toast.isInitialized)
24 | this.toast.show()
25 | else {
26 | logW(
27 | Tag.Crash,
28 | "You should call Toast() with constructor parameters, otherwise it won't work!"
29 | )
30 | }
31 | }
32 |
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/views/components/resource-components.kt:
--------------------------------------------------------------------------------
1 | package modules.common.views.components
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.ColorFilter
8 | import androidx.compose.ui.graphics.DefaultAlpha
9 | import androidx.compose.ui.graphics.painter.Painter
10 | import androidx.compose.ui.layout.ContentScale
11 |
12 | @Composable
13 | fun WImage(
14 | modifier: Modifier = Modifier,
15 | painter: Painter,
16 | contentDescription: String?,
17 | alignment: Alignment = Alignment.Center,
18 | contentScale: ContentScale = ContentScale.Fit,
19 | alpha: Float = DefaultAlpha,
20 | colorFilter: ColorFilter? = null
21 | ) {
22 | Image(
23 | painter = painter,
24 | contentDescription = contentDescription,
25 | modifier = modifier,
26 | alignment = alignment,
27 | contentScale = contentScale,
28 | alpha = alpha,
29 | colorFilter = colorFilter
30 | )
31 | }
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/modules/common/views/components/expected/sharable.kt:
--------------------------------------------------------------------------------
1 | package modules.common.views.components.expected
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import androidx.compose.ui.interop.LocalUIViewController
6 | import platform.UIKit.UIActivityViewController
7 | import platform.UIKit.UIApplication
8 | import platform.UIKit.UIViewController
9 |
10 |
11 | actual class ShareData(
12 | private val rootController: UIViewController
13 | ) {
14 | actual fun shareContent(text: String) {
15 | // Use iOS sharing functionality
16 | val activityItems = listOf(text)
17 | val activityController = UIActivityViewController(activityItems, null)
18 | val context = UIApplication.sharedApplication.keyWindow?.rootViewController
19 | context?.presentViewController(activityController, true, null)
20 | }
21 | }
22 |
23 | @Composable
24 | actual fun createShareSheet(): ShareData {
25 | val uiViewController = LocalUIViewController.current
26 | return remember { ShareData(uiViewController) }
27 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/features/auth/auth.kt:
--------------------------------------------------------------------------------
1 | package modules.common.features.auth
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.collectAsState
5 | import arrow.core.Either
6 | import arrow.core.none
7 | import data.responses.ErrMessage
8 | import data.types.flatten
9 | import modules.common.features.auth.models.Auth
10 |
11 | @Composable
12 | fun authentication(authVM: AuthVM): Authentication {
13 | AuthComponent(authVM)
14 | val auth = authVM.getAuth().collectAsState(none()).value
15 | return Authentication(
16 | authVM,
17 | auth.flatten(),
18 | auth.fold(
19 | { false },
20 | { it.isRight() }
21 | ))
22 | }
23 |
24 | data class Authentication(
25 | val authVM: AuthVM,
26 | val auth: Either,
27 | val authenticated: Boolean
28 | ) {
29 | fun require(block: () -> Unit = {}) = if (authenticated) {
30 | block()
31 | } else {
32 | authVM.trigger(block = block)
33 | }
34 |
35 | fun matches(userId: Long) = auth.fold(
36 | { false },
37 | { it.id == userId }
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/features/todo/todo.repository.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.features.todo
2 |
3 | import arrow.core.Either
4 | import data.types.Err
5 | import io.ktor.client.HttpClient
6 | import io.ktor.client.call.body
7 | import io.ktor.client.request.get
8 | import io.ktor.client.request.url
9 | import io.ktor.http.ContentType
10 | import io.ktor.http.contentType
11 | import modules.common.models.ErrResponse
12 | import modules.exampleModule.features.todo.dto.TodoResponse
13 | import modules.exampleModule.routes.ExampleRoutes
14 | import utils.result
15 |
16 | interface TodoRepository {
17 | suspend fun getTodos(): Either, List>
18 | }
19 |
20 | class TodoRepositoryImpl(
21 | private val httpClient: HttpClient
22 | ) : TodoRepository {
23 |
24 | override suspend fun getTodos(): Either, List> = result {
25 | this.httpClient.get {
26 | url(ExampleRoutes.Todo.GET_TODO_ITEMS)
27 | contentType(ContentType.Application.Json)
28 | }.body()
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/library/src/iosMain/kotlin/utils/expected/datastore.koin-module.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.Preferences
5 | import kotlinx.cinterop.ExperimentalForeignApi
6 | import org.koin.core.module.Module
7 | import org.koin.dsl.module
8 | import platform.Foundation.NSDocumentDirectory
9 | import platform.Foundation.NSFileManager
10 | import platform.Foundation.NSURL
11 | import platform.Foundation.NSUserDomainMask
12 | import utils.createDataStore
13 | import utils.dataStoreFileName
14 |
15 | actual val datastoreModule: Module = module {
16 | single { dataStore() }
17 | }
18 |
19 | @OptIn(ExperimentalForeignApi::class)
20 | fun dataStore(): DataStore = createDataStore(
21 | producePath = {
22 | val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
23 | directory = NSDocumentDirectory,
24 | inDomain = NSUserDomainMask,
25 | appropriateForURL = null,
26 | create = false,
27 | error = null,
28 | )
29 | requireNotNull(documentDirectory).path + "/$dataStoreFileName"
30 | }
31 | )
32 |
--------------------------------------------------------------------------------
/shared/src/commonMain/composeResources/drawable/box.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/features/notifications/models/notifications.kt:
--------------------------------------------------------------------------------
1 | package modules.common.features.notifications.models
2 |
3 | import kotlinx.serialization.SerialName
4 |
5 | data class NChannel(
6 | val id: Long,
7 |
8 | @SerialName("channel_id")
9 | val channelId: String,
10 |
11 | @SerialName("name")
12 | val name: String,
13 |
14 | @SerialName("description")
15 | val description: String,
16 |
17 | @SerialName("priority")
18 | val priority: Int,
19 |
20 | @SerialName("topics")
21 | val topics: Set
22 | )
23 |
24 | data class Notification(
25 | val title: String,
26 | val body: String,
27 | val image: String?
28 | )
29 |
30 | data class NotificationData(
31 | val notificationId: Int,
32 | val referenceId: Long?,
33 | val referenceId2: Long?,
34 | val navigable: Boolean,
35 | val intentAction: String?,
36 | val channelId: String,
37 | )
38 |
39 | enum class NDataKeys(
40 | val key: String
41 | ) {
42 | NOTIFICATION_ID("notification_id"),
43 | REFERENCE_ID("reference_id"),
44 | REFERENCE_ID_2("reference_id_2"),
45 | NAVIGABLE("navigable"),
46 | CHANNEL_ID("channel_id"),
47 | INTENT_ACTION("intent_action"),
48 | VAL_OPEN_POLL_DETAILS_PAGE("open_poll_details_page"),
49 | VAL_OPEN_KOLLYAN_MAIN("open_kollyan_main"),
50 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/configs/Credentials.kt:
--------------------------------------------------------------------------------
1 | package configs
2 |
3 | import utils.expected.isDebug
4 |
5 | sealed interface Credentials {
6 | data object Sentry : Credentials {
7 | // TODO: Update sentry credentials
8 | val dsn = Credential(
9 | debug = "https://invalidf46ba474d9invalid57269f51@o4505135996272640.ingest.sentry.io/450513599invalid",
10 | release = "https://invalidf46ba474d9539eac057269f51@invalid5996272640.ingest.sentry.io/450513599invalid"
11 | )
12 | }
13 |
14 | data object Auth : Credentials {
15 | val clientId = Credential(
16 | debug = "client_id",
17 | release = "client_id"
18 | )
19 | val clientSecret = Credential(
20 | debug = "client_secret",
21 | release = "client_secret"
22 | )
23 | }
24 |
25 | data object Ads {
26 | // ad credentials
27 | val adUnitInterstitialPB = Credential(
28 | debug = "",
29 | release = ""
30 | )
31 |
32 | }
33 |
34 | data object UserPass {
35 | val username = Credential(
36 | debug = "",
37 | release = ""
38 | )
39 | val password = Credential(
40 | debug = "",
41 | release = ""
42 | )
43 | }
44 |
45 | data class Credential(val debug: String, val release: String) {
46 | override fun toString() = get()
47 | fun get() = if (isDebug) debug else release
48 | }
49 | }
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/library/src/androidMain/kotlin/utils/expected/platform.util.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import android.os.Build
4 | import android.os.Debug
5 | import org.cognitox.clientlib.BuildConfig
6 | import java.util.UUID
7 |
8 |
9 | actual fun uuid(): String = UUID.randomUUID().toString()
10 |
11 | actual val isDebug = BuildConfig.DEBUG || isEmulator
12 |
13 | private val isEmulator: Boolean
14 | get() = (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
15 | || Build.FINGERPRINT.startsWith("generic")
16 | || Build.FINGERPRINT.startsWith("unknown")
17 | || Build.HARDWARE.contains("goldfish")
18 | || Build.HARDWARE.contains("ranchu")
19 | || Build.MODEL.contains("google_sdk")
20 | || Build.MODEL.contains("Emulator")
21 | || Build.MODEL.contains("Android SDK built for x86")
22 | || Build.MANUFACTURER.contains("Genymotion")
23 | || Build.PRODUCT.contains("sdk_google")
24 | || Build.PRODUCT.contains("google_sdk")
25 | || Build.PRODUCT.contains("sdk")
26 | || Build.PRODUCT.contains("sdk_x86")
27 | || Build.PRODUCT.contains("sdk_gphone64_arm64")
28 | || Build.PRODUCT.contains("vbox86p")
29 | || Build.PRODUCT.contains("emulator")
30 | || Build.PRODUCT.contains("simulator")
31 | || Debug.isDebuggerConnected()
32 |
33 |
34 | actual fun platform(): Platforms = Platforms.ANDROID
35 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/animations/transition-animations.kt:
--------------------------------------------------------------------------------
1 | package modules.common.animations
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.AnimatedVisibilityScope
5 | import androidx.compose.animation.EnterTransition
6 | import androidx.compose.animation.ExitTransition
7 | import androidx.compose.animation.core.MutableTransitionState
8 | import androidx.compose.animation.fadeIn
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.LocalDensity
12 |
13 | @Composable
14 | fun EnterAnimation(content: @Composable () -> Unit) {
15 | AnimatedVisibility(
16 | visibleState = MutableTransitionState(
17 | initialState = false
18 | ).apply { targetState = true },
19 | modifier = Modifier,
20 | enter = Transition.FadeInOut(1000).enter,
21 | exit = Transition.FadeInOut(500).exit,
22 | ) {
23 | content()
24 | }
25 | }
26 |
27 | @Composable
28 | fun LoadingAnimation(
29 | visible: Boolean,
30 | modifier: Modifier = Modifier,
31 | content: @Composable() AnimatedVisibilityScope.() -> Unit
32 | ) =
33 | AnimatedVisibility(
34 | visible = visible,
35 | modifier = Modifier.then(modifier),
36 | enter = Transition.FadeInOut().enter,
37 | exit = Transition.FadeInOut().exit,
38 | content = content
39 | )
--------------------------------------------------------------------------------
/library/src/linuxX64Main/kotlin/utils/expected/datastore.koin-module.kt:
--------------------------------------------------------------------------------
1 | package utils.expected
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.Preferences
5 | import io.ktor.http.encodeURLPath
6 | import kotlinx.cinterop.ExperimentalForeignApi
7 | import kotlinx.cinterop.refTo
8 | import kotlinx.cinterop.toKString
9 | import org.koin.core.module.Module
10 | import org.koin.dsl.module
11 | import platform.posix.fclose
12 | import platform.posix.fgets
13 | import platform.posix.fopen
14 | import utils.createDataStore
15 | import utils.dataStoreFileName
16 |
17 | actual val datastoreModule: Module = module {
18 | single { dataStore() }
19 | }
20 |
21 | fun dataStore(): DataStore = createDataStore(
22 | producePath = {
23 | // Define the path to the local file
24 | val file = readFile("/tmp/" + dataStoreFileName)
25 | file.encodeURLPath()
26 | }
27 | )
28 |
29 | @OptIn(ExperimentalForeignApi::class)
30 | fun readFile(filePath: String): String {
31 | val file = fopen(filePath, "r") ?: throw IllegalArgumentException("Cannot open file: $filePath")
32 | try {
33 | val buffer = StringBuilder()
34 | val line = ByteArray(1024)
35 | while (fgets(line.refTo(0), line.size, file) != null) {
36 | buffer.append(line.toKString())
37 | }
38 | return buffer.toString()
39 | } finally {
40 | fclose(file)
41 | }
42 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/features/events/models/event.model.kt:
--------------------------------------------------------------------------------
1 | package modules.common.features.events.models
2 |
3 | import kotlinx.datetime.Clock
4 | import kotlinx.datetime.Instant
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | data class TaskEventReq(
10 |
11 | @SerialName("start_at")
12 | val startAt: Instant,
13 |
14 | @SerialName("end_at")
15 | val endAt: Instant?
16 |
17 | )
18 |
19 | @Serializable
20 | data class EventBrief(
21 | val id: Long,
22 |
23 | @SerialName("created_at")
24 | val createdAt: Instant,
25 |
26 | @SerialName("updated_at")
27 | val updatedAt: Instant? = null,
28 |
29 | val title: String,
30 |
31 | @SerialName("ref_id")
32 | val refId: Long?,
33 |
34 | val image: String?,
35 |
36 | val type: EventTypes,
37 |
38 | val active: Boolean,
39 |
40 | @SerialName("start_at")
41 | val startAt: Instant,
42 |
43 | @SerialName("end_at")
44 | val endAt: Instant?,
45 |
46 | val repetitive: Boolean,
47 |
48 | @SerialName("repeat_interval")
49 | val repeatInterval: Long,
50 |
51 | @SerialName("repeat_count")
52 | val repeatCount: Int,
53 |
54 | @SerialName("user_id")
55 | val userId: Long
56 | ){
57 | val isExpired = !this.active
58 | || this.endAt?.let { it
24 |
25 | Column(
26 | modifier = Modifier.padding(paddingValues)
27 | .fillMaxSize()
28 | .padding(Paddings.Screen.horizontal),
29 | verticalArrangement = Arrangement.Center,
30 | horizontalAlignment = Alignment.CenterHorizontally
31 | ) {
32 | Text(
33 | modifier = Modifier.clickable {
34 | snackbar.show("Unbelievable! You've successfully clicked me.")
35 | },
36 | textAlign = TextAlign.Center,
37 | text = "Click Me"
38 | )
39 | }
40 | }
41 | }
42 |
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quickstart: Getting Started
2 | 
3 |
4 | ## Getting Started with this project
5 |
6 | This quickstart guide provides a brief overview of the essential steps to get started with this project. For more detailed information, refer to the linked documentation pages.
7 |
8 | ## Key Steps:
9 |
10 | ### [Prerequisites:](https://cognitox.gitbook.io/cognitox-docs/getting-started/prerequisites)
11 |
12 | Ensure you have the necessary tools and dependencies installed.
13 |
14 | ### [Installation:](https://cognitox.gitbook.io/cognitox-docs/getting-started/installation)
15 |
16 | Follow the instructions to set up your KMM development environment.
17 |
18 | ### [Creating a New App:](https://cognitox.gitbook.io/cognitox-docs/getting-started/creating-new-app)
19 |
20 | Learn how to quickly generate a new app module within your project.
21 |
22 | ### [Project Architecture:](https://cognitox.gitbook.io/cognitox-docs/architecture/editor)
23 |
24 | Familiarize yourself with the project structure and how different modules interact.
25 |
26 |
27 | ## [Documentation](https://cognitox.gitbook.io/cognitox-docs)
28 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/routes/route.kt:
--------------------------------------------------------------------------------
1 | package modules.common.routes
2 |
3 | import utils.expected.isDebug
4 |
5 | val BASE_URL = if (isDebug) {
6 | "https://dev.example.com"
7 | } else {
8 | "https://api.example.com"
9 | }
10 |
11 | val SOCKET_CONNECT = if (isDebug) {
12 | "wss://dev.example.com/connect"
13 | } else {
14 | "wss://api.example.com/connect"
15 | }
16 |
17 |
18 | object SocketRoutes {
19 | private const val MESSAGES = "/messages"
20 | fun queue(sessionId: String, name: String? = null) =
21 | name?.let { "/user/$sessionId/queue/$name" } ?: "/user/$sessionId/queue/default"
22 |
23 | object V1 {
24 | /*
25 | Send/Destinations
26 | */
27 | const val SEND_PING = "$MESSAGES/ping"
28 |
29 | /*
30 | Topics
31 | */
32 | const val TOPIC_PING = "/ping"
33 |
34 | fun queuePing(sessionId: String) = queue(sessionId, "ping")
35 | }
36 | }
37 |
38 | object Routes {
39 |
40 | private const val API_VERSION = "/api/v1"
41 | private const val API_VERSION_V2 = "/api/v2"
42 |
43 | // Auth
44 | val GET_TOKEN = "$BASE_URL/oauth/token"
45 | fun getOTP(phoneOrEmail: String) =
46 | "$BASE_URL$API_VERSION/register/verify?identity=$phoneOrEmail"
47 |
48 | fun checkUsername(username: String) =
49 | "$BASE_URL$API_VERSION/public/register/check-username?username=$username"
50 |
51 | fun signup(token: String) = "$BASE_URL$API_VERSION/register?token=$token"
52 | fun registerFirebaseToken() = "$BASE_URL$API_VERSION/firebase/token"
53 |
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
16 |
17 |
18 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
16 |
17 |
18 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/di/koin.common.kt:
--------------------------------------------------------------------------------
1 | package modules.common.di
2 |
3 | import configs.Credentials
4 | import modules.common.AuthCredentials
5 | import modules.common.features.auth.AuthRepository
6 | import modules.common.features.auth.AuthRepositoryImpl
7 | import modules.common.features.auth.AuthService
8 | import modules.common.features.auth.AuthServiceImpl
9 | import modules.common.features.auth.AuthVM
10 | import modules.common.features.preferences.PrefVM
11 | import modules.common.libModule
12 | import modules.common.routes.Routes
13 | import org.koin.core.component.KoinComponent
14 | import org.koin.core.component.inject
15 | import org.koin.dsl.module
16 |
17 | val commonModule = module {
18 |
19 | /*
20 | Auth Components
21 | */
22 | single { AuthRepositoryImpl(get()) }
23 | single { AuthServiceImpl(get()) }
24 | // AuthVM needs to be singleton because auth is used in every screens and ViewModel
25 | // needs to survive as long as app is running on the foreground, so the auth state isn't lost.
26 | single { AuthVM(get(), get(), get()) }
27 |
28 | /*
29 | Preference/Settings Components
30 | */
31 | factory { PrefVM(get()) }
32 |
33 | } + libModule(
34 | AuthCredentials(
35 | tokenUrl = Routes.GET_TOKEN,
36 | clientId = Credentials.Auth.clientId.get(),
37 | clientSecret = Credentials.Auth.clientSecret.get()
38 | )
39 | )
40 |
41 | inline fun getKoinInstance(): T {
42 | return object : KoinComponent {
43 | val value: T by inject()
44 | }.value
45 | }
46 |
47 |
48 |
--------------------------------------------------------------------------------
/scripts/gen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function execTool() {
4 | java -jar scripts/tools/gentool.jar "$1" "$2" "$srcDir" "$3"
5 | }
6 |
7 | if [ "$1" = "crud" ]; then
8 | srcDir="examples/crudexample"
9 | execTool "$1" "$2" "."
10 | elif [ "$1" = "module" ]; then
11 | srcDir="examples/exampleApp"
12 | execTool "$1" "$2" "./"
13 | mv "./$2" "$2App"
14 |
15 | srcDir="examples/exampleIOS"
16 | execTool "$1" "$2" "./"
17 | mv "$2" "./$2IOS"
18 |
19 | srcDir="examples/shared/example"
20 | execTool "$1" "$2" "shared/src/commonMain/kotlin/modules"
21 | mv shared/src/commonMain/kotlin/modules/"$2" shared/src/commonMain/kotlin/modules/"$2"Module
22 |
23 | srcDir="examples/shared/exampleAndroid"
24 | execTool "$1" "$2" "shared/src/androidMain/kotlin/modules"
25 | mv shared/src/androidMain/kotlin/modules/"$2" shared/src/androidMain/kotlin/modules/"$2"Module
26 |
27 | srcDir="examples/shared/exampleIOS"
28 | execTool "$1" "$2" "shared/src/iosMain/kotlin/modules"
29 | mv shared/src/iosMain/kotlin/modules/"$2" shared/src/iosMain/kotlin/modules/"$2"Module
30 | cp examples/exampleIOS/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png "$2"IOS/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png
31 |
32 | echo -e "\ninclude(\":$2App\")" >> ./settings.gradle.kts
33 |
34 | elif [ "$1" = "migration" ]; then
35 | echo "-- $(date "+%b %d, %Y")" >"app/src/main/resources/db/migration/V$(date +%s)__$2.sql"
36 | echo "Migration Created!"
37 | else
38 | echo "Must specify type of generated asset. i.e. crud | module | migration"
39 | fi
40 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/views/components/charts.kt:
--------------------------------------------------------------------------------
1 | package modules.common.views.components
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.material3.Card
5 | import androidx.compose.material3.CardDefaults
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.text.TextStyle
10 | import androidx.compose.ui.text.font.FontWeight
11 | import com.aay.compose.baseComponents.model.LegendPosition
12 | import com.aay.compose.donutChart.PieChart
13 | import com.aay.compose.donutChart.model.PieChartData
14 |
15 | @Composable
16 | fun PiChart(
17 | modifier: Modifier,
18 | data: List
19 | ) {
20 | if (data.sumOf { it.data } <= 0) return
21 | Card(
22 | modifier = modifier,
23 | colors = CardDefaults.cardColors(
24 | containerColor = MaterialTheme.colorScheme.surfaceVariant
25 | )
26 | ) {
27 | PieChart(
28 | modifier = Modifier.fillMaxSize(),
29 | pieChartData = data,
30 | ratioLineColor = MaterialTheme.colorScheme.surfaceVariant,
31 | outerCircularColor = MaterialTheme.colorScheme.surfaceVariant,
32 | textRatioStyle = TextStyle(color = MaterialTheme.colorScheme.onSurfaceVariant),
33 | legendPosition = LegendPosition.BOTTOM,
34 | descriptionStyle = TextStyle(
35 | color = MaterialTheme.colorScheme.onSurfaceVariant,
36 | fontSize = MaterialTheme.typography.labelLarge.fontSize,
37 | fontWeight = FontWeight.Bold
38 | ),
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/data/pagination.kt:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import kotlinx.serialization.Serializable
4 | import utils.toParamString
5 |
6 | @Serializable
7 | data class Page(
8 | val content: List,
9 | val totalElements: Long,
10 | val last: Boolean,
11 | val totalPages: Int,
12 | val sort: Sort,
13 | val numberOfElements: Long,
14 | val first: Boolean,
15 | val size: Int,
16 | val number: Long,
17 | val empty: Boolean,
18 | ) {
19 | companion object {
20 | fun of(content: List) =
21 | Page(
22 | content = content,
23 | totalElements = 0,
24 | last = true,
25 | totalPages = 0,
26 | sort = Sort(false, true, false),
27 | numberOfElements = 0L,
28 | first = true,
29 | size = 10,
30 | number = 0,
31 | empty = false
32 | )
33 | }
34 | }
35 |
36 | @Serializable
37 | data class Sort(
38 | val sorted: Boolean,
39 | val unsorted: Boolean,
40 | val empty: Boolean,
41 | )
42 |
43 | data class PageableParams(
44 | val query: String? = null,
45 | val page: Long = 0,
46 | val size: Int = 10,
47 | val sortBy: SortByFields = SortByFields.ID,
48 | val direction: SortDirections = SortDirections.DESC
49 | ){
50 | fun toParamString() = mapOf(
51 | "q" to if (this.query == null) "" else this.query.toString(),
52 | "page" to this.page,
53 | "size" to this.size,
54 | "sort_by" to this.sortBy.name,
55 | "sort_direction" to this.direction.name
56 | ).toParamString()
57 | }
58 |
59 | enum class SortByFields {
60 | ID, CREATED_AT, SERIAL;
61 | }
62 |
63 | enum class SortDirections {
64 | ASC, DESC
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/examples/shared/exampleAndroid/theme/theme.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.dynamicDarkColorScheme
7 | import androidx.compose.material3.dynamicLightColorScheme
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.SideEffect
10 | import androidx.compose.ui.graphics.toArgb
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.compose.ui.platform.LocalView
13 | import androidx.core.view.WindowCompat
14 |
15 | @Composable
16 | actual fun ExampleAppTheme(
17 | useDarkTheme: Boolean,
18 | dynamicColorScheme: Boolean,
19 | content: @Composable () -> Unit
20 | ) {
21 | val context = LocalContext.current
22 | val colors = when {
23 | dynamicColorScheme && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
24 | if (useDarkTheme) dynamicDarkColorScheme(context)
25 | else dynamicLightColorScheme(context)
26 | }
27 |
28 | useDarkTheme -> darkColors
29 | else -> lightColors
30 | }
31 |
32 | // Add primary status bar color from chosen color scheme.
33 | val view = LocalView.current
34 | if (!view.isInEditMode) {
35 | SideEffect {
36 | val window = (view.context as Activity).window
37 | window.statusBarColor = colors.primary.toArgb()
38 | WindowCompat
39 | .getInsetsController(window, view)
40 | .isAppearanceLightStatusBars = useDarkTheme
41 | }
42 | }
43 |
44 | MaterialTheme(
45 | colorScheme = colors,
46 | shapes = shapes,
47 | typography = typography,
48 | content = content,
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/modules/exampleModule/theme/theme.example.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.dynamicDarkColorScheme
7 | import androidx.compose.material3.dynamicLightColorScheme
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.SideEffect
10 | import androidx.compose.ui.graphics.toArgb
11 | import androidx.compose.ui.platform.LocalContext
12 | import androidx.compose.ui.platform.LocalView
13 | import androidx.core.view.WindowCompat
14 |
15 | @Composable
16 | actual fun ExampleAppTheme(
17 | useDarkTheme: Boolean,
18 | dynamicColorScheme: Boolean,
19 | content: @Composable () -> Unit
20 | ) {
21 | val context = LocalContext.current
22 | val colors = when {
23 | dynamicColorScheme && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
24 | if (useDarkTheme) dynamicDarkColorScheme(context)
25 | else dynamicLightColorScheme(context)
26 | }
27 |
28 | useDarkTheme -> darkColors
29 | else -> lightColors
30 | }
31 |
32 | // Add primary status bar color from chosen color scheme.
33 | val view = LocalView.current
34 | if (!view.isInEditMode) {
35 | SideEffect {
36 | val window = (view.context as Activity).window
37 | window.statusBarColor = colors.primary.toArgb()
38 | WindowCompat
39 | .getInsetsController(window, view)
40 | .isAppearanceLightStatusBars = useDarkTheme
41 | }
42 | }
43 |
44 | MaterialTheme(
45 | colorScheme = colors,
46 | shapes = shapes,
47 | typography = typography,
48 | content = content,
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/exampleIOS/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | CADisableMinimumFrameDurationOnPhone
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/examples/exampleIOS/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | CADisableMinimumFrameDurationOnPhone
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/shared/shared.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'shared'
3 | spec.version = '1.0.0'
4 | spec.homepage = 'Link to the Shared Module homepage'
5 | spec.source = { :http=> ''}
6 | spec.authors = ''
7 | spec.license = ''
8 | spec.summary = 'Some description for the Shared Module'
9 | spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework'
10 | spec.libraries = 'c++'
11 | spec.ios.deployment_target = '14.1'
12 |
13 |
14 | spec.pod_target_xcconfig = {
15 | 'KOTLIN_PROJECT_PATH' => ':shared',
16 | 'PRODUCT_MODULE_NAME' => 'shared',
17 | }
18 |
19 | spec.script_phases = [
20 | {
21 | :name => 'Build shared',
22 | :execution_position => :before_compile,
23 | :shell_path => '/bin/sh',
24 | :script => <<-SCRIPT
25 | if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
26 | echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
27 | exit 0
28 | fi
29 | set -ev
30 | REPO_ROOT="$PODS_TARGET_SRCROOT"
31 | "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
32 | -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
33 | -Pkotlin.native.cocoapods.archs="$ARCHS" \
34 | -Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
35 | SCRIPT
36 | }
37 | ]
38 | spec.resources = ['src/commonMain/resources/**', 'src/iosMain/resources/**']
39 | end
--------------------------------------------------------------------------------
/exampleApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/examples/exampleApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/exampleModule/screens/home.screen.kt:
--------------------------------------------------------------------------------
1 | package modules.exampleModule.screens
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.LaunchedEffect
7 | import androidx.compose.runtime.collectAsState
8 | import androidx.compose.ui.Modifier
9 | import cafe.adriel.voyager.koin.koinScreenModel
10 | import modules.common.features.auth.AuthVM
11 | import modules.common.features.auth.authentication
12 | import modules.common.views.components.LazyColumnWithLoadingForList
13 | import modules.common.views.dimensions.Paddings
14 | import modules.common.views.screens.AppScreen
15 | import modules.exampleModule.features.todo.TodoCategoryView
16 | import modules.exampleModule.features.todo.TodoItemView
17 | import modules.exampleModule.features.todo.TodoVM
18 | import modules.exampleModule.screens.layouts.ExampleAppLayout
19 | import utils.show
20 |
21 | object MainScreen : AppScreen {
22 |
23 | @Composable
24 | override fun Content() {
25 | val todoVM = koinScreenModel()
26 | val authVM = koinScreenModel()
27 |
28 | ExampleAppLayout { paddingValues, snackbar ->
29 |
30 | LaunchedEffect(Unit) {
31 | todoVM.fetchTodos()
32 | }
33 |
34 | val auth = authentication(authVM)
35 |
36 | Column(
37 | modifier = Modifier.padding(paddingValues)
38 | .padding(Paddings.Screen.horizontal),
39 | ) {
40 | TodoCategoryView()
41 |
42 | LazyColumnWithLoadingForList(
43 | remoteData = todoVM.todos.collectAsState().value,
44 | itemView = {
45 | TodoItemView(
46 | todo = it,
47 | itemClick = {
48 | auth.require {
49 | snackbar.show("Hi, you clicked #${it.id}")
50 | }
51 | }
52 | )
53 | }
54 | )
55 | }
56 | }
57 | }
58 |
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/views/dimensions/size.kt:
--------------------------------------------------------------------------------
1 | package modules.common.views.dimensions
2 |
3 | import androidx.compose.foundation.layout.Spacer
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.foundation.layout.width
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.unit.dp
9 |
10 | @Composable
11 | fun HorizontalSpacer() = Spacer(modifier = Modifier.width(Paddings.General.spacerWidth))
12 |
13 | @Composable
14 | fun VerticalSpacer() = Spacer(modifier = Modifier.height(Paddings.General.spacerWidth))
15 |
16 | object Paddings {
17 | object Internal {
18 | val innerPadding = 10.dp
19 |
20 | object SmallObjects {
21 | val vertical = 5.dp
22 | val horizontal = 5.dp
23 | val tiny = 2.dp
24 | }
25 | }
26 |
27 | object Grid {
28 | val top = 24.dp
29 | val bottom = 24.dp
30 | val bottomWithButton = 50.dp
31 | val horizontalSpacing = 16.dp
32 | val verticalSpacing = 16.dp
33 | }
34 |
35 | object Card {
36 | val horizontal = 16.dp
37 | val vertical = 20.dp
38 | val horizontalLarge = 24.dp
39 | val verticalLarge = 24.dp
40 | val verticalLargeWithButtons = 50.dp
41 | val spacerHeight = 16.dp
42 | val internalHorizontal = 5.dp
43 | val internalVertical = 8.dp
44 | }
45 |
46 | object General {
47 | val surround = 16.dp
48 | val spacerHeightSmall = 5.dp
49 | val spacerHeight = 10.dp
50 | val spacerWidth = 10.dp
51 | val buttonSpacerWidth = 20.dp
52 | }
53 |
54 | object Image {
55 | val surround = 5.dp
56 | }
57 |
58 | object Screen {
59 | val horizontal = 10.dp
60 | val vertical = 0.dp
61 | }
62 |
63 | object Quiz {
64 | object Header {
65 | val horizontal = 16.dp
66 | val vertical = 10.dp
67 | }
68 |
69 | object Buttons {
70 | val horizontal = 0.dp
71 | val vertical = 20.dp
72 | }
73 | }
74 |
75 | object Dialogs {
76 | val surround = 30.dp
77 |
78 | object Buttons {
79 | val horizontal = 16.dp
80 | val vertical = 5.dp
81 | }
82 | }
83 |
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/logger.kt:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import co.touchlab.kermit.Logger
4 | import utils.expected.isDebug
5 |
6 | sealed interface Tag {
7 |
8 | sealed interface View : Tag {
9 | data object SocialButton : View
10 | data object Pager : View
11 | data object Quiz : View
12 | data object Pref : View
13 | data object DatePicker : View
14 | }
15 |
16 | sealed interface Auth : Tag {
17 | data object LoadAuthFromStorage : Auth
18 | data object RefreshToken : Auth
19 | data object SignUp : Auth
20 | }
21 |
22 | sealed interface Network : Tag {
23 | data object PushMessage : Network
24 | data object Call : Network
25 | data object JsonParsing : Network
26 |
27 | data object WebSocket : Network {
28 | override fun toString() = "WebSocket"
29 | }
30 | }
31 |
32 | sealed interface Notification : Tag {
33 | data object FirebaseServiceToken : Notification
34 | }
35 |
36 | data object General : Tag
37 | data object Locale : Tag
38 | data object Crash : Tag
39 | data class Service(val name: String) : Tag {
40 | override fun toString() = name
41 | }
42 |
43 | data object BroadcastReceiver : Tag
44 |
45 | sealed class Event(val name: String) : Tag {
46 | data object Select : Event("select_item")
47 | data object Completed : Event("completed")
48 | data object Open : Event("open")
49 | }
50 |
51 | }
52 |
53 | fun logD(tag: Tag, msg: String, throwable: Throwable? = null) {
54 | if (isDebug) Logger.d(msg, throwable, tag::class.simpleName ?: "")
55 | }
56 |
57 | fun logE(tag: Tag, msg: String, throwable: Throwable? = null) {
58 | if (isDebug) Logger.e(msg, throwable, tag::class.simpleName ?: "")
59 | }
60 |
61 | fun logI(tag: Tag, msg: String) = Logger.e(msg, null, tag::class.simpleName ?: "")
62 | fun logV(tag: Tag, msg: String) = Logger.v(msg, null, tag::class.simpleName ?: "")
63 | fun logW(tag: Tag, msg: String, throwable: Throwable? = null) =
64 | Logger.w(msg, throwable, tag::class.simpleName ?: "")
65 |
66 | fun logWTF(tag: Tag, msg: String, throwable: Throwable? = null) =
67 | Logger.a(msg, throwable, tag::class.simpleName ?: "")
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/modules/common/ads/ads.android.kt:
--------------------------------------------------------------------------------
1 | package modules.common.ads
2 |
3 | import androidx.activity.ComponentActivity
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.platform.LocalContext
6 | import com.google.android.gms.ads.AdError
7 | import com.google.android.gms.ads.AdRequest
8 | import com.google.android.gms.ads.FullScreenContentCallback
9 | import com.google.android.gms.ads.LoadAdError
10 | import com.google.android.gms.ads.interstitial.InterstitialAd
11 | import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
12 | import configs.Credentials
13 |
14 | actual class Ads(
15 | private val context: ComponentActivity
16 | ) {
17 | private var mInterstitialAd: InterstitialAd? = null
18 | private final val TAG = "Ads"
19 |
20 | actual fun load(adUnitId: String) {
21 | val adRequest = AdRequest.Builder().build()
22 |
23 | InterstitialAd.load(
24 | context,
25 | adUnitId,
26 | adRequest,
27 | object : InterstitialAdLoadCallback() {
28 | override fun onAdFailedToLoad(adError: LoadAdError) {
29 | mInterstitialAd = null
30 | }
31 |
32 | override fun onAdLoaded(interstitialAd: InterstitialAd) {
33 | mInterstitialAd = interstitialAd
34 | }
35 | })
36 |
37 | mInterstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback() {
38 | override fun onAdClicked() {
39 | // Called when a click is recorded for an ad.
40 | }
41 |
42 | override fun onAdDismissedFullScreenContent() {
43 | // Called when ad is dismissed.
44 | mInterstitialAd = null
45 | }
46 |
47 | override fun onAdFailedToShowFullScreenContent(p0: AdError) {
48 | mInterstitialAd = null
49 | }
50 |
51 | override fun onAdImpression() {
52 | // Called when an impression is recorded for an ad.
53 | }
54 |
55 | override fun onAdShowedFullScreenContent() {
56 | // Called when ad is shown.
57 | }
58 | }
59 | }
60 |
61 | actual fun show() {
62 | this.mInterstitialAd?.show(this.context)
63 | }
64 | }
65 |
66 | @Composable
67 | actual fun interstitialAd(adUnitId: String): Ads {
68 | val context = LocalContext.current as ComponentActivity
69 | val ad = Ads(context)
70 | ad.load(adUnitId)
71 | return ad
72 | }
--------------------------------------------------------------------------------
/exampleApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.kotlinMultiplatform)
3 | alias(libs.plugins.androidApplication)
4 | alias(libs.plugins.jetbrainsCompose)
5 | alias(libs.plugins.compose.compiler)
6 | alias(libs.plugins.crashlytics)
7 | }
8 | /*
9 | Conditionally apply google services plugin based on existence of google-services.json file
10 | */
11 | val googleServicesFile = File(projectDir, "google-services.json")
12 | if (googleServicesFile.exists()) {
13 | println("google-services.json found, Google Services will be enabled.")
14 | apply(plugin = libs.plugins.googleServices.get().pluginId)
15 | } else {
16 | println("google-services.json not found, Google Services will be disabled.")
17 | }
18 | /*
19 | End loading google services plugin
20 | */
21 |
22 | kotlin {
23 | androidTarget()
24 | sourceSets {
25 | val androidMain by getting {
26 | dependencies {
27 | implementation(project(":shared"))
28 | }
29 | }
30 | }
31 | }
32 |
33 | android {
34 | compileSdk = (findProperty("android.compileSdk") as String).toInt()
35 | namespace = "org.cognitox"
36 |
37 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
38 |
39 | defaultConfig {
40 | applicationId = "$namespace"
41 | minSdk = libs.versions.android.minSdk.get().toInt()
42 | targetSdk = libs.versions.android.targetSdk.get().toInt()
43 | versionCode = 1
44 | versionName = "1.0"
45 | }
46 | packaging {
47 | resources {
48 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
49 | excludes += "META-INF/versions/9/previous-compilation-data.bin"
50 | }
51 | }
52 | compileOptions {
53 | sourceCompatibility = JavaVersion.VERSION_17
54 | targetCompatibility = JavaVersion.VERSION_17
55 | }
56 | kotlin {
57 | jvmToolchain(libs.versions.jdk.get().toInt())
58 | }
59 |
60 | composeOptions {
61 | kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
62 | }
63 |
64 | buildFeatures {
65 | compose = true
66 | }
67 |
68 | // buildTypes {
69 | // release {
70 | // isMinifyEnabled = true
71 | // proguardFiles(
72 | // getDefaultProguardFile("proguard-android-optimize.txt"),
73 | // "proguard-rules.pro"
74 | // )
75 | // }
76 | // }
77 | }
78 |
--------------------------------------------------------------------------------
/examples/exampleApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.kotlinMultiplatform)
3 | alias(libs.plugins.androidApplication)
4 | alias(libs.plugins.jetbrainsCompose)
5 | alias(libs.plugins.compose.compiler)
6 | alias(libs.plugins.crashlytics)
7 | }
8 | /*
9 | Conditionally apply google services plugin based on existence of google-services.json file
10 | */
11 | val googleServicesFile = File(projectDir, "google-services.json")
12 | if (googleServicesFile.exists()) {
13 | println("google-services.json found, Google Services will be enabled.")
14 | apply(plugin = libs.plugins.googleServices.get().pluginId)
15 | } else {
16 | println("google-services.json not found, Google Services will be disabled.")
17 | }
18 | /*
19 | End loading google services plugin
20 | */
21 |
22 | kotlin {
23 | androidTarget()
24 | sourceSets {
25 | val androidMain by getting {
26 | dependencies {
27 | implementation(project(":shared"))
28 | }
29 | }
30 | }
31 | }
32 |
33 | android {
34 | compileSdk = (findProperty("android.compileSdk") as String).toInt()
35 | namespace = "org.cognitox"
36 |
37 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
38 |
39 | defaultConfig {
40 | applicationId = "$namespace"
41 | minSdk = libs.versions.android.minSdk.get().toInt()
42 | targetSdk = libs.versions.android.targetSdk.get().toInt()
43 | versionCode = 1
44 | versionName = "1.0"
45 | }
46 | packaging {
47 | resources {
48 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
49 | excludes += "META-INF/versions/9/previous-compilation-data.bin"
50 | }
51 | }
52 | compileOptions {
53 | sourceCompatibility = JavaVersion.VERSION_17
54 | targetCompatibility = JavaVersion.VERSION_17
55 | }
56 | kotlin {
57 | jvmToolchain(libs.versions.jdk.get().toInt())
58 | }
59 |
60 | composeOptions {
61 | kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
62 | }
63 |
64 | buildFeatures {
65 | compose = true
66 | }
67 |
68 | // buildTypes {
69 | // release {
70 | // isMinifyEnabled = true
71 | // proguardFiles(
72 | // getDefaultProguardFile("proguard-android-optimize.txt"),
73 | // "proguard-rules.pro"
74 | // )
75 | // }
76 | // }
77 | }
78 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/views/components/header.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalMaterial3Api::class)
2 |
3 | package modules.common.views.components
4 |
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.RowScope
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.material3.CenterAlignedTopAppBar
11 | import androidx.compose.material3.ExperimentalMaterial3Api
12 | import androidx.compose.material3.Icon
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.material3.TopAppBarScrollBehavior
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.painter.Painter
18 | import androidx.compose.ui.semantics.Role
19 | import androidx.compose.ui.semantics.contentDescription
20 | import androidx.compose.ui.semantics.role
21 | import androidx.compose.ui.semantics.semantics
22 | import androidx.compose.ui.unit.dp
23 |
24 | @OptIn(ExperimentalMaterial3Api::class)
25 | @Composable
26 | fun AppBarTop(
27 | modifier: Modifier = Modifier,
28 | logo: @Composable ()->Unit,
29 | scrollBehavior: TopAppBarScrollBehavior? = null,
30 | onLogoClicked: () -> Unit = { },
31 | title: @Composable () -> Unit,
32 | actions: @Composable RowScope.() -> Unit = {}
33 | ) {
34 | CenterAlignedTopAppBar(
35 | modifier = modifier,
36 | actions = actions,
37 | title = title,
38 | scrollBehavior = scrollBehavior,
39 | navigationIcon = {
40 | HeaderAppLogo(
41 | modifier = Modifier
42 | .size(64.dp)
43 | .clickable(onClick = onLogoClicked)
44 | .padding(16.dp),
45 | logo = logo,
46 | contentDescription = "Open Drawer"
47 | )
48 | }
49 | )
50 | }
51 |
52 | @Composable
53 | fun HeaderAppLogo(
54 | modifier: Modifier = Modifier,
55 | logo: @Composable ()->Unit,
56 | contentDescription: String?
57 | ) {
58 |
59 | val semantics = contentDescription?.let {
60 | Modifier.semantics {
61 | this.contentDescription = contentDescription
62 | this.role = Role.Image
63 | }
64 | } ?: Modifier
65 |
66 | Box(modifier = modifier.then(semantics)) {
67 | logo()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/library/src/commonMain/kotlin/utils/time.kt:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import kotlinx.datetime.Clock
4 | import kotlinx.datetime.Instant
5 | import kotlinx.datetime.TimeZone
6 | import kotlinx.datetime.toLocalDateTime
7 | import kotlin.math.min
8 |
9 | fun Instant.toReadableDate(timeZone: TimeZone = TimeZone.currentSystemDefault()): String {
10 | val localDate = this.toLocalDateTime(timeZone).date
11 | val month = localDate.month.name.lowercase()
12 | .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
13 | val monthShort = month.substring(0, min(3, month.length))
14 | return "$monthShort ${localDate.dayOfMonth}, ${localDate.year}"
15 | }
16 |
17 | fun Instant.toReadableTime(
18 | timeZone: TimeZone = TimeZone.currentSystemDefault(),
19 | withSeconds: Boolean = false
20 | ): String {
21 | val localTime = this.toLocalDateTime(timeZone).time
22 | return "${localTime.hour}:${localTime.minute}" + if (withSeconds) ":${localTime.second}" else ""
23 | }
24 |
25 | fun Instant.toReadableDuration(
26 | instant: Instant = Clock.System.now(),
27 | short: Boolean = false
28 | ): String {
29 | val duration = (instant - this).absoluteValue
30 | val days = duration.inWholeDays
31 | val hours = duration.inWholeHours % 24
32 | val minutes = duration.inWholeMinutes % 60
33 | val seconds = duration.inWholeSeconds % 60
34 | val years = duration.inWholeDays / 365
35 | val yName = if (short) "y" else "years"
36 | val dName = if (short) "d" else "days"
37 | val hName = if (short) "h" else "hours"
38 | val mnName = if (short) "m" else "minutes"
39 | val sName = if (short) "s" else "seconds"
40 | return when {
41 | years > 0 -> "$years $yName ${days % 365} $dName"
42 | days > 0 -> "$days $dName $hours $hName"
43 | hours > 0 -> "$hours $hName $minutes $mnName"
44 | else -> "$minutes $mnName $seconds $sName"
45 | }
46 | }
47 |
48 | fun Instant.toHumanReadable(
49 | timeZone: TimeZone = TimeZone.currentSystemDefault()
50 | ): String {
51 | val duration = Clock.System.now() - this
52 | return if (duration.absoluteValue.inWholeDays > 0) {
53 | this.toReadableDate(timeZone) + " " + this.toReadableTime(timeZone)
54 | } else {
55 | this.toReadableDuration(short = true)
56 | }
57 | }
58 |
59 | fun secondsToTime(seconds: Long): String {
60 | val hours = seconds / 3600
61 | val minutes = (seconds % 3600) / 60
62 | val remainingSeconds = seconds % 60
63 |
64 | return "$hours h $minutes m $remainingSeconds s"
65 | }
66 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/modules/common/features/preferences/pref.vm.kt:
--------------------------------------------------------------------------------
1 | package modules.common.features.preferences
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.Preferences
5 | import arrow.core.Option
6 | import arrow.core.toOption
7 | import arrow.fx.coroutines.parZip
8 | import cafe.adriel.voyager.core.model.ScreenModel
9 | import cafe.adriel.voyager.core.model.screenModelScope
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.first
12 | import kotlinx.coroutines.flow.map
13 | import kotlinx.coroutines.launch
14 | import kotlinx.coroutines.runBlocking
15 |
16 | class PrefVM(
17 | private val dataStore: DataStore
18 | ) : ScreenModel {
19 |
20 | fun updatePref(prefItem: PrefItem) = screenModelScope.launch {
21 | dataStore.updateData {
22 | val pref = it.toMutablePreferences()
23 | pref[prefItem.key] = prefItem.value
24 | pref
25 | }
26 | }
27 |
28 | fun getPref(
29 | key: Preferences.Key,
30 | defaultValue: T? = null
31 | ): Flow