├── .gitignore ├── .idea ├── .gitignore ├── artifacts │ ├── compose_ui_desktop.xml │ ├── desktop_jvm.xml │ ├── main_desktop.xml │ ├── root_desktop.xml │ └── utils_desktop.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── jarRepositories.xml ├── kotlinScripting.xml ├── misc.xml └── vcs.xml ├── README.md ├── androidApp ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── nikolam │ │ └── kmm_weather │ │ └── androidApp │ │ ├── App.kt │ │ ├── Color.kt │ │ ├── MainActivity.kt │ │ ├── Shape.kt │ │ ├── Theme.kt │ │ └── Type.kt │ └── res │ └── values │ └── colors.xml ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── buildSrc │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── AndroidGradle.kt │ │ └── Deps.kt └── src │ └── main │ └── kotlin │ ├── android-setup.gradle.kts │ ├── multiplatform-compose-setup.gradle.kts │ └── multiplatform-setup.gradle.kts ├── common ├── compose-ui │ ├── build.gradle.kts │ └── src │ │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── nikolam │ │ │ │ └── kmm_weather │ │ │ │ └── ui │ │ │ │ ├── Composables.kt │ │ │ │ └── Utils.kt │ │ └── res │ │ │ └── drawable │ │ │ ├── angry_clouds.xml │ │ │ ├── cloudy.xml │ │ │ ├── day_clear.xml │ │ │ ├── day_partial_cloud.xml │ │ │ ├── day_rain.xml │ │ │ ├── day_rain_thunder.xml │ │ │ ├── day_sleet.xml │ │ │ ├── day_snow.xml │ │ │ ├── day_snow_thunder.xml │ │ │ ├── fog.xml │ │ │ ├── ic_air_black_24dp.xml │ │ │ ├── ic_water_drop_black_24dp.xml │ │ │ ├── mist.xml │ │ │ ├── night_full_moon_clear.xml │ │ │ ├── night_full_moon_partial_cloud.xml │ │ │ ├── night_full_moon_rain.xml │ │ │ ├── night_full_moon_rain_thunder.xml │ │ │ ├── night_full_moon_sleet.xml │ │ │ ├── night_full_moon_snow.xml │ │ │ ├── night_full_moon_snow_thunder.xml │ │ │ ├── night_half_moon_clear.xml │ │ │ ├── night_half_moon_partial_cloud.xml │ │ │ ├── night_half_moon_rain.xml │ │ │ ├── night_half_moon_rain_thunder.xml │ │ │ ├── night_half_moon_sleet.xml │ │ │ ├── night_half_moon_snow.xml │ │ │ ├── night_half_moon_snow_thunder.xml │ │ │ ├── overcast.xml │ │ │ ├── rain.xml │ │ │ ├── rain_thunder.xml │ │ │ ├── sleet.xml │ │ │ ├── snow.xml │ │ │ ├── snow_thunder.xml │ │ │ ├── thunder.xml │ │ │ ├── tornado.xml │ │ │ └── wind.xml │ │ ├── commonMain │ │ └── kotlin │ │ │ └── com │ │ │ └── nikolam │ │ │ └── kmm_weather │ │ │ └── ui │ │ │ ├── Composables.kt │ │ │ ├── Utils.kt │ │ │ ├── WeatherMainContent.kt │ │ │ ├── WeatherRootContent.kt │ │ │ └── colors.kt │ │ └── desktopMain │ │ ├── kotlin │ │ └── com.nikolam.kmm_weather.ui │ │ │ └── Composables.kt │ │ └── resources │ │ └── images │ │ ├── air.png │ │ ├── angry_clouds.png │ │ ├── cloudy.png │ │ ├── day_clear.png │ │ ├── day_partial_cloud.png │ │ ├── day_rain.png │ │ ├── day_rain_thunder.png │ │ ├── day_sleet.png │ │ ├── day_snow.png │ │ ├── day_snow_thunder.png │ │ ├── fog.png │ │ ├── mist.png │ │ ├── night_full_moon_clear.png │ │ ├── night_full_moon_partial_cloud.png │ │ ├── night_full_moon_rain.png │ │ ├── night_full_moon_rain_thunder.png │ │ ├── night_full_moon_sleet.png │ │ ├── night_full_moon_snow.png │ │ ├── night_full_moon_snow_thunder.png │ │ ├── night_half_moon_clear.png │ │ ├── night_half_moon_partial_cloud.png │ │ ├── night_half_moon_rain.png │ │ ├── night_half_moon_rain_thunder.png │ │ ├── night_half_moon_sleet.png │ │ ├── night_half_moon_snow.png │ │ ├── night_half_moon_snow_thunder.png │ │ ├── overcast.png │ │ ├── rain.png │ │ ├── rain_thunder.png │ │ ├── sleet.png │ │ ├── snow.png │ │ ├── snow_thunder.png │ │ ├── thunder.png │ │ ├── tornado.png │ │ ├── water_drop.png │ │ └── wind.png ├── main │ ├── build.gradle.kts │ └── src │ │ ├── androidMain │ │ └── AndroidManifest.xml │ │ └── commonMain │ │ └── kotlin │ │ └── com.nikolam.kmm_weather.common.main │ │ ├── WeatherMain.kt │ │ ├── data │ │ ├── model │ │ │ ├── CurrentWeatherModel.kt │ │ │ └── CurrentWeatherNetworkModel.kt │ │ └── network │ │ │ └── WeatherAPI.kt │ │ ├── integration │ │ ├── Mappers.kt │ │ └── WeatherMainComponent.kt │ │ └── store │ │ ├── WeatherMainStore.kt │ │ └── WeatherMainStoreProvider.kt ├── root │ ├── build.gradle.kts │ └── src │ │ ├── androidMain │ │ └── AndroidManifest.xml │ │ └── commonMain │ │ └── kotlin │ │ └── com.nikolam.kmm_weather.common.root │ │ ├── WeatherRoot.kt │ │ └── integration │ │ └── WeatherRootComponent.kt └── utils │ ├── build.gradle.kts │ └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── com.nikolam.kmm_weather.common.utils │ │ └── PlatformServiceLocator.kt │ ├── commonMain │ └── kotlin │ │ └── com.nikolam.kmm_weather.common.utils │ │ ├── InstanceKeeperExt.kt │ │ ├── PlatformServiceLocator.kt │ │ ├── ReaktiveExt.kt │ │ └── StoreExt.kt │ ├── desktopMain │ └── kotlin │ │ └── com.nikolam.kmm_weather.common.utils │ │ └── PlatformServiceLocator.kt │ └── iosMain │ └── kotlin │ └── com.nikolam.kmm_weather.common.utils │ └── PlatformServiceLocator.kt ├── desktop ├── build.gradle.kts └── src │ └── jvmMain │ └── kotlin │ └── com.nikolam.kmm_weather.desktop │ └── Main.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── iosApp ├── kmm_weather.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── kmm_weather │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── cloudy.imageset │ │ ├── Contents.json │ │ └── cloudy.svg │ ├── day_clear.imageset │ │ ├── Contents.json │ │ └── day_clear.svg │ ├── day_partial_cloud.imageset │ │ ├── Contents.json │ │ └── day_partial_cloud.svg │ ├── day_rain.imageset │ │ ├── Contents.json │ │ └── day_rain.svg │ ├── day_rain_thunder.imageset │ │ ├── Contents.json │ │ └── day_rain_thunder.svg │ ├── day_sleet.imageset │ │ ├── Contents.json │ │ └── day_sleet.svg │ ├── day_snow.imageset │ │ ├── Contents.json │ │ └── day_snow.svg │ ├── day_snow_thunder.imageset │ │ ├── Contents.json │ │ └── day_snow_thunder.svg │ ├── fog.imageset │ │ ├── Contents.json │ │ └── fog.svg │ ├── mist.imageset │ │ ├── Contents.json │ │ └── mist.svg │ ├── night_clear.imageset │ │ ├── Contents.json │ │ └── night_clear.svg │ ├── night_partial_cloud.imageset │ │ ├── Contents.json │ │ └── night_partial_cloud.svg │ ├── night_rain.imageset │ │ ├── Contents.json │ │ └── night_rain.svg │ ├── night_rain_thunder.imageset │ │ ├── Contents.json │ │ └── night_rain_thunder.svg │ ├── night_sleet.imageset │ │ ├── Contents.json │ │ └── night_sleet.svg │ ├── night_snow.imageset │ │ ├── Contents.json │ │ └── night_snow.svg │ ├── night_snow_thunder.imageset │ │ ├── Contents.json │ │ └── night_snow_thunder.svg │ ├── overcast.imageset │ │ ├── Contents.json │ │ └── overcast.svg │ ├── rain.imageset │ │ ├── Contents.json │ │ └── rain.svg │ ├── rain_thunder.imageset │ │ ├── Contents.json │ │ └── rain_thunder.svg │ ├── sleet.imageset │ │ ├── Contents.json │ │ └── sleet.svg │ ├── snow.imageset │ │ ├── Contents.json │ │ └── snow.svg │ ├── snow_thunder.imageset │ │ ├── Contents.json │ │ └── snow_thunder.svg │ ├── thunder.imageset │ │ ├── Contents.json │ │ └── thunder.svg │ ├── tornado.imageset │ │ ├── Contents.json │ │ └── tornado.svg │ └── wind.imageset │ │ ├── Contents.json │ │ └── wind.svg │ ├── ComponentHolder.swift │ ├── ContentView.swift │ ├── Info.plist │ ├── LaunchScreen.storyboard │ ├── MainView.swift │ ├── MutableValueBuilder.swift │ ├── ObservableValue.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── RootView.swift │ ├── SceneDelegate.swift │ ├── SearchBox.swift │ ├── SimpleRouterState.swift │ ├── Utils.swift │ └── WeatherAppDelegate.swift ├── settings.gradle.kts └── showcase ├── android_dark.png ├── android_light.png ├── desktop.png └── ios.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | 17 | # Built application files 18 | *.apk 19 | *.aar 20 | *.ap_ 21 | *.aab 22 | 23 | # Files for the ART/Dalvik VM 24 | *.dex 25 | 26 | # Java class files 27 | *.class 28 | 29 | # Generated files 30 | bin/ 31 | gen/ 32 | out/ 33 | # Uncomment the following line in case you need and you don't have the release build type files in your app 34 | release/ 35 | 36 | 37 | # Xcode 38 | # 39 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 40 | 41 | ## User settings 42 | xcuserdata/ 43 | 44 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 45 | *.xcscmblueprint 46 | *.xccheckout 47 | 48 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 49 | build/ 50 | DerivedData/ 51 | *.moved-aside 52 | *.pbxuser 53 | !default.pbxuser 54 | *.mode1v3 55 | !default.mode1v3 56 | *.mode2v3 57 | !default.mode2v3 58 | *.perspectivev3 59 | !default.perspectivev3 60 | 61 | ## Obj-C/Swift specific 62 | *.hmap 63 | 64 | ## App packaging 65 | *.ipa 66 | *.dSYM.zip 67 | *.dSYM 68 | 69 | ## Playgrounds 70 | timeline.xctimeline 71 | playground.xcworkspace 72 | 73 | # Swift Package Manager 74 | # 75 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 76 | # Packages/ 77 | # Package.pins 78 | # Package.resolved 79 | # *.xcodeproj 80 | # 81 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 82 | # hence it is not needed unless you have added a package configuration file to your project 83 | # .swiftpm 84 | 85 | .build/ 86 | 87 | # CocoaPods 88 | # 89 | # We recommend against adding the Pods directory to your .gitignore. However 90 | # you should judge for yourself, the pros and cons are mentioned at: 91 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 92 | # 93 | # Pods/ 94 | # 95 | # Add this line if you want to avoid checking in source code from the Xcode workspace 96 | # *.xcworkspace 97 | 98 | # Carthage 99 | # 100 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 101 | # Carthage/Checkouts 102 | 103 | Carthage/Build/ 104 | 105 | # Accio dependency management 106 | Dependencies/ 107 | .accio/ 108 | 109 | # fastlane 110 | # 111 | # It is recommended to not store the screenshots in the git repo. 112 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 113 | # For more information about the recommended setup visit: 114 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 115 | 116 | fastlane/report.xml 117 | fastlane/Preview.html 118 | fastlane/screenshots/**/*.png 119 | fastlane/test_output 120 | 121 | # Code Injection 122 | # 123 | # After new code Injection tools there's a generated folder /iOSInjectionProject 124 | # https://github.com/johnno1962/injectionforxcode 125 | 126 | iOSInjectionProject/ -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/artifacts/compose_ui_desktop.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/common/compose-ui/build/libs 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/artifacts/desktop_jvm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/desktop/build/libs 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/artifacts/main_desktop.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/common/main/build/libs 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/artifacts/root_desktop.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/common/root/build/libs 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/artifacts/utils_desktop.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/common/utils/build/libs 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 26 | 27 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/kotlinScripting.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Kotlin Multiplatform Sample Weather App 3 | 4 | This is a sample weather app made to play around with Kotlin Multiplatform. I tried to strive for maximum code sharing between platforms, all the business logic is shared. Only thing that is platform depended is the native UI. Jetpack Compose for Desktop and Android and SwiftUI for IOS. 5 | 6 | This kind of architecture is only achiveable thanks to the work of 7 | Arkadii Ivanov and his libraries [Decompose](https://github.com/arkivanov/Decompose) and [MVIKotlin](https://github.com/arkivanov/MVIKotlin). 8 | 9 | Kotlin Multiplatform and libraries supporting it are highly violatile at the moment and in the early stages of development. It can be kinda a pain in the bottom to work with but nevertheless it has been a great experience. I can't wait to see what the future holds, I truly believe this will be the future of cross platform development. Just thinking about using Kotlin to develop a codebase shared across platforms is almost like a dream. 10 | 11 | 12 | ## UI 13 | 14 | The Android and Desktop app share most of the UI code that resides in the :common:compose-ui. There are slight platform specific implementations that allow for dark mode support on Android. Besides that, it's super easy to develop a single UI and have it work on Android and Desktop. 15 | 16 | The IOS UI is made with the help of SwiftUI and considering it's pretty similar to Jetpack Compose it's also an easy switch. You can pretty much just go and 1 to 1 copy the Compose code with the respected SwiftUI alternatives. 17 | 18 | 19 | ## Demo 20 | 21 | ### Android app 22 | 23 |                 24 | 25 | Light Mode                                                           Dark Mode 26 | 27 | 28 | 29 | ### Desktop app 30 | 31 | 32 | 33 | 34 | ### Ios app 35 | 36 | 37 | 38 | 39 | ## Shared code 40 | 41 | With the help of the amazing libraries from [Arkadii Ivanov](https://github.com/arkivanov) like [MVIKotlin](https://github.com/arkivanov/MVIKotlin) and [Decompose](https://github.com/arkivanov/Decompose) sharing 90%+ of the business logic is achieveable. Currently, albeit a simple Weather app, I am using the exact same models, logic, use-cases for all platforms. That means, with the single common module I am able to have the same logic running on Android, Ios and Desktop (Mac, Linux and Windows!). Which makes testing easier, development (as most of the apps share functionality across platforms) and it's just nice to write in Kotlin. 42 | -------------------------------------------------------------------------------- /androidApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.compose 2 | 3 | plugins { 4 | id("com.android.application") 5 | kotlin("android") 6 | id("org.jetbrains.compose") 7 | } 8 | 9 | android { 10 | compileSdkVersion(AndroidGradle.TARGETSDK) 11 | defaultConfig { 12 | applicationId = "com.nikolam.kmm_weather.androidApp" 13 | minSdkVersion(AndroidGradle.MINSDK) 14 | targetSdkVersion(AndroidGradle.TARGETSDK) 15 | versionCode = 1 16 | versionName = "1.0" 17 | } 18 | compileOptions { 19 | sourceCompatibility = JavaVersion.VERSION_1_8 20 | targetCompatibility = JavaVersion.VERSION_1_8 21 | } 22 | 23 | packagingOptions { 24 | exclude("META-INF/*") 25 | } 26 | } 27 | dependencies { 28 | implementation(project(":common:utils")) 29 | implementation(project(":common:root")) 30 | implementation(project(":common:compose-ui")) 31 | implementation(compose.material) 32 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) 33 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) 34 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinLogging) 35 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinTimeTravel) 36 | implementation(Deps.ArkIvanov.Decompose.decompose) 37 | implementation(Deps.ArkIvanov.Decompose.extensionsCompose) 38 | implementation(Deps.AndroidX.AppCompat.appCompat) 39 | implementation(Deps.AndroidX.Activity.activityCompose) 40 | 41 | implementation(Deps.Utils.napier) 42 | } -------------------------------------------------------------------------------- /androidApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/nikolam/kmm_weather/androidApp/App.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.androidApp 2 | 3 | import android.app.Application 4 | import com.arkivanov.mvikotlin.timetravel.server.TimeTravelServer 5 | 6 | class App : Application() { 7 | 8 | override fun onCreate() { 9 | super.onCreate() 10 | TimeTravelServer().start() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/nikolam/kmm_weather/androidApp/Color.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.androidApp 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val DarkPurple = Color(0xFF5550F2) 6 | val SkyBlue = Color(0xFF8CCEF4) 7 | val MediumPurple = Color(0xFF6187F6) 8 | 9 | val DarkDarkPurple = Color(0xFF00002c) 10 | val DarkSkyBlue = Color(0xFF443381) 11 | val DarkMediumPurple = Color(0xFF110c54) 12 | val DarkTextColor = Color(0xFF87a1ff) -------------------------------------------------------------------------------- /androidApp/src/main/java/com/nikolam/kmm_weather/androidApp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.androidApp 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.compose.foundation.isSystemInDarkTheme 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.Surface 9 | import androidx.compose.ui.graphics.toArgb 10 | import com.arkivanov.decompose.ComponentContext 11 | import com.arkivanov.decompose.extensions.compose.jetbrains.rememberRootComponent 12 | import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory 13 | import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory 14 | import com.arkivanov.mvikotlin.timetravel.store.TimeTravelStoreFactory 15 | import com.nikolam.kmm_weather.common.root.WeatherRoot 16 | import com.nikolam.kmm_weather.common.root.integration.WeatherRootComponent 17 | import com.nikolam.kmm_weather.ui.WeatherRootContent 18 | import io.github.aakira.napier.DebugAntilog 19 | import io.github.aakira.napier.Napier 20 | 21 | class MainActivity : AppCompatActivity() { 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | Napier.base(DebugAntilog("my_weather_tag")) 25 | setContent { 26 | ComposeAppTheme { 27 | if (!isSystemInDarkTheme()) { 28 | window.statusBarColor = DarkPurple.toArgb() 29 | } else { 30 | window.statusBarColor = DarkDarkPurple.toArgb() 31 | } 32 | 33 | Surface(color = MaterialTheme.colors.background) { 34 | WeatherRootContent(rememberRootComponent(::weatherRoot)) 35 | } 36 | } 37 | } 38 | } 39 | 40 | private fun weatherRoot(componentContext: ComponentContext): WeatherRoot = 41 | WeatherRootComponent( 42 | componentContext = componentContext, 43 | storeFactory = LoggingStoreFactory(TimeTravelStoreFactory(DefaultStoreFactory)) 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/nikolam/kmm_weather/androidApp/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.androidApp 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) 12 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/nikolam/kmm_weather/androidApp/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.androidApp 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.graphics.Color 9 | 10 | 11 | private val DarkColorPalette = darkColors( 12 | primary = DarkDarkPurple, 13 | primaryVariant = DarkMediumPurple, 14 | secondary = DarkSkyBlue, 15 | onPrimary = DarkTextColor, 16 | onSecondary = DarkTextColor 17 | ) 18 | 19 | private val LightColorPalette = lightColors( 20 | primary = DarkPurple, 21 | primaryVariant = MediumPurple, 22 | secondary = SkyBlue, 23 | onPrimary = Color.White, 24 | onSecondary = Color.White 25 | 26 | /* Other default colors to override 27 | background = Color.White, 28 | surface = Color.White, 29 | onPrimary = Color.White, 30 | onSecondary = Color.Black, 31 | onBackground = Color.Black, 32 | onSurface = Color.Black, 33 | */ 34 | ) 35 | 36 | @Composable 37 | fun ComposeAppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { 38 | val colors = if (darkTheme) { 39 | DarkColorPalette 40 | } else { 41 | LightColorPalette 42 | } 43 | 44 | MaterialTheme( 45 | colors = colors, 46 | typography = typography, 47 | shapes = shapes, 48 | content = content 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/nikolam/kmm_weather/androidApp/Type.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.androidApp 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | ) 17 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | apply(plugin = "com.github.ben-manes.versions") 6 | 7 | allprojects { 8 | repositories { 9 | google() 10 | mavenCentral() 11 | mavenLocal() 12 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 13 | } 14 | 15 | 16 | // afterEvaluate { 17 | // tasks.withType { 18 | // kotlinOptions { 19 | // useIR = true 20 | // if (configurations.findByName("kotlinCompilerPluginClasspath") 21 | // ?.dependencies 22 | // ?.any { it.group == "androidx.compose.compiler" } == true 23 | // ) { 24 | // freeCompilerArgs += listOf( 25 | // "-P", 26 | // "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true" 27 | // ) 28 | // } 29 | // } 30 | // } 31 | // } 32 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | // TODO: remove after new build is published 7 | mavenLocal() 8 | google() 9 | mavenCentral() 10 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 11 | gradlePluginPortal() 12 | } 13 | 14 | dependencies { 15 | implementation(Deps.JetBrains.Compose.gradlePlugin) 16 | implementation(Deps.JetBrains.Kotlin.gradlePlugin) 17 | implementation(Deps.Android.Tools.Build.gradlePlugin) 18 | implementation(Deps.Squareup.SQLDelight.gradlePlugin) 19 | implementation(Deps.JetBrains.Kotlin.serializationGradle) 20 | implementation(Deps.Utils.dependenciesCheck) 21 | } 22 | 23 | kotlin { 24 | // Add Deps to compilation, so it will become available in main project 25 | sourceSets.getByName("main").kotlin.srcDir("buildSrc/src/main/kotlin") 26 | } 27 | -------------------------------------------------------------------------------- /buildSrc/buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/buildSrc/src/main/kotlin/AndroidGradle.kt: -------------------------------------------------------------------------------- 1 | object AndroidGradle { 2 | val MINSDK = 21 3 | val MAXSDK = 30 4 | val TARGETSDK = 30 5 | } 6 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/android-setup.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | } 4 | 5 | android { 6 | compileSdkVersion(AndroidGradle.TARGETSDK) 7 | 8 | defaultConfig { 9 | minSdkVersion(AndroidGradle.MINSDK) 10 | targetSdkVersion(AndroidGradle.TARGETSDK) 11 | } 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | sourceSets { 19 | named("main") { 20 | manifest.srcFile("src/androidMain/AndroidManifest.xml") 21 | res.srcDirs("src/androidMain/res") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/multiplatform-compose-setup.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.compose 2 | 3 | plugins { 4 | id("com.android.library") 5 | id("kotlin-multiplatform") 6 | id("org.jetbrains.compose") 7 | } 8 | 9 | ////workaround for https://youtrack.jetbrains.com/issue/KT-43944 10 | //android { 11 | // configurations { 12 | // create("androidTestApi") 13 | // create("androidTestDebugApi") 14 | // create("androidTestReleaseApi") 15 | // create("testApi") 16 | // create("testDebugApi") 17 | // create("testReleaseApi") 18 | // } 19 | //} 20 | kotlin { 21 | jvm("desktop") 22 | android() 23 | 24 | sourceSets { 25 | named("commonMain") { 26 | dependencies { 27 | implementation(compose.runtime) 28 | implementation(compose.foundation) 29 | implementation(compose.material) 30 | } 31 | } 32 | 33 | named("androidMain") { 34 | dependencies { 35 | implementation("androidx.appcompat:appcompat:1.3.0-beta01") 36 | implementation("androidx.core:core-ktx:1.3.1") 37 | } 38 | } 39 | 40 | named("desktopMain") { 41 | dependencies { 42 | implementation(compose.desktop.common) 43 | } 44 | } 45 | } 46 | 47 | tasks.withType { 48 | kotlinOptions.jvmTarget = "1.8" 49 | } 50 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-multiplatform") 4 | } 5 | 6 | ////workaround for https://youtrack.jetbrains.com/issue/KT-43944 7 | //android { 8 | // configurations { 9 | // create("androidTestApi") 10 | // create("androidTestDebugApi") 11 | // create("androidTestReleaseApi") 12 | // create("testApi") 13 | // create("testDebugApi") 14 | // create("testReleaseApi") 15 | // } 16 | //} 17 | 18 | kotlin { 19 | jvm("desktop") 20 | android() 21 | ios() 22 | 23 | sourceSets { 24 | named("commonTest") { 25 | dependencies { 26 | implementation(Deps.JetBrains.Kotlin.testCommon) 27 | implementation(Deps.JetBrains.Kotlin.testAnnotationsCommon) 28 | } 29 | } 30 | 31 | named("androidTest") { 32 | dependencies { 33 | implementation(Deps.JetBrains.Kotlin.testJunit) 34 | } 35 | } 36 | named("desktopTest") { 37 | dependencies { 38 | implementation(Deps.JetBrains.Kotlin.testJunit) 39 | } 40 | } 41 | } 42 | 43 | tasks.withType { 44 | kotlinOptions.jvmTarget = "1.8" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/compose-ui/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | import org.jetbrains.compose.compose 3 | 4 | plugins { 5 | id("multiplatform-compose-setup") 6 | id("android-setup") 7 | } 8 | 9 | kotlin { 10 | sourceSets { 11 | named("commonMain") { 12 | dependencies { 13 | implementation(project(":common:main")) 14 | implementation(project(":common:root")) 15 | implementation(Deps.ArkIvanov.Decompose.decompose) 16 | implementation(Deps.ArkIvanov.Decompose.extensionsCompose) 17 | 18 | implementation(Deps.Utils.napier) 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/kotlin/com/nikolam/kmm_weather/ui/Composables.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.ui 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.isSystemInDarkTheme 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.graphics.ColorFilter 8 | import androidx.compose.ui.res.painterResource 9 | import com.nikolam.kmm_weather.common.ui.R 10 | 11 | @Composable 12 | actual fun isDarkMode(): Boolean { 13 | return isSystemInDarkTheme() 14 | } 15 | 16 | @Composable 17 | actual fun KMPImage( 18 | id: Int, 19 | modifier: Modifier, 20 | colorFilter: ColorFilter?, 21 | contentDescription: String 22 | ) { 23 | var resId = R.drawable.angry_clouds 24 | 25 | when (id) { 26 | 1 -> { 27 | resId = R.drawable.ic_water_drop_black_24dp 28 | } 29 | 2 -> { 30 | resId = R.drawable.ic_air_black_24dp 31 | } 32 | in 200..232 -> { 33 | resId = R.drawable.thunder 34 | } 35 | in 300..321 -> { 36 | resId = R.drawable.day_rain 37 | } 38 | in 500..531 -> { 39 | resId = R.drawable.rain 40 | } 41 | in 600..622 -> { 42 | resId = R.drawable.snow 43 | } 44 | 800 -> { 45 | resId = R.drawable.day_clear 46 | } 47 | in 801..804 -> { 48 | resId = R.drawable.cloudy 49 | } 50 | else -> { 51 | resId = R.drawable.day_clear 52 | } 53 | } 54 | Image( 55 | painter = painterResource(resId), modifier = modifier, 56 | contentDescription = contentDescription, 57 | colorFilter = colorFilter 58 | ) 59 | } -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/kotlin/com/nikolam/kmm_weather/ui/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.ui 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.view.inputmethod.InputMethodManager 6 | 7 | 8 | fun hideKeyboard(context: Context) { 9 | val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager 10 | imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); 11 | } 12 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/angry_clouds.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/cloudy.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/fog.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | 20 | 24 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/ic_air_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/ic_water_drop_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/mist.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | 20 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/night_full_moon_clear.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 67 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/night_half_moon_clear.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 31 | 36 | 41 | 46 | 47 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/rain.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | 20 | 23 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/rain_thunder.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 38 | 41 | 46 | 49 | 52 | 53 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/sleet.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 18 | 21 | 24 | 26 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | -------------------------------------------------------------------------------- /common/compose-ui/src/androidMain/res/drawable/wind.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | 20 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /common/compose-ui/src/commonMain/kotlin/com/nikolam/kmm_weather/ui/Composables.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.graphics.ColorFilter 6 | import androidx.compose.ui.graphics.ImageBitmap 7 | 8 | 9 | @Composable 10 | expect fun isDarkMode(): Boolean 11 | 12 | @Composable 13 | expect fun KMPImage( 14 | id: Int, 15 | modifier: Modifier, 16 | colorFilter: ColorFilter?, 17 | contentDescription: String 18 | ) 19 | 20 | //@Composable 21 | //expect fun SearchBox(modifier: Modifier) 22 | -------------------------------------------------------------------------------- /common/compose-ui/src/commonMain/kotlin/com/nikolam/kmm_weather/ui/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.ui 2 | 3 | import kotlin.math.roundToInt 4 | 5 | fun String.toTempUnit(unit : String) : String{ 6 | return if (unit == "C") "$this°" 7 | else (this.toInt() * 1.8f + 32).roundToInt().toString() +"F" 8 | } -------------------------------------------------------------------------------- /common/compose-ui/src/commonMain/kotlin/com/nikolam/kmm_weather/ui/WeatherRootContent.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("EXPERIMENTAL_API_USAGE") 2 | 3 | package com.nikolam.kmm_weather.ui 4 | 5 | import androidx.compose.runtime.Composable 6 | import com.arkivanov.decompose.extensions.compose.jetbrains.Children 7 | import com.arkivanov.decompose.extensions.compose.jetbrains.animation.child.crossfadeScale 8 | import com.nikolam.kmm_weather.common.root.WeatherRoot 9 | 10 | @Composable 11 | fun WeatherRootContent(component: WeatherRoot) { 12 | Children(routerState = component.routerState, animation = crossfadeScale()) { 13 | when (val child = it.instance) { 14 | is WeatherRoot.Child.Main -> WeatherMainContent(child.component) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/compose-ui/src/commonMain/kotlin/com/nikolam/kmm_weather/ui/colors.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.ui 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val DarkPurple = Color(0xFF5550F2) 6 | val SkyBlue = Color(0xFF8CCEF4) 7 | val MediumPurple = Color(0xFF6187F6) 8 | 9 | val DarkDarkPurple = Color(0xFF00002c) 10 | val DarkSkyBlue = Color(0xFF443381) 11 | val DarkMediumPurple = Color(0xFF110c54) 12 | val DarkTextColor = Color(0xFF87a1ff) -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/kotlin/com.nikolam.kmm_weather.ui/Composables.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.ui 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.graphics.ColorFilter 7 | import androidx.compose.ui.res.imageResource 8 | 9 | @Composable 10 | actual fun isDarkMode(): Boolean { 11 | return false //isSystemInDarkTheme() 12 | } 13 | 14 | @Composable 15 | actual fun KMPImage( 16 | id: Int, 17 | modifier: Modifier, 18 | colorFilter: ColorFilter?, 19 | contentDescription: String 20 | ) { 21 | 22 | var fileName = "images/day_clear.png" 23 | 24 | when (id) { 25 | 1 -> { 26 | fileName = "images/water_drop.png" 27 | } 28 | 2 -> { 29 | fileName = "images/air.png" 30 | } 31 | in 200..232 -> { 32 | fileName = "images/thunder.png" 33 | } 34 | in 300..321 -> { 35 | fileName = "images/day_rain.png" 36 | } 37 | in 500..531 -> { 38 | fileName = "images/rain.png" 39 | } 40 | in 600..622 -> { 41 | fileName = "images/snow.png" 42 | } 43 | 800 -> { 44 | fileName = "images/day_clear.png" 45 | } 46 | in 801..804 -> { 47 | fileName = "images/cloudy.png" 48 | } 49 | else -> { 50 | fileName = "images/day_clear.png" 51 | } 52 | } 53 | 54 | Image(imageResource(fileName), 55 | modifier = modifier, 56 | contentDescription = contentDescription, 57 | colorFilter = colorFilter 58 | ) 59 | } 60 | 61 | //@Composable 62 | //actual fun SearchBox(modifier: Modifier) { 63 | //} -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/air.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/air.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/angry_clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/angry_clouds.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/cloudy.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/day_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/day_clear.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/day_partial_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/day_partial_cloud.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/day_rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/day_rain.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/day_rain_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/day_rain_thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/day_sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/day_sleet.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/day_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/day_snow.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/day_snow_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/day_snow_thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/fog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/fog.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/mist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/mist.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_full_moon_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_full_moon_clear.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_full_moon_partial_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_full_moon_partial_cloud.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_full_moon_rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_full_moon_rain.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_full_moon_rain_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_full_moon_rain_thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_full_moon_sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_full_moon_sleet.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_full_moon_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_full_moon_snow.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_full_moon_snow_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_full_moon_snow_thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_half_moon_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_half_moon_clear.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_half_moon_partial_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_half_moon_partial_cloud.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_half_moon_rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_half_moon_rain.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_half_moon_rain_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_half_moon_rain_thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_half_moon_sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_half_moon_sleet.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_half_moon_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_half_moon_snow.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/night_half_moon_snow_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/night_half_moon_snow_thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/overcast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/overcast.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/rain.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/rain_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/rain_thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/sleet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/sleet.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/snow.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/snow_thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/snow_thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/thunder.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/tornado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/tornado.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/water_drop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/water_drop.png -------------------------------------------------------------------------------- /common/compose-ui/src/desktopMain/resources/images/wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/common/compose-ui/src/desktopMain/resources/images/wind.png -------------------------------------------------------------------------------- /common/main/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet 2 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 3 | 4 | plugins { 5 | id("multiplatform-setup") 6 | id("android-setup") 7 | id("kotlinx-serialization") 8 | } 9 | 10 | kotlin { 11 | sourceSets { 12 | named("commonMain") { 13 | dependencies { 14 | implementation(project(":common:utils")) 15 | // implementation(project(":common:database")) 16 | implementation(Deps.ArkIvanov.Decompose.decompose) 17 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) 18 | // implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsReaktive) 19 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinExtensionsCoroutines) 20 | implementation(Deps.Badoo.Reaktive.reaktive) 21 | 22 | implementation(Deps.Utils.napier) 23 | 24 | implementation(Deps.JetBrains.Ktor.core) 25 | implementation(Deps.JetBrains.Ktor.json) 26 | implementation(Deps.JetBrains.Ktor.serialization) 27 | } 28 | } 29 | 30 | named("androidMain") { 31 | dependencies { 32 | implementation(Deps.Utils.napier) 33 | 34 | implementation(Deps.JetBrains.Ktor.android) 35 | implementation(Deps.JetBrains.Ktor.jsonJVM) 36 | implementation(Deps.JetBrains.Ktor.serializationJVM) 37 | //implementation(Deps.JetBrains.Ktor.logging) 38 | } 39 | } 40 | 41 | // named("iosMain") { 42 | // dependencies { 43 | // implementation(Deps.JetBrains.Ktor.ios) 44 | // implementation(Deps.JetBrains.Ktor.iosSerialization) 45 | // // implementation(Deps.JetBrains.Ktor.jsonNative) 46 | // } 47 | // } 48 | 49 | named("commonTest") { 50 | dependencies { 51 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) 52 | implementation(Deps.Badoo.Reaktive.reaktiveTesting) 53 | implementation(Deps.Badoo.Reaktive.utils) 54 | } 55 | } 56 | } 57 | 58 | targets.getByName("iosX64").compilations.forEach { 59 | it.kotlinOptions.freeCompilerArgs += arrayOf("-linker-options", "-lsqlite3") 60 | } 61 | } -------------------------------------------------------------------------------- /common/main/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /common/main/src/commonMain/kotlin/com.nikolam.kmm_weather.common.main/WeatherMain.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.main 2 | 3 | import com.arkivanov.decompose.value.Value 4 | import com.nikolam.kmm_weather.common.main.data.model.CurrentWeatherModel 5 | 6 | interface WeatherMain { 7 | val models : Value 8 | 9 | fun onItemClicked(id : Long) 10 | 11 | data class Model( 12 | val currentWeather : CurrentWeatherModel? = null, 13 | val isLoading : Boolean = true, 14 | val isError : Boolean = true 15 | ) 16 | 17 | sealed class Output { 18 | data class SelectedDay(val id : Long) : Output() 19 | } 20 | 21 | 22 | } -------------------------------------------------------------------------------- /common/main/src/commonMain/kotlin/com.nikolam.kmm_weather.common.main/data/model/CurrentWeatherModel.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.main.data.model 2 | 3 | data class CurrentWeatherModel(val weatherID : Int, val daily : List, val temp: Int, val wind : Int, val humidity : Int, val weatherDesc : String 4 | ) 5 | data class DailyWeatherModel(val weatherID : Int, val index : Int, val temp: Int) -------------------------------------------------------------------------------- /common/main/src/commonMain/kotlin/com.nikolam.kmm_weather.common.main/data/model/CurrentWeatherNetworkModel.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.main.data.model 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class CurrentWeatherNetworkModel( 8 | val lat: Double, 9 | val lon: Double, 10 | val timezone: String, 11 | 12 | @SerialName("timezone_offset") 13 | val timezoneOffset: Long, 14 | 15 | val current: Current, 16 | val hourly: List, 17 | val daily: List, 18 | var alerts: List = listOf() 19 | ) { 20 | fun toBusinessModel(): CurrentWeatherModel { 21 | val newList = arrayListOf() 22 | for (i in daily.indices) { 23 | val temp = daily[i].temp.day 24 | newList.add(DailyWeatherModel(daily[i].weather[0].id.toInt(), i, temp.toInt())) 25 | } 26 | 27 | return CurrentWeatherModel( 28 | weatherID = current.weather[0].id.toInt(), newList, current.temp.toInt(), 29 | current.windSpeed.toInt(), current.humidity.toInt(), current.weather[0].description 30 | ) 31 | } 32 | } 33 | 34 | @Serializable 35 | data class Alert( 36 | @SerialName("sender_name") 37 | val senderName: String, 38 | 39 | val event: String, 40 | val start: Long, 41 | val end: Long, 42 | val description: String 43 | ) 44 | 45 | @Serializable 46 | data class Current( 47 | val dt: Long, 48 | val sunrise: Long? = null, 49 | val sunset: Long? = null, 50 | val temp: Double, 51 | 52 | @SerialName("feels_like") 53 | val feelsLike: Double, 54 | 55 | val pressure: Long, 56 | val humidity: Long, 57 | 58 | @SerialName("dew_point") 59 | val dewPoint: Double, 60 | 61 | val uvi: Double, 62 | val clouds: Long, 63 | val visibility: Long, 64 | 65 | @SerialName("wind_speed") 66 | val windSpeed: Double, 67 | 68 | @SerialName("wind_deg") 69 | val windDeg: Long, 70 | 71 | val weather: List, 72 | 73 | @SerialName("wind_gust") 74 | val windGust: Double? = null 75 | ) 76 | 77 | @Serializable 78 | data class Rain( 79 | @SerialName("1h") 80 | val the1H: Double 81 | ) 82 | 83 | @Serializable 84 | data class Weather( 85 | val id: Long, 86 | val main: String, 87 | val description: String, 88 | val icon: String 89 | ) 90 | 91 | @Serializable 92 | data class Daily( 93 | val dt: Long, 94 | val sunrise: Long, 95 | val sunset: Long, 96 | val moonrise: Long, 97 | val moonset: Long, 98 | 99 | @SerialName("moon_phase") 100 | val moonPhase: Double, 101 | 102 | val temp: Temp, 103 | 104 | @SerialName("feels_like") 105 | val feelsLike: FeelsLike, 106 | 107 | val pressure: Long, 108 | val humidity: Long, 109 | 110 | @SerialName("dew_point") 111 | val dewPoint: Double, 112 | 113 | @SerialName("wind_speed") 114 | val windSpeed: Double, 115 | 116 | @SerialName("wind_deg") 117 | val windDeg: Long, 118 | 119 | val weather: List, 120 | val clouds: Long, 121 | val pop: Double, 122 | val uvi: Double 123 | ) 124 | 125 | @Serializable 126 | data class FeelsLike( 127 | val day: Double, 128 | val night: Double, 129 | val eve: Double, 130 | val morn: Double 131 | ) 132 | 133 | @Serializable 134 | data class Temp( 135 | val day: Double, 136 | val min: Double, 137 | val max: Double, 138 | val night: Double, 139 | val eve: Double, 140 | val morn: Double 141 | ) 142 | -------------------------------------------------------------------------------- /common/main/src/commonMain/kotlin/com.nikolam.kmm_weather.common.main/data/network/WeatherAPI.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.main.data.network 2 | 3 | import com.nikolam.kmm_weather.common.main.data.model.CurrentWeatherModel 4 | import com.nikolam.kmm_weather.common.main.data.model.CurrentWeatherNetworkModel 5 | import io.ktor.client.* 6 | import io.ktor.client.engine.* 7 | import io.ktor.client.features.json.* 8 | import io.ktor.client.features.json.serializer.* 9 | import io.ktor.client.request.* 10 | import io.ktor.client.statement.* 11 | import io.ktor.http.* 12 | import kotlinx.serialization.json.Json 13 | import io.github.aakira.napier.Napier 14 | 15 | class WeatherAPI(clientEngine: HttpClientEngine) { 16 | 17 | private val client = HttpClient(clientEngine) { 18 | install(JsonFeature) { 19 | serializer = KotlinxSerializer() 20 | } 21 | } 22 | 23 | suspend fun getWeather(): CurrentWeatherModel { 24 | // Actually we're able to just return the get()-call and Ktor's JsonFeature will automatically do the 25 | // JSON parsing for us. However, this currently doesn't work with Kotlin/Native as it doesn't support reflection 26 | // and we have to manually use PopularMoviesEntity.serializer() 27 | val response = client.get { 28 | url { 29 | protocol = URLProtocol.HTTPS 30 | host = "api.openweathermap.org/" 31 | encodedPath = "/data/2.5/onecall" 32 | // parameter("q", "Belgrade") 33 | parameter("appid", "f3a39ee4cb7053b4b27f3bbb8bca11c8") 34 | parameter("units", "metric") 35 | parameter("lat", 44.787197) 36 | parameter("lon", 20.457273) 37 | // header(HEADER_AUTHORIZATION, API_KEY.asBearerToken()) 38 | } 39 | } 40 | 41 | val jsonBody = response.readText() 42 | val netModel = Json { 43 | ignoreUnknownKeys = true 44 | }.decodeFromString(CurrentWeatherNetworkModel.serializer(), jsonBody) 45 | 46 | return netModel.toBusinessModel() 47 | } 48 | } -------------------------------------------------------------------------------- /common/main/src/commonMain/kotlin/com.nikolam.kmm_weather.common.main/integration/Mappers.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.main.integration 2 | 3 | import com.nikolam.kmm_weather.common.main.store.WeatherMainStore 4 | import com.nikolam.kmm_weather.common.main.WeatherMain 5 | 6 | internal val STATE_TO: (WeatherMainStore.State) -> WeatherMain.Model = 7 | { 8 | WeatherMain.Model( 9 | currentWeather = it.currentWeather, 10 | isError = it.isError, 11 | isLoading = it.isLoading 12 | ) 13 | } -------------------------------------------------------------------------------- /common/main/src/commonMain/kotlin/com.nikolam.kmm_weather.common.main/integration/WeatherMainComponent.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.main.integration 2 | 3 | import com.nikolam.kmm_weather.common.utils.asValue 4 | import com.arkivanov.decompose.ComponentContext 5 | import com.arkivanov.decompose.value.Value 6 | import com.arkivanov.decompose.value.operator.map 7 | import com.arkivanov.mvikotlin.core.store.StoreFactory 8 | import com.badoo.reaktive.base.Consumer 9 | import com.badoo.reaktive.base.invoke 10 | import com.nikolam.kmm_weather.common.main.store.WeatherMainStoreProvider 11 | import com.nikolam.kmm_weather.common.utils.getStore 12 | import com.nikolam.kmm_weather.common.main.WeatherMain 13 | 14 | class WeatherMainComponent( 15 | componentContext: ComponentContext, 16 | storeFactory: StoreFactory, 17 | private val output: Consumer 18 | ) : WeatherMain, ComponentContext by componentContext { 19 | 20 | private val store = 21 | instanceKeeper.getStore { 22 | WeatherMainStoreProvider( 23 | storeFactory = storeFactory, 24 | /// database = TodoMainStoreDatabase(queries = database.todoDatabaseQueries) 25 | ).provide() 26 | } 27 | 28 | override val models: Value = store.asValue().map(STATE_TO) 29 | 30 | override fun onItemClicked(id: Long) { 31 | output(WeatherMain.Output.SelectedDay(id = id)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common/main/src/commonMain/kotlin/com.nikolam.kmm_weather.common.main/store/WeatherMainStore.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.main.store 2 | 3 | import com.arkivanov.mvikotlin.core.store.Store 4 | import com.nikolam.kmm_weather.common.main.data.model.CurrentWeatherModel 5 | 6 | 7 | internal interface WeatherMainStore : Store { 8 | 9 | sealed class Intent { 10 | } 11 | 12 | data class State( 13 | val currentWeather : CurrentWeatherModel? = null, 14 | val isLoading : Boolean = true, 15 | val isError : Boolean = true 16 | ) 17 | } -------------------------------------------------------------------------------- /common/main/src/commonMain/kotlin/com.nikolam.kmm_weather.common.main/store/WeatherMainStoreProvider.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.main.store 2 | 3 | import com.arkivanov.mvikotlin.core.store.Reducer 4 | import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper 5 | import com.arkivanov.mvikotlin.core.store.Store 6 | import com.arkivanov.mvikotlin.core.store.StoreFactory 7 | import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor 8 | import com.nikolam.kmm_weather.common.main.data.model.CurrentWeatherModel 9 | import com.nikolam.kmm_weather.common.main.data.network.WeatherAPI 10 | import com.nikolam.kmm_weather.common.main.store.WeatherMainStore.State 11 | import com.nikolam.kmm_weather.common.utils.PlatformServiceLocator 12 | import io.github.aakira.napier.Napier 13 | 14 | internal class WeatherMainStoreProvider( 15 | private val storeFactory: StoreFactory, 16 | ) { 17 | 18 | private val weatherAPI by lazy { WeatherAPI(PlatformServiceLocator.httpClientEngine) } 19 | 20 | fun provide(): WeatherMainStore = 21 | object : WeatherMainStore, 22 | Store by storeFactory.create( 23 | name = "TodoListStore", 24 | initialState = State(), 25 | bootstrapper = SimpleBootstrapper(Unit), 26 | executorFactory = ::ExecutorImpl, 27 | reducer = ReducerImpl 28 | ) {} 29 | 30 | 31 | private sealed class Result { 32 | data class WeatherLoaded( 33 | val currentWeather: CurrentWeatherModel 34 | ) : Result() 35 | 36 | data class ItemsLoadFailed(val err: Int) : Result() 37 | } 38 | 39 | private inner class ExecutorImpl : 40 | SuspendExecutor() { 41 | override suspend fun executeAction(action: Unit, getState: () -> State) { 42 | try { 43 | val m = weatherAPI.getWeather() 44 | Napier.d("Sucess $m\n", tag = "my_tag") 45 | 46 | dispatch(Result.WeatherLoaded(m)) 47 | } catch (e: Exception) { 48 | print(e.message) 49 | Napier.e(e.toString(), e, tag = "my_tag") 50 | dispatch(Result.ItemsLoadFailed(-1)) 51 | } 52 | } 53 | } 54 | 55 | private object ReducerImpl : Reducer { 56 | override fun State.reduce(result: Result): State = 57 | when (result) { 58 | is Result.WeatherLoaded -> copy( 59 | currentWeather = result.currentWeather, 60 | isLoading = false, 61 | isError = false 62 | ) 63 | is Result.ItemsLoadFailed -> copy(isLoading = false, isError = true) 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /common/root/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 2 | 3 | plugins { 4 | id("multiplatform-setup") 5 | id("android-setup") 6 | id("kotlin-parcelize") 7 | } 8 | 9 | kotlin { 10 | ios { 11 | binaries { 12 | framework { 13 | baseName = "Weather" 14 | export(project(":common:main")) 15 | export(project(":common:utils")) 16 | export(Deps.ArkIvanov.Decompose.decompose) 17 | export(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) 18 | } 19 | } 20 | } 21 | 22 | sourceSets { 23 | named("commonMain") { 24 | dependencies { 25 | implementation(project(":common:utils")) 26 | // implementation(project(":common:database")) 27 | implementation(project(":common:main")) 28 | // implementation(project(":common:edit")) 29 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) 30 | implementation(Deps.ArkIvanov.Decompose.decompose) 31 | implementation(Deps.Badoo.Reaktive.reaktive) 32 | } 33 | } 34 | } 35 | 36 | sourceSets { 37 | named("iosMain") { 38 | dependencies { 39 | // api(project(":common:database")) 40 | api(project(":common:main")) 41 | // api(project(":common:edit")) 42 | api(Deps.ArkIvanov.Decompose.decompose) 43 | api(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) 44 | } 45 | } 46 | } 47 | } 48 | 49 | fun getIosTarget(): String { 50 | val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator" 51 | 52 | return if (sdkName.startsWith("iphoneos")) "iosArm64" else "iosX64" 53 | } 54 | 55 | val packForXcode by tasks.creating(Sync::class) { 56 | group = "build" 57 | val mode = System.getenv("CONFIGURATION") ?: "DEBUG" 58 | val targetName = getIosTarget() 59 | val framework = kotlin.targets.getByName(targetName).binaries.getFramework(mode) 60 | inputs.property("mode", mode) 61 | dependsOn(framework.linkTask) 62 | val targetDir = File(buildDir, "xcode-frameworks") 63 | from(framework.outputDirectory) 64 | into(targetDir) 65 | } 66 | -------------------------------------------------------------------------------- /common/root/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /common/root/src/commonMain/kotlin/com.nikolam.kmm_weather.common.root/WeatherRoot.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.root 2 | 3 | import com.arkivanov.decompose.RouterState 4 | import com.arkivanov.decompose.value.Value 5 | import com.nikolam.kmm_weather.common.main.WeatherMain 6 | 7 | interface WeatherRoot { 8 | val routerState : Value> 9 | 10 | sealed class Child { 11 | data class Main(val component: WeatherMain) : Child() 12 | } 13 | } -------------------------------------------------------------------------------- /common/root/src/commonMain/kotlin/com.nikolam.kmm_weather.common.root/integration/WeatherRootComponent.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.root.integration 2 | 3 | import com.arkivanov.decompose.ComponentContext 4 | import com.arkivanov.decompose.RouterState 5 | import com.arkivanov.decompose.router 6 | import com.arkivanov.decompose.statekeeper.Parcelable 7 | import com.arkivanov.decompose.statekeeper.Parcelize 8 | import com.arkivanov.decompose.value.Value 9 | import com.arkivanov.mvikotlin.core.store.StoreFactory 10 | import com.badoo.reaktive.base.Consumer 11 | import com.nikolam.kmm_weather.common.root.WeatherRoot 12 | import com.nikolam.kmm_weather.common.main.WeatherMain 13 | import com.nikolam.kmm_weather.common.main.integration.WeatherMainComponent 14 | import com.nikolam.kmm_weather.common.root.WeatherRoot.* 15 | import com.nikolam.kmm_weather.common.utils.Consumer 16 | 17 | class WeatherRootComponent internal constructor( 18 | componentContext: ComponentContext, 19 | private val weatherMain : (ComponentContext, Consumer) -> WeatherMain 20 | ) : WeatherRoot, ComponentContext by componentContext{ 21 | 22 | constructor( 23 | componentContext: ComponentContext, 24 | storeFactory: StoreFactory 25 | ) : this( 26 | componentContext = componentContext, 27 | weatherMain = { childContext, output -> 28 | WeatherMainComponent( 29 | componentContext = childContext, 30 | storeFactory = storeFactory, 31 | output = output 32 | ) 33 | } 34 | ) 35 | 36 | private val router = 37 | router( 38 | initialConfiguration = Configuration.Main, 39 | handleBackButton = true, 40 | childFactory = ::createChild 41 | ) 42 | 43 | override val routerState: Value> = router.state 44 | 45 | private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child = 46 | when (configuration) { 47 | is Configuration.Main -> Child.Main(weatherMain(componentContext, Consumer(::onMainOutput))) 48 | } 49 | 50 | private fun onMainOutput(output: WeatherMain.Output): Unit = 51 | when (output) { 52 | is WeatherMain.Output.SelectedDay -> {}// router.push(Configuration.Edit(itemId = output.id)) 53 | } 54 | 55 | 56 | private sealed class Configuration : Parcelable { 57 | @Parcelize 58 | object Main : Configuration() 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /common/utils/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("multiplatform-setup") 3 | id("android-setup") 4 | id("kotlinx-serialization") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | named("commonMain") { 10 | dependencies { 11 | implementation(Deps.ArkIvanov.MVIKotlin.rx) 12 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) 13 | implementation(Deps.ArkIvanov.Decompose.decompose) 14 | implementation(Deps.Badoo.Reaktive.reaktive) 15 | 16 | implementation(Deps.JetBrains.Ktor.core) 17 | } 18 | } 19 | 20 | named("androidMain") { 21 | dependencies { 22 | implementation(Deps.JetBrains.Ktor.android) 23 | implementation(Deps.JetBrains.Ktor.jsonJVM) 24 | implementation(Deps.JetBrains.Ktor.serializationJVM) 25 | // implementation(Deps.JetBrains.Ktor.logging) 26 | implementation(Deps.JetBrains.Ktor.okhttp) 27 | } 28 | } 29 | 30 | 31 | named("iosMain") { 32 | dependencies { 33 | implementation(Deps.JetBrains.Ktor.ios) 34 | } 35 | } 36 | 37 | named("desktopMain"){ 38 | dependencies{ 39 | implementation(Deps.JetBrains.Ktor.core) 40 | implementation(Deps.JetBrains.Ktor.javaClient) 41 | // implementation(Deps.JetBrains.Ktor.curlClient) 42 | } 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/utils/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /common/utils/src/androidMain/kotlin/com.nikolam.kmm_weather.common.utils/PlatformServiceLocator.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.utils 2 | 3 | import io.ktor.client.engine.HttpClientEngine 4 | import io.ktor.client.engine.okhttp.OkHttp 5 | 6 | actual object PlatformServiceLocator { 7 | actual val httpClientEngine: HttpClientEngine by lazy { 8 | OkHttp.create { 9 | // val networkInterceptor = HttpLoggingInterceptor().apply { 10 | // level = HttpLoggingInterceptor.Level.BODY 11 | // } 12 | // addNetworkInterceptor(networkInterceptor) 13 | // } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /common/utils/src/commonMain/kotlin/com.nikolam.kmm_weather.common.utils/InstanceKeeperExt.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.utils 2 | 3 | import com.arkivanov.decompose.instancekeeper.InstanceKeeper 4 | import com.arkivanov.decompose.instancekeeper.getOrCreate 5 | import com.arkivanov.mvikotlin.core.store.Store 6 | 7 | fun > InstanceKeeper.getStore(key: Any, factory: () -> T): T = 8 | getOrCreate(key) { StoreHolder(factory()) } 9 | .store 10 | 11 | inline fun > InstanceKeeper.getStore(noinline factory: () -> T): T = 12 | getStore(T::class, factory) 13 | 14 | private class StoreHolder>( 15 | val store: T 16 | ) : InstanceKeeper.Instance { 17 | override fun onDestroy() { 18 | store.dispose() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/utils/src/commonMain/kotlin/com.nikolam.kmm_weather.common.utils/PlatformServiceLocator.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.utils 2 | 3 | import io.ktor.client.engine.* 4 | 5 | /** 6 | * Contains some expected dependencies for the [ServiceLocator] that have to be resolved by Android/iOS. 7 | */ 8 | expect object PlatformServiceLocator { 9 | val httpClientEngine: HttpClientEngine 10 | } -------------------------------------------------------------------------------- /common/utils/src/commonMain/kotlin/com.nikolam.kmm_weather.common.utils/ReaktiveExt.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.utils 2 | 3 | import com.badoo.reaktive.base.Consumer 4 | 5 | @Suppress("FunctionName") // Factory function 6 | inline fun Consumer(crossinline block: (T) -> Unit): Consumer = 7 | object : Consumer { 8 | override fun onNext(value: T) { 9 | block(value) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /common/utils/src/commonMain/kotlin/com.nikolam.kmm_weather.common.utils/StoreExt.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.utils 2 | 3 | import com.arkivanov.decompose.value.Value 4 | import com.arkivanov.decompose.value.ValueObserver 5 | import com.arkivanov.mvikotlin.core.store.Store 6 | import com.arkivanov.mvikotlin.rx.Disposable 7 | 8 | fun Store<*, T, *>.asValue(): Value = 9 | object : Value() { 10 | override val value: T get() = state 11 | private var disposables = emptyMap, Disposable>() 12 | 13 | override fun subscribe(observer: ValueObserver) { 14 | val disposable = states(com.arkivanov.mvikotlin.rx.observer(onNext = observer)) 15 | this.disposables += observer to disposable 16 | } 17 | 18 | override fun unsubscribe(observer: ValueObserver) { 19 | val disposable = disposables[observer] ?: return 20 | this.disposables -= observer 21 | disposable.dispose() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/utils/src/desktopMain/kotlin/com.nikolam.kmm_weather.common.utils/PlatformServiceLocator.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.utils 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.engine.* 5 | import io.ktor.client.engine.java.* 6 | 7 | actual object PlatformServiceLocator { 8 | actual val httpClientEngine: HttpClientEngine = Java.create() { 9 | // this: JavaHttpConfig 10 | threadsCount = 8 11 | pipelining = true 12 | } 13 | } -------------------------------------------------------------------------------- /common/utils/src/iosMain/kotlin/com.nikolam.kmm_weather.common.utils/PlatformServiceLocator.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.common.utils 2 | 3 | import io.ktor.client.engine.* 4 | import io.ktor.client.engine.ios.* 5 | 6 | actual object PlatformServiceLocator { 7 | actual val httpClientEngine: HttpClientEngine = Ios.create() 8 | } -------------------------------------------------------------------------------- /desktop/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.compose.compose 2 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat 3 | 4 | plugins { 5 | kotlin("multiplatform") // kotlin("jvm") doesn't work well in IDEA/AndroidStudio (https://github.com/JetBrains/compose-jb/issues/22) 6 | id("org.jetbrains.compose") 7 | } 8 | 9 | kotlin { 10 | jvm { 11 | withJava() 12 | } 13 | 14 | sourceSets { 15 | named("jvmMain") { 16 | dependencies { 17 | implementation(compose.desktop.currentOs) 18 | implementation(project(":common:utils")) 19 | implementation(project(":common:root")) 20 | implementation(project(":common:compose-ui")) 21 | implementation(Deps.ArkIvanov.Decompose.decompose) 22 | implementation(Deps.ArkIvanov.Decompose.extensionsCompose) 23 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlin) 24 | implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) 25 | implementation(Deps.Badoo.Reaktive.reaktive) 26 | implementation(Deps.Badoo.Reaktive.coroutinesInterop) 27 | } 28 | } 29 | } 30 | } 31 | 32 | compose.desktop { 33 | application { 34 | mainClass = "com.nikolam.kmm_weather.desktop.MainKt" 35 | 36 | nativeDistributions { 37 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) 38 | packageName = "WeatherApp" 39 | packageVersion = "1.0.0" 40 | 41 | modules("java.sql") 42 | 43 | windows { 44 | menuGroup = "Compose Examples" 45 | // see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html 46 | upgradeUuid = "BF9CDA6A-1391-46D5-9ED5-383D6E68CCEB" 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /desktop/src/jvmMain/kotlin/com.nikolam.kmm_weather.desktop/Main.kt: -------------------------------------------------------------------------------- 1 | package com.nikolam.kmm_weather.desktop 2 | 3 | import androidx.compose.desktop.DesktopTheme 4 | import androidx.compose.desktop.Window 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.material.Surface 8 | import androidx.compose.material.Text 9 | import androidx.compose.material.TextField 10 | import androidx.compose.ui.Modifier 11 | import com.arkivanov.decompose.ComponentContext 12 | import com.arkivanov.decompose.extensions.compose.jetbrains.rememberRootComponent 13 | import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory 14 | import com.badoo.reaktive.coroutinesinterop.asScheduler 15 | import com.badoo.reaktive.scheduler.overrideSchedulers 16 | import com.nikolam.kmm_weather.common.root.WeatherRoot 17 | import com.nikolam.kmm_weather.common.root.integration.WeatherRootComponent 18 | import com.nikolam.kmm_weather.ui.WeatherRootContent 19 | import kotlinx.coroutines.Dispatchers 20 | 21 | fun main() { 22 | //overrideSchedulers(main = Dispatchers.Main::asScheduler) 23 | 24 | Window("Weather") { 25 | Surface(modifier = Modifier.fillMaxSize()) { 26 | MaterialTheme { 27 | DesktopTheme { 28 | WeatherRootContent(rememberRootComponent(factory = ::weatherRoot)) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | private fun weatherRoot(componentContext: ComponentContext): WeatherRoot = 36 | WeatherRootComponent( 37 | componentContext = componentContext, 38 | storeFactory = DefaultStoreFactory 39 | ) 40 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dkotlin.daemon.jvm.options=--illegal-access=permit 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | org.gradle.parallel=true 23 | org.gradle.caching=true 24 | kotlin.native.disableCompilerDaemon=true 25 | kotlin.mpp.enableGranularSourceSetsMetadata=true 26 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 21 18:00:24 CEST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /iosApp/kmm_weather.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iosApp/kmm_weather.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | import UIKit 8 | 9 | @UIApplicationMain 10 | class AppDelegate: UIResponder, UIApplicationDelegate { 11 | 12 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 13 | // Override point for customization after application launch. 14 | return true 15 | } 16 | 17 | // MARK: UISceneSession Lifecycle 18 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 19 | // Called when a new scene session is being created. 20 | // Use this method to select a configuration to create the new scene with. 21 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 22 | } 23 | 24 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 25 | // Called when the user discards a scene session. 26 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 27 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/cloudy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cloudy.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/cloudy.imageset/cloudy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 17 | 18 | 26 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_clear.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "day_clear.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_clear.imageset/day_clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_partial_cloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "day_partial_cloud.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_partial_cloud.imageset/day_partial_cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 42 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_rain.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "day_rain.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_rain.imageset/day_rain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 48 | 49 | 50 | 51 | 52 | 54 | 56 | 57 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_rain_thunder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "day_rain_thunder.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_rain_thunder.imageset/day_rain_thunder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 57 | 58 | 60 | 61 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_sleet.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "day_sleet.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_snow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "day_snow.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/day_snow_thunder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "day_snow_thunder.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/fog.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "fog.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/fog.imageset/fog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 18 | 19 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/mist.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "mist.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/mist.imageset/mist.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 18 | 19 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_clear.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "night_clear.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_clear.imageset/night_clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_partial_cloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "night_partial_cloud.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_partial_cloud.imageset/night_partial_cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 14 | 15 | 16 | 17 | 24 | 25 | 33 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_rain.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "night_rain.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_rain.imageset/night_rain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 39 | 40 | 41 | 42 | 43 | 45 | 47 | 48 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_rain_thunder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "night_rain_thunder.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_rain_thunder.imageset/night_rain_thunder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 49 | 50 | 52 | 53 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_sleet.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "night_sleet.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_sleet.imageset/night_sleet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 48 | 50 | 52 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_snow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "night_snow.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/night_snow_thunder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "night_snow_thunder.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/overcast.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "overcast.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/rain.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "rain.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/rain.imageset/rain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 18 | 19 | 32 | 33 | 34 | 35 | 36 | 38 | 40 | 41 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/rain_thunder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "rain_thunder.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/rain_thunder.imageset/rain_thunder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 19 | 20 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 42 | 43 | 45 | 46 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/sleet.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "sleet.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/sleet.imageset/sleet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 18 | 19 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 41 | 43 | 45 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/snow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "snow.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/snow.imageset/snow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 17 | 18 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 40 | 42 | 44 | 45 | 46 | 48 | 50 | 52 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/snow_thunder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "snow_thunder.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/thunder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "thunder.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/thunder.imageset/thunder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 18 | 19 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 43 | 44 | 45 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/tornado.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tornado.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/tornado.imageset/tornado.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 13 | 15 | 16 | 18 | 20 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/wind.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "wind.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Assets.xcassets/wind.imageset/wind.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 18 | 19 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/ComponentHolder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComponentHolder.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | 8 | import Weather 9 | 10 | class ComponentHolder { 11 | let lifecycle: LifecycleRegistry 12 | let component: T 13 | 14 | init(factory: (ComponentContext) -> T) { 15 | let lifecycle = LifecycleRegistryKt.LifecycleRegistry() 16 | let component = factory(DefaultComponentContext(lifecycle: lifecycle)) 17 | self.lifecycle = lifecycle 18 | self.component = component 19 | 20 | lifecycle.onCreate() 21 | } 22 | 23 | deinit { 24 | lifecycle.onDestroy() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 12/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | import Weather 10 | 11 | struct ContentView: View { 12 | @State 13 | private var componentHolder = ComponentHolder{ WeatherRootComponent(componentContext: $0, storeFactory: DefaultStoreFactory())} 14 | 15 | var body: some View { 16 | RootView(componentHolder.component) 17 | .onAppear { LifecycleRegistryExtKt.resume(self.componentHolder.lifecycle) } 18 | .onDisappear { LifecycleRegistryExtKt.stop(self.componentHolder.lifecycle) } 19 | } 20 | } 21 | 22 | struct ContentView_Previews: PreviewProvider { 23 | static var previews: some View { 24 | ContentView() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/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 | UIApplicationSceneManifest 24 | 25 | UISceneConfigurations 26 | 27 | UIWindowSceneSessionRoleApplication 28 | 29 | 30 | UISceneDelegateClassName 31 | $(PRODUCT_MODULE_NAME).SceneDelegate 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneStoryboardFile 35 | LaunchScreen 36 | 37 | 38 | 39 | UIApplicationSupportsMultipleScenes 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchScreen 45 | 46 | UILaunchStoryboardName 47 | LaunchScreen 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/MutableValueBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MutableValueBuilder.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | 8 | import Foundation 9 | import Weather 10 | 11 | func valueOf(_ value: T) -> Value { 12 | return MutableValueBuilderKt.MutableValue(initialValue: value) as! MutableValue 13 | } 14 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/ObservableValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObservableValue.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | import Weather 10 | 11 | public class ObservableValue : ObservableObject { 12 | private let observableValue: Value 13 | 14 | @Published 15 | var value: T 16 | 17 | private var observer: ((T) -> Void)? 18 | 19 | init(_ value: Value) { 20 | self.observableValue = value 21 | self.value = observableValue.value 22 | 23 | self.observer = { value in 24 | self.value = value 25 | } 26 | observableValue.subscribe(observer: observer!) 27 | } 28 | 29 | deinit { 30 | self.observableValue.unsubscribe(observer: self.observer!) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/RootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootView.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | import Weather 10 | 11 | struct RootView: View { 12 | @ObservedObject 13 | private var component: ObservableValue 14 | 15 | init(_ component: WeatherRoot) { 16 | self.component = ObservableValue(valueOf(component)) 17 | } 18 | 19 | var body: some View { 20 | let weatherMain = component.value.routerState.value.activeChild.instance as! WeatherRootChild.Main 21 | MainView(weatherMain.component as! WeatherMainComponent) 22 | } 23 | } 24 | 25 | struct RootView_Previews: PreviewProvider { 26 | static var previews: some View { 27 | RootView(StubWeatherRoot()) 28 | .previewDevice("iPhone 8") 29 | } 30 | 31 | class StubWeatherRoot: WeatherRoot { 32 | var routerState: Value> = simpleRouterState(WeatherRootChild.Main(component:StubWeatherMain())) 33 | } 34 | 35 | class StubWeatherMain : WeatherMain { 36 | func onItemClicked(id: Int64) {} 37 | 38 | var models: Value = valueOf(WeatherMainModel(currentWeather: CurrentWeatherModel(weatherID: 400, daily: [DailyWeatherModel(weatherID: 500, index:1, temp: 28)], temp: 32, wind: 15, humidity: 15, weatherDesc: "Cloudy"), isLoading: false, isError: false)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | // Create the SwiftUI view that provides the window contents. 20 | let contentView = ContentView() 21 | 22 | // Use a UIHostingController as window root view controller. 23 | if let windowScene = scene as? UIWindowScene { 24 | let window = UIWindow(windowScene: windowScene) 25 | window.rootViewController = UIHostingController(rootView: contentView) 26 | self.window = window 27 | window.makeKeyAndVisible() 28 | } 29 | } 30 | 31 | func sceneDidDisconnect(_ scene: UIScene) { 32 | // Called as the scene is being released by the system. 33 | // This occurs shortly after the scene enters the background, or when its session is discarded. 34 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 35 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 36 | } 37 | 38 | func sceneDidBecomeActive(_ scene: UIScene) { 39 | // Called when the scene has moved from an inactive state to an active state. 40 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 41 | } 42 | 43 | func sceneWillResignActive(_ scene: UIScene) { 44 | // Called when the scene will move from an active state to an inactive state. 45 | // This may occur due to temporary interruptions (ex. an incoming phone call). 46 | } 47 | 48 | func sceneWillEnterForeground(_ scene: UIScene) { 49 | // Called as the scene transitions from the background to the foreground. 50 | // Use this method to undo the changes made on entering the background. 51 | } 52 | 53 | func sceneDidEnterBackground(_ scene: UIScene) { 54 | // Called as the scene transitions from the foreground to the background. 55 | // Use this method to save data, release shared resources, and store enough scene-specific state information 56 | // to restore the scene back to its current state. 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/SearchBox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchBox.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SearchBox: View { 11 | @Binding var text: String 12 | 13 | @State private var isEditing = false 14 | 15 | var body: some View { 16 | HStack { 17 | 18 | TextField("Search ...", text: $text) 19 | .padding(7) 20 | .padding(.horizontal, 25) 21 | .background(Color(.systemGray6)) 22 | .cornerRadius(8) 23 | .overlay( 24 | HStack { 25 | Image(systemName: "magnifyingglass") 26 | .foregroundColor(.gray) 27 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) 28 | .padding(.leading, 8) 29 | 30 | if isEditing { 31 | Button(action: { 32 | self.text = "" 33 | 34 | }) { 35 | Image(systemName: "multiply.circle.fill") 36 | .foregroundColor(.gray) 37 | .padding(.trailing, 8) 38 | } 39 | } 40 | } 41 | ) 42 | .padding(.horizontal, 10) 43 | .onTapGesture { 44 | self.isEditing = true 45 | } 46 | 47 | if isEditing { 48 | Button(action: { 49 | self.isEditing = false 50 | self.text = "" 51 | 52 | // Dismiss the keyboard 53 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 54 | }) { 55 | Text("Cancel").foregroundColor(.white) 56 | } 57 | .padding(.trailing, 10) 58 | .transition(.move(edge: .trailing)) 59 | .animation(.default) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/SimpleRouterState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleRouterState.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | 8 | import Weather 9 | 10 | func simpleRouterState(_ child: T) -> Value> { 11 | return valueOf( 12 | RouterState( 13 | activeChild: ChildCreated( 14 | configuration: "config" as AnyObject, 15 | instance: child 16 | ), 17 | backStack: [] 18 | ) 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 19/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | func hexStringToColor (hex: String) -> Color { 11 | var cString: String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() 12 | 13 | if (cString.hasPrefix("#")) { 14 | cString.remove(at: cString.startIndex) 15 | } 16 | 17 | if ((cString.count) != 6) { 18 | return Color.gray 19 | } 20 | 21 | var rgbValue: UInt64 = 0 22 | Scanner(string: cString).scanHexInt64(&rgbValue) 23 | 24 | return Color( 25 | red: Double(CGFloat((rgbValue & 0xFF0000) >> 16)) / 255.0, 26 | green: Double(CGFloat((rgbValue & 0x00FF00) >> 8)) / 255.0, 27 | blue: Double(CGFloat(rgbValue & 0x0000FF) / 255.0) 28 | ) 29 | } 30 | 31 | 32 | func getCurrentDate() -> String { 33 | let currentDateTime = Date() 34 | // initialize the date formatter and set the style 35 | let formatter = DateFormatter() 36 | formatter.timeStyle = .short 37 | formatter.dateStyle = .medium 38 | 39 | return formatter.string(from: currentDateTime) 40 | } 41 | 42 | func formatTempString(str: String, fahr: Bool) -> String { 43 | if !fahr { 44 | var appendString = str + "C" 45 | return appendString 46 | } else { 47 | return String(Int(((Double(str) ?? 0 * 1.8) + 32))) + "F" 48 | } 49 | } 50 | 51 | func getWeatherIconStringFromWeatherID(id : Int32) -> String { 52 | switch id { 53 | case 200..<233: 54 | return "thunder" 55 | case 300..<322: 56 | return "day_rain" 57 | case 500..<532: 58 | return "rain" 59 | case 600..<623: 60 | return "snow" 61 | case 800: 62 | return "day_clear" 63 | case 801..<805: 64 | return "cloudy" 65 | default: 66 | return "day_clear" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /iosApp/kmm_weather/WeatherAppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // kmm_weatherApp.swift 3 | // kmm_weather 4 | // 5 | // Created by Nikola Milovic on 12/06/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct kmm_weatherApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include( 2 | ":common:root", 3 | ":common:main", 4 | ":common:utils", 5 | ":common:compose-ui", 6 | ":androidApp", 7 | ":desktop" 8 | ) -------------------------------------------------------------------------------- /showcase/android_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/showcase/android_dark.png -------------------------------------------------------------------------------- /showcase/android_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/showcase/android_light.png -------------------------------------------------------------------------------- /showcase/desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/showcase/desktop.png -------------------------------------------------------------------------------- /showcase/ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikola-Milovic/KotlinMultiplaftorm-WeatherApp/281cd1638d92723a05b1ef49875e361d4e725006/showcase/ios.png --------------------------------------------------------------------------------