├── ios ├── .tool-versions ├── Gemfile ├── iosApp │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Preview Content │ │ ├── Preview Assets.xcassets │ │ │ └── Contents.json │ │ └── PreviewContext.swift │ ├── iOSApp.swift │ ├── GreetingView.swift │ ├── Utils │ │ └── FlowUtils.swift │ └── Info.plist ├── iosApp.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Podfile ├── Podfile.lock ├── Gemfile.lock ├── iosApp.xcodeproj │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── iosApp.xcscheme │ └── project.pbxproj └── .swiftlint.yml ├── .tool-versions ├── Gemfile ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .gitignore ├── shared ├── src │ ├── commonMain │ │ └── kotlin │ │ │ └── com │ │ │ └── mirego │ │ │ └── kmp │ │ │ └── boilerplate │ │ │ ├── utils │ │ │ └── CFlow.kt │ │ │ ├── platform │ │ │ └── Platform.kt │ │ │ └── Greeting.kt │ ├── androidMain │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── mirego │ │ │ │ └── kmp │ │ │ │ └── boilerplate │ │ │ │ ├── utils │ │ │ │ └── CFlow.kt │ │ │ │ └── platform │ │ │ │ ├── AppContextInitializer.kt │ │ │ │ └── Platform.kt │ │ └── AndroidManifest.xml │ └── iosMain │ │ └── kotlin │ │ └── com │ │ └── mirego │ │ └── kmp │ │ └── boilerplate │ │ ├── utils │ │ └── CFlow.kt │ │ └── platform │ │ └── Platform.kt ├── Shared.podspec └── build.gradle.kts ├── androidApp ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── java │ │ └── com │ │ │ └── mirego │ │ │ └── kmp │ │ │ └── boilerplate │ │ │ ├── MainActivity.kt │ │ │ ├── previews │ │ │ └── PreviewContext.kt │ │ │ └── Greeting.kt │ │ └── AndroidManifest.xml ├── build.gradle.kts └── proguard-rules.pro ├── accent.json ├── CHANGELOG.md ├── gradle.properties ├── .github ├── pull_request_template.md └── workflows │ ├── ci.yaml │ ├── accent.yaml │ └── dependency-check.yaml ├── settings.gradle.kts ├── CONTRIBUTING.md ├── LICENSE.md ├── CODE_OF_CONDUCT.md ├── gradlew.bat ├── BOILERPLATE_README.md ├── Gemfile.lock ├── boilerplate-setup.sh ├── gradlew └── README.md /ios/.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 3.2.2 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | java openjdk-17 2 | ruby 3.2.2 3 | -------------------------------------------------------------------------------- /ios/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 1.14' 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'cocoapods', '~> 1.14' 4 | gem 'cocoapods-generate', '~> 2.2' 5 | -------------------------------------------------------------------------------- /ios/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mirego/kmp-boilerplate/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /ios/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct IOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | GreetingView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | */build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | xcuserdata/ 13 | Pods/ -------------------------------------------------------------------------------- /ios/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/mirego/kmp/boilerplate/utils/CFlow.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate.utils 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | expect class CFlow : Flow 6 | 7 | expect fun Flow.wrap(): CFlow 8 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | -------------------------------------------------------------------------------- /ios/iosApp/Preview Content/PreviewContext.swift: -------------------------------------------------------------------------------- 1 | import Shared 2 | import SwiftUI 3 | 4 | struct PreviewContext: View where Content: View { 5 | let content: @MainActor () -> Content 6 | 7 | var body: some View { 8 | content() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Oct 12 13:41:45 EDT 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /ios/iosApp.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/com/mirego/kmp/boilerplate/utils/CFlow.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate.utils 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | actual class CFlow internal constructor(origin: Flow) : Flow by origin 6 | 7 | actual fun Flow.wrap(): CFlow = CFlow(this) 8 | -------------------------------------------------------------------------------- /ios/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /accent.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiUrl": "", 3 | "apiKey": "", 4 | "files": [ 5 | { 6 | "language": "en", 7 | "format": "json", 8 | "source": "shared/src/commonMain/resources/translations/translation.en.json", 9 | "target": "shared/src/commonMain/resources/translations/translation.%slug%.json" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /androidApp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /ios/iosApp/GreetingView.swift: -------------------------------------------------------------------------------- 1 | import Shared 2 | import SwiftUI 3 | 4 | struct GreetingView: View { 5 | 6 | @ObservedObject var greet = ObservableFlowWrapper(Greeting().greeting(), initial: "initial") 7 | 8 | var body: some View { 9 | Text("\(greet.value)") 10 | } 11 | } 12 | 13 | #Preview { 14 | PreviewContext { 15 | GreetingView() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 6 | 7 | Since it is a boilerplate project, there are technically no official (versioned) _releases_. Therefore, the `main` branch should always be stable and usable. 8 | 9 | ## 2022-11-17 10 | 11 | Initial release 12 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | target 'iosApp' do 2 | use_frameworks! 3 | platform :ios, '15.0' 4 | pod 'Shared', :path => '../shared' 5 | 6 | pod 'SwiftLint' 7 | end 8 | 9 | post_install do |installer| 10 | installer.pods_project.targets.each do |target| 11 | target.build_configurations.each do |config| 12 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = 15.0 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle 2 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" 3 | org.gradle.configuration-cache=true 4 | #Kotlin 5 | kotlin.code.style=official 6 | #Android 7 | android.useAndroidX=true 8 | #MPP 9 | kotlin.mpp.androidSourceSetLayoutVersion=2 10 | kotlin.mpp.enableCInteropCommonization=true 11 | kotlin.mpp.stability.nowarn=true 12 | #Version 13 | versionName=0.0.1 14 | versionCode=1 15 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 📖 Description 2 | 3 | 4 | 5 | ## 📝 Notes 6 | 7 | 8 | 9 | ## 📓 References 10 | 11 | 12 | 13 | ## 🦀 Dispatch 14 | 15 | - `#dispatch/kmp` 16 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | pluginManagement { 4 | repositories { 5 | google() 6 | gradlePluginPortal() 7 | mavenCentral() 8 | } 9 | } 10 | 11 | dependencyResolutionManagement { 12 | repositories { 13 | google() 14 | mavenCentral() 15 | maven("https://s3.amazonaws.com/mirego-maven/public") 16 | } 17 | } 18 | 19 | rootProject.name = "kmp-boilerplate" 20 | include(":androidApp") 21 | include(":shared") 22 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/mirego/kmp/boilerplate/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | 7 | 8 | class MainActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContent { 12 | Greeting(textFlow = Greeting().greeting()) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ios/iosApp/Utils/FlowUtils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Shared 3 | 4 | public class ObservableFlowWrapper: ObservableObject { 5 | 6 | @Published public private(set) var value: T 7 | 8 | private var watcher: Closeable? 9 | 10 | public init(_ flow: CFlow, initial value: T) { 11 | self.value = value 12 | 13 | watcher = flow.watch { [weak self] t in 14 | self?.value = t 15 | } 16 | } 17 | 18 | deinit { 19 | watcher?.close() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/mirego/kmp/boilerplate/platform/Platform.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate.platform 2 | 3 | expect fun Platform(): Platform 4 | 5 | interface Platform { 6 | val system: System 7 | val locale: Locale 8 | val version: Version 9 | } 10 | 11 | data class Locale( 12 | val languageCode: String, 13 | val regionCode: String? 14 | ) 15 | 16 | data class System( 17 | val name: String, 18 | val version: String 19 | ) 20 | 21 | data class Version( 22 | val name: String, 23 | val code: Int 24 | ) 25 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/com/mirego/kmp/boilerplate/platform/AppContextInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate.platform 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import com.mirego.konnectivity.KonnectivityInitializer 6 | 7 | internal lateinit var appContext: Context 8 | 9 | class AppContextInitializer : Initializer { 10 | override fun create(context: Context) { 11 | appContext = context 12 | } 13 | 14 | override fun dependencies() = listOf( 15 | KonnectivityInitializer::class.java 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Reachability (3.2) 3 | - Shared (0.0.1): 4 | - Reachability (~> 3.2) 5 | - SwiftLint (0.52.4) 6 | 7 | DEPENDENCIES: 8 | - Shared (from `../shared`) 9 | - SwiftLint 10 | 11 | SPEC REPOS: 12 | trunk: 13 | - Reachability 14 | - SwiftLint 15 | 16 | EXTERNAL SOURCES: 17 | Shared: 18 | :path: "../shared" 19 | 20 | SPEC CHECKSUMS: 21 | Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 22 | Shared: 394ccbb1a148f40e374cb91aa5b75c96b121a775 23 | SwiftLint: 1cc5cd61ba9bacb2194e340aeb47a2a37fda00b3 24 | 25 | PODFILE CHECKSUM: 83ebf5d7b61ce65029f18160027c7392430ee27f 26 | 27 | COCOAPODS: 1.14.3 28 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/mirego/kmp/boilerplate/previews/PreviewContext.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate.previews 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.platform.LocalContext 5 | import androidx.startup.AppInitializer 6 | import com.mirego.kmp.boilerplate.platform.AppContextInitializer 7 | 8 | @Composable 9 | fun PreviewContext(content: @Composable () -> Unit) { 10 | // @Composable previews do not call AppInitializer. We must initialize our components manually. 11 | AppInitializer.getInstance(LocalContext.current) 12 | .initializeComponent(AppContextInitializer::class.java) 13 | 14 | content() 15 | } 16 | -------------------------------------------------------------------------------- /shared/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /androidApp/src/main/java/com/mirego/kmp/boilerplate/Greeting.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate 2 | 3 | import androidx.compose.material.Text 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.collectAsState 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.ui.tooling.preview.Preview 8 | import com.mirego.kmp.boilerplate.previews.PreviewContext 9 | import kotlinx.coroutines.flow.Flow 10 | 11 | @Composable 12 | fun Greeting(textFlow: Flow) { 13 | val text: String by textFlow.collectAsState("initial") 14 | 15 | Text(text = text) 16 | } 17 | 18 | @Preview(showSystemUi = true) 19 | @Composable 20 | fun PreviewGreeting() { 21 | PreviewContext { 22 | Greeting(Greeting().greeting()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /androidApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/com/mirego/kmp/boilerplate/platform/Platform.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate.platform 2 | 3 | import android.os.Build 4 | import com.mirego.kmp.boilerplate.common.BuildConfig 5 | 6 | actual fun Platform(): Platform = AndroidPlatform() 7 | 8 | private class AndroidPlatform : Platform { 9 | override val system = System( 10 | name = "Android", 11 | version = Build.VERSION.SDK_INT.toString() 12 | ) 13 | 14 | override val locale = java.util.Locale.getDefault().let { 15 | Locale( 16 | languageCode = it.language, 17 | regionCode = it.country 18 | ) 19 | } 20 | 21 | override val version = Version( 22 | name = BuildConfig.VERSION_NAME, 23 | code = BuildConfig.VERSION_CODE 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | # Trigger the workflow on push only for the main branch 5 | push: 6 | branches: 7 | - main 8 | # Trigger the workflow on any pull requests 9 | pull_request: 10 | branches: 11 | - '**' 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Gradle cache 24 | uses: actions/cache@v1 25 | with: 26 | path: ~/.gradle/caches 27 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 28 | restore-keys: | 29 | ${{ runner.os }}-gradle- 30 | 31 | - name: Set up JDK 32 | uses: actions/setup-java@v1 33 | with: 34 | java-version: 17 35 | 36 | - name: Grant execute permission for gradlew 37 | run: chmod +x gradlew 38 | 39 | - name: Build with Gradle 40 | run: ./gradlew check 41 | -------------------------------------------------------------------------------- /.github/workflows/accent.yaml: -------------------------------------------------------------------------------- 1 | name: Accent 2 | 3 | on: 4 | schedule: 5 | - cron: "0 4 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | sync: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: 16 16 | - run: npm install -g accent-cli 17 | - run: accent sync --add-translations --merge-type=passive --order-by=key 18 | - uses: mirego/create-pull-request@v5 19 | with: 20 | add-paths: "*.json" 21 | commit-message: Update translations 22 | committer: github-actions[bot] 23 | author: github-actions[bot] 24 | branch: accent 25 | draft: false 26 | delete-branch: true 27 | title: New translations are available to merge 28 | body: The translation files have been updated, feel free to merge this pull request after review. 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## A word about the project 4 | 5 | First of all, thank you for your interest in contributing to this project! 6 | 7 | This project is our vision of a well set up project and is the base we use to create all of our Kotlin Multiplatform applications at Mirego. We decided to make it public so that others can benefit from our experience and the lessons learned over the years of building several projects with different objectives; all fulfilled by this boilerplate. 8 | 9 | While we accept pull requests and suggestions, it is more of a project that we want to share so that you can build awesome things with it and maybe, base your own boilerplate off of it! 10 | 11 | ## Contributing 12 | 13 | We strongly suggest you open an issue before starting to work on code that you would like to see in this project. This will prevent you, for example, from implementing a feature that we, Mirego, already discussed and decided not to use. 14 | 15 | Bug and typo fixes are always welcomed, of course 🙂 16 | 17 | Thank you! ❤️ 18 | -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/com/mirego/kmp/boilerplate/utils/CFlow.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package com.mirego.kmp.boilerplate.utils 4 | 5 | import kotlinx.coroutines.CoroutineDispatcher 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.Job 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.launchIn 11 | import kotlinx.coroutines.flow.onEach 12 | 13 | fun interface Closeable { 14 | fun close() 15 | } 16 | 17 | actual class CFlow internal constructor( 18 | private val origin: Flow, 19 | private val dispatcher: CoroutineDispatcher = Dispatchers.Main 20 | ) : Flow by origin { 21 | 22 | fun watch(block: (T) -> Unit): Closeable { 23 | val job = Job() 24 | val scope = CoroutineScope(dispatcher + job) 25 | 26 | onEach { 27 | block(it) 28 | }.launchIn(scope) 29 | 30 | return Closeable { job.cancel() } 31 | } 32 | } 33 | 34 | actual fun Flow.wrap(): CFlow = CFlow(this) 35 | -------------------------------------------------------------------------------- /shared/src/iosMain/kotlin/com/mirego/kmp/boilerplate/platform/Platform.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate.platform 2 | 3 | import platform.Foundation.NSBundle 4 | import platform.Foundation.NSLocale 5 | import platform.Foundation.currentLocale 6 | import platform.Foundation.languageCode 7 | import platform.Foundation.regionCode 8 | import platform.UIKit.UIDevice 9 | 10 | actual fun Platform(): Platform = IOSPlatform() 11 | 12 | private class IOSPlatform : Platform { 13 | override val system = UIDevice.currentDevice.let { 14 | System( 15 | name = it.systemName, 16 | version = it.systemVersion 17 | ) 18 | } 19 | 20 | override val locale = NSLocale.currentLocale.let { 21 | Locale( 22 | languageCode = it.languageCode, 23 | regionCode = it.regionCode 24 | ) 25 | } 26 | 27 | override val version = NSBundle.mainBundle().infoDictionary()!!.let { 28 | Version( 29 | name = it["CFBundleShortVersionString"] as String, 30 | code = (it["CFBundleVersion"] as String).toInt() 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/dependency-check.yaml: -------------------------------------------------------------------------------- 1 | name: Dependency Check 2 | 3 | on: 4 | schedule: 5 | - cron: "34 14 * * 2" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | dep-check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - uses: actions/cache@v3 15 | with: 16 | path: ~/.gradle 17 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 18 | restore-keys: | 19 | ${{ runner.os }}-gradle- 20 | 21 | - uses: ruby/setup-ruby@v1 22 | with: 23 | ruby-version: .tool-versions 24 | bundler-cache: true 25 | - run: | 26 | bundle install 27 | gem install bundler-audit 28 | bundle-audit update 29 | 30 | - uses: actions/setup-java@v3 31 | with: 32 | distribution: "temurin" 33 | java-version: "17" 34 | 35 | - run: chmod +x gradlew 36 | 37 | - run: ./gradlew dependencyCheckAggregate 38 | 39 | - uses: github/codeql-action/upload-sarif@v2 40 | with: 41 | sarif_file: reports/dependency-check-report.sarif 42 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/mirego/kmp/boilerplate/Greeting.kt: -------------------------------------------------------------------------------- 1 | package com.mirego.kmp.boilerplate 2 | 3 | import com.mirego.kmp.boilerplate.platform.Platform 4 | import com.mirego.kmp.boilerplate.utils.CFlow 5 | import com.mirego.kmp.boilerplate.utils.wrap 6 | import com.mirego.konnectivity.Konnectivity 7 | import com.mirego.konnectivity.NetworkState 8 | import kotlinx.coroutines.flow.map 9 | 10 | class Greeting { 11 | private val platform = Platform() 12 | private val konnectivity = Konnectivity() 13 | 14 | private val greetingText = buildString { 15 | appendLine("Hello! 👋") 16 | appendLine(platform.system) 17 | appendLine(platform.locale) 18 | appendLine(platform.version) 19 | appendLine() 20 | } 21 | 22 | fun greeting(): CFlow = konnectivity.networkState 23 | .map { networkState -> 24 | greetingText + networkState.asGreetingInfo() 25 | } 26 | .wrap() 27 | 28 | private fun NetworkState.asGreetingInfo(): String = "By the way, you're " + when (this) { 29 | NetworkState.Unreachable -> "offline. 🔌" 30 | is NetworkState.Reachable -> when (metered) { 31 | true -> "online, but your connection is metered. 📶" 32 | else -> "online! 🛜" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Mirego 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /ios/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | kmp-boilerplate 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /androidApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | kotlin { 7 | jvmToolchain(17) 8 | } 9 | 10 | android { 11 | namespace = "com.mirego.kmp.boilerplate" 12 | compileSdk = 34 13 | defaultConfig { 14 | minSdk = 21 15 | targetSdk = 34 16 | 17 | applicationId = "com.mirego.kmp.boilerplate" 18 | versionCode = project.property("versionCode") as? Int 19 | versionName = project.property("versionName") as? String 20 | } 21 | buildTypes { 22 | debug { 23 | isDebuggable = true 24 | isMinifyEnabled = false 25 | isShrinkResources = false 26 | } 27 | release { 28 | isDebuggable = false 29 | isMinifyEnabled = true 30 | isShrinkResources = true 31 | 32 | proguardFiles( 33 | getDefaultProguardFile("proguard-android-optimize.txt"), 34 | "proguard-rules.pro" 35 | ) 36 | } 37 | } 38 | buildFeatures { 39 | compose = true 40 | } 41 | composeOptions { 42 | kotlinCompilerExtensionVersion = libs.versions.androidComposeCompiler.get() 43 | } 44 | packaging { 45 | // Do not include coroutines debug infrastructure in the resulting APK 46 | // See https://github.com/Kotlin/kotlinx.coroutines#avoiding-including-the-debug-infrastructure-in-the-resulting-apk 47 | resources.excludes += "DebugProbesKt.bin" 48 | } 49 | } 50 | 51 | dependencies { 52 | implementation(project(":shared")) 53 | 54 | implementation(libs.androidx.appcompat) 55 | implementation(libs.androidx.activity.compose) 56 | 57 | implementation(platform(libs.androidx.compose.bom)) 58 | implementation(libs.androidx.compose.ui) 59 | implementation(libs.androidx.compose.ui.tooling) 60 | implementation(libs.androidx.compose.material) 61 | 62 | implementation(libs.androidx.startup.runtime) 63 | } 64 | -------------------------------------------------------------------------------- /ios/iosApp/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 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Contact: info@mirego.com 4 | 5 | ## Why have a Code of Conduct? 6 | 7 | As contributors and maintainers of this project, we are committed to providing a friendly, safe and welcoming environment for all, regardless of age, disability, gender, nationality, race, religion, sexuality, or similar personal characteristic. 8 | 9 | The goal of the Code of Conduct is to specify a baseline standard of behavior so that people with different social values and communication styles can talk about the project effectively, productively, and respectfully, even in face of disagreements. The Code of Conduct also provides a mechanism for resolving conflicts in the community when they arise. 10 | 11 | ## Our Values 12 | 13 | These are the values KMP Boilerplate developers should aspire to: 14 | 15 | - Be friendly and welcoming 16 | - Be patient 17 | - Remember that people have varying communication styles and that not everyone is using their native language. (Meaning and tone can be lost in translation.) 18 | - Be thoughtful 19 | - Productive communication requires effort. Think about how your words will be interpreted. 20 | - Remember that sometimes it is best to refrain entirely from commenting. 21 | - Be respectful 22 | - In particular, respect differences of opinion. It is important that we resolve disagreements and differing views constructively. 23 | - Avoid destructive behavior 24 | - Derailing: stay on topic; if you want to talk about something else, start a new conversation. 25 | - Unconstructive criticism: don't merely decry the current state of affairs; offer (or at least solicit) suggestions as to how things may be improved. 26 | - Snarking (pithy, unproductive, sniping comments). 27 | 28 | The following actions are explicitly forbidden: 29 | 30 | - Insulting, demeaning, hateful, or threatening remarks. 31 | - Discrimination based on age, disability, gender, nationality, race, religion, sexuality, or similar personal characteristic. 32 | - Bullying or systematic harassment. 33 | - Unwelcome sexual advances. 34 | - Incitement to any of these. 35 | 36 | ## Acknowledgements 37 | 38 | This document was based on the Code of Conduct from the Elixir project. 39 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | androidComposeCompiler = "1.5.6" 3 | androidGradlePlugin = "8.2.0" 4 | androidxStartup = "1.1.1" 5 | androidxActivityCompose = "1.8.1" 6 | androidxAppcompat = "1.6.1" 7 | androidxComposeBom = "2023.10.01" 8 | konnectivity = "0.3.0" 9 | kotlin = "1.9.21" 10 | kotlinxCoroutines = "1.7.3" 11 | kotlinxSerialization = "1.6.0" 12 | ktlint = "11.6.1" 13 | 14 | [libraries] 15 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidxActivityCompose" } 16 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppcompat" } 17 | androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" } 18 | androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } 19 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } 20 | androidx-compose-material = { group = "androidx.compose.material", name = "material" } 21 | androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "androidxStartup" } 22 | konnectivity = { module = "com.mirego:konnectivity", version.ref = "konnectivity" } 23 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } 24 | kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } 25 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" } 26 | 27 | [plugins] 28 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } 29 | android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } 30 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 31 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 32 | kotlin-native-cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } 33 | ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" } 34 | owasp-dependencycheck = { id = "org.owasp.dependencycheck", version = "8.4.2" } 35 | serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 36 | 37 | [bundles] 38 | 39 | -------------------------------------------------------------------------------- /shared/Shared.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'Shared' 3 | spec.version = '0.0.1' 4 | spec.homepage = 'https://github.com/mirego/your-project' 5 | spec.source = { :http=> ''} 6 | spec.authors = '' 7 | spec.license = '' 8 | spec.summary = 'Project summary' 9 | spec.vendored_frameworks = 'build/cocoapods/framework/Shared.framework' 10 | spec.libraries = 'c++' 11 | spec.ios.deployment_target = '15.0' 12 | spec.dependency 'Reachability', '~> 3.2' 13 | 14 | if !Dir.exist?('build/cocoapods/framework/Shared.framework') || Dir.empty?('build/cocoapods/framework/Shared.framework') 15 | raise " 16 | 17 | Kotlin framework 'Shared' doesn't exist yet, so a proper Xcode project can't be generated. 18 | 'pod install' should be executed after running ':generateDummyFramework' Gradle task: 19 | 20 | ./gradlew :shared:generateDummyFramework 21 | 22 | Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" 23 | end 24 | 25 | spec.pod_target_xcconfig = { 26 | 'KOTLIN_PROJECT_PATH' => ':shared', 27 | 'PRODUCT_MODULE_NAME' => 'Shared', 28 | } 29 | 30 | spec.script_phases = [ 31 | { 32 | :name => 'Build Shared', 33 | :execution_position => :before_compile, 34 | :shell_path => '/bin/sh', 35 | :script => <<-SCRIPT 36 | if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then 37 | echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" 38 | exit 0 39 | fi 40 | set -ev 41 | REPO_ROOT="$PODS_TARGET_SRCROOT" 42 | "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ 43 | -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ 44 | -Pkotlin.native.cocoapods.archs="$ARCHS" \ 45 | -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" 46 | SCRIPT 47 | } 48 | ] 49 | 50 | end -------------------------------------------------------------------------------- /androidApp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # kotlinx.serialization 24 | # See (https://github.com/Kotlin/kotlinx.serialization#android) 25 | 26 | # Keep `Companion` object fields of serializable classes. 27 | # This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects. 28 | -if @kotlinx.serialization.Serializable class ** 29 | -keepclassmembers class <1> { 30 | static <1>$Companion Companion; 31 | } 32 | 33 | # Keep `serializer()` on companion objects (both default and named) of serializable classes. 34 | -if @kotlinx.serialization.Serializable class ** { 35 | static **$* *; 36 | } 37 | -keepclassmembers class <2>$<3> { 38 | kotlinx.serialization.KSerializer serializer(...); 39 | } 40 | 41 | # Keep `INSTANCE.serializer()` of serializable objects. 42 | -if @kotlinx.serialization.Serializable class ** { 43 | public static ** INSTANCE; 44 | } 45 | -keepclassmembers class <1> { 46 | public static <1> INSTANCE; 47 | kotlinx.serialization.KSerializer serializer(...); 48 | } 49 | 50 | # @Serializable and @Polymorphic are used at runtime for polymorphic serialization. 51 | -keepattributes RuntimeVisibleAnnotations,AnnotationDefault 52 | 53 | # Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`. 54 | # If you have any, uncomment and replace classes with those containing named companion objects. 55 | #-keepattributes InnerClasses # Needed for `getDeclaredClasses`. 56 | #-if @kotlinx.serialization.Serializable class 57 | #com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions. 58 | #com.example.myapplication.HasNamedCompanion2 59 | #{ 60 | # static **$* *; 61 | #} 62 | #-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept. 63 | # static <1>$$serializer INSTANCE; 64 | #} 65 | -------------------------------------------------------------------------------- /shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNUSED_VARIABLE") 2 | 3 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 4 | 5 | plugins { 6 | alias(libs.plugins.kotlin.multiplatform) 7 | alias(libs.plugins.kotlin.native.cocoapods) 8 | alias(libs.plugins.serialization) 9 | alias(libs.plugins.android.library) 10 | alias(libs.plugins.ktlint) 11 | } 12 | 13 | version = project.property("versionName") as String 14 | 15 | kotlin { 16 | jvmToolchain(17) 17 | 18 | androidTarget { 19 | publishAllLibraryVariants() 20 | } 21 | iosX64() 22 | iosArm64() 23 | iosSimulatorArm64() 24 | 25 | cocoapods { 26 | summary = "Project summary" 27 | homepage = "https://github.com/mirego/your-project" 28 | name = "Shared" 29 | 30 | podfile = project.file("../ios/Podfile") 31 | 32 | framework { 33 | baseName = "Shared" 34 | } 35 | ios.deploymentTarget = "15.0" 36 | 37 | pod("Reachability", "~> 3.2") 38 | } 39 | 40 | sourceSets { 41 | all { 42 | languageSettings { 43 | optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") 44 | } 45 | } 46 | 47 | commonMain { 48 | dependencies { 49 | implementation(libs.kotlinx.coroutines.core) 50 | implementation(libs.kotlinx.serialization.json) 51 | api(libs.konnectivity) 52 | } 53 | } 54 | 55 | commonTest { 56 | dependencies { 57 | implementation(kotlin("test")) 58 | implementation(libs.kotlinx.coroutines.test) 59 | } 60 | } 61 | 62 | androidMain { 63 | dependencies { 64 | implementation(libs.androidx.startup.runtime) 65 | } 66 | } 67 | } 68 | 69 | @OptIn(ExperimentalKotlinGradlePluginApi::class) 70 | compilerOptions { 71 | freeCompilerArgs.add("-Xexpect-actual-classes") 72 | } 73 | } 74 | 75 | android { 76 | namespace = "com.mirego.kmp.boilerplate.common" 77 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") 78 | 79 | compileSdk = 34 80 | defaultConfig { 81 | minSdk = 21 82 | 83 | buildConfigField("Integer", "VERSION_CODE", "${project.property("versionCode")}") 84 | buildConfigField("String", "VERSION_NAME", "\"$version\"") 85 | } 86 | compileOptions { 87 | sourceCompatibility = JavaVersion.VERSION_17 88 | targetCompatibility = JavaVersion.VERSION_17 89 | } 90 | buildFeatures { 91 | buildConfig = true 92 | } 93 | } 94 | 95 | ktlint { 96 | android.set(true) 97 | enableExperimentalRules.set(true) 98 | filter { 99 | exclude { element -> element.file.path.contains("generated/") } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /ios/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (7.1.2) 7 | base64 8 | bigdecimal 9 | concurrent-ruby (~> 1.0, >= 1.0.2) 10 | connection_pool (>= 2.2.5) 11 | drb 12 | i18n (>= 1.6, < 2) 13 | minitest (>= 5.1) 14 | mutex_m 15 | tzinfo (~> 2.0) 16 | addressable (2.8.5) 17 | public_suffix (>= 2.0.2, < 6.0) 18 | algoliasearch (1.27.5) 19 | httpclient (~> 2.8, >= 2.8.3) 20 | json (>= 1.5.1) 21 | atomos (0.1.3) 22 | base64 (0.2.0) 23 | bigdecimal (3.1.4) 24 | claide (1.1.0) 25 | cocoapods (1.14.3) 26 | addressable (~> 2.8) 27 | claide (>= 1.0.2, < 2.0) 28 | cocoapods-core (= 1.14.3) 29 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 30 | cocoapods-downloader (>= 2.1, < 3.0) 31 | cocoapods-plugins (>= 1.0.0, < 2.0) 32 | cocoapods-search (>= 1.0.0, < 2.0) 33 | cocoapods-trunk (>= 1.6.0, < 2.0) 34 | cocoapods-try (>= 1.1.0, < 2.0) 35 | colored2 (~> 3.1) 36 | escape (~> 0.0.4) 37 | fourflusher (>= 2.3.0, < 3.0) 38 | gh_inspector (~> 1.0) 39 | molinillo (~> 0.8.0) 40 | nap (~> 1.0) 41 | ruby-macho (>= 2.3.0, < 3.0) 42 | xcodeproj (>= 1.23.0, < 2.0) 43 | cocoapods-core (1.14.3) 44 | activesupport (>= 5.0, < 8) 45 | addressable (~> 2.8) 46 | algoliasearch (~> 1.0) 47 | concurrent-ruby (~> 1.1) 48 | fuzzy_match (~> 2.0.4) 49 | nap (~> 1.0) 50 | netrc (~> 0.11) 51 | public_suffix (~> 4.0) 52 | typhoeus (~> 1.0) 53 | cocoapods-deintegrate (1.0.5) 54 | cocoapods-downloader (2.1) 55 | cocoapods-plugins (1.0.0) 56 | nap 57 | cocoapods-search (1.0.1) 58 | cocoapods-trunk (1.6.0) 59 | nap (>= 0.8, < 2.0) 60 | netrc (~> 0.11) 61 | cocoapods-try (1.2.0) 62 | colored2 (3.1.2) 63 | concurrent-ruby (1.2.2) 64 | connection_pool (2.4.1) 65 | drb (2.2.0) 66 | ruby2_keywords 67 | escape (0.0.4) 68 | ethon (0.16.0) 69 | ffi (>= 1.15.0) 70 | ffi (1.16.3) 71 | fourflusher (2.3.1) 72 | fuzzy_match (2.0.4) 73 | gh_inspector (1.1.3) 74 | httpclient (2.8.3) 75 | i18n (1.14.1) 76 | concurrent-ruby (~> 1.0) 77 | json (2.6.3) 78 | minitest (5.20.0) 79 | molinillo (0.8.0) 80 | mutex_m (0.2.0) 81 | nanaimo (0.3.0) 82 | nap (1.1.0) 83 | netrc (0.11.0) 84 | public_suffix (4.0.7) 85 | rexml (3.2.6) 86 | ruby-macho (2.5.1) 87 | ruby2_keywords (0.0.5) 88 | typhoeus (1.4.1) 89 | ethon (>= 0.9.0) 90 | tzinfo (2.0.6) 91 | concurrent-ruby (~> 1.0) 92 | xcodeproj (1.23.0) 93 | CFPropertyList (>= 2.3.3, < 4.0) 94 | atomos (~> 0.1.3) 95 | claide (>= 1.0.2, < 2.0) 96 | colored2 (~> 3.1) 97 | nanaimo (~> 0.3.0) 98 | rexml (~> 3.2.4) 99 | 100 | PLATFORMS 101 | arm64-darwin-23 102 | 103 | DEPENDENCIES 104 | cocoapods (~> 1.14) 105 | 106 | BUNDLED WITH 107 | 2.4.20 108 | -------------------------------------------------------------------------------- /ios/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /BOILERPLATE_README.md: -------------------------------------------------------------------------------- 1 | # kmp-boilerplate 2 | 3 | | Section | Description | 4 | |-------------------------------------------------------|-----------------------------------------------------------------| 5 | | [🎯 Objectives and context](#-objectives-and-context) | Project introduction and context | 6 | | [🚧 Dependencies](#-dependencies) | Technical dependencies and how to install them | 7 | | [🏎 Kickstart](#-kickstart) | Details on how to kickstart development on the project | 8 | | [🚀 Deploy](#-deployment) | Deployment details for platforms and environments | 9 | | [🏗 Code & architecture](#-code--architecture) | Details on the application modules and technical specifications | 10 | | [🔭 Possible improvements](#-possible-improvements) | Possible code refactors, improvements and ideas | 11 | | [🚑 Troubleshooting](#-troubleshooting) | Recurring problems and proven solutions | 12 | 13 | ## 🎯 Objectives and context 14 | 15 | … 16 | 17 | ### Supported platforms 18 | 19 | | Platform | Version | 20 | |----------|------------| 21 | | iOS | ≥ 14.1 | 22 | | Android | ≥ 5.0 (21) | 23 | 24 | ## 🚧 Dependencies 25 | 26 | Every runtime dependencies are defined in the `.tool-versions` file. These external tools and 27 | dependencies are also required: 28 | 29 | - [Android Studio Dolphin](https://developer.android.com/studio) 30 | - [Xcode 14](https://developer.apple.com/xcode/) 31 | or [AppCode 2022.2](https://www.jetbrains.com/objc/) 32 | 33 | ## 🏎 Kickstart 34 | 35 | ### Initial setup 36 | 37 | #### Android 38 | 39 | 1. Open the root folder using [Android Studio](https://developer.android.com/studio) 40 | 2. Run the app on your device or simulator 🚀 41 | 42 | #### iOS 43 | 44 | 1. Open the [workspace](./ios/iosApp.xcworkspace) using [Xcode](https://developer.apple.com/xcode/) 45 | or [AppCode](https://www.jetbrains.com/objc/) 46 | 2. Specify your Development Team under the `Signing and Capabilities` tab of the `iosApp` target 47 | 3. Run the app on your device or simulator 🚀 48 | 49 | ### Tests 50 | 51 | Tests can be ran with `./gradlew test`. 52 | 53 | ### Linting 54 | 55 | Several linting and formatting tools can be ran to ensure coding style consistency: 56 | 57 | - `./gradlew ktlintFormat` ensures Kotlin code follows our guidelines and best practices 58 | - `cd ios && ./Pods/SwiftLint/swiftlint` ensures Swift code follows our guidelines and best 59 | practices 60 | 61 | ### Continuous integration 62 | 63 | The `.github/workflows/ci.yaml` workflow ensures that the codebase is in good shape on each pull 64 | request and branch push. 65 | 66 | ## 🚀 Deployment 67 | 68 | ### Versions & branches 69 | 70 | … 71 | 72 | ## 🏗 Code & architecture 73 | 74 | … 75 | 76 | ## 🔭 Possible improvements 77 | 78 | | Description | Priority | Complexity | Ideas | 79 | |-------------|----------|------------|-------| 80 | | … | … | … | … | 81 | 82 | ## 🚑 Troubleshooting 83 | 84 | … 85 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (7.1.2) 7 | base64 8 | bigdecimal 9 | concurrent-ruby (~> 1.0, >= 1.0.2) 10 | connection_pool (>= 2.2.5) 11 | drb 12 | i18n (>= 1.6, < 2) 13 | minitest (>= 5.1) 14 | mutex_m 15 | tzinfo (~> 2.0) 16 | addressable (2.8.5) 17 | public_suffix (>= 2.0.2, < 6.0) 18 | algoliasearch (1.27.5) 19 | httpclient (~> 2.8, >= 2.8.3) 20 | json (>= 1.5.1) 21 | atomos (0.1.3) 22 | base64 (0.2.0) 23 | bigdecimal (3.1.4) 24 | claide (1.1.0) 25 | cocoapods (1.14.3) 26 | addressable (~> 2.8) 27 | claide (>= 1.0.2, < 2.0) 28 | cocoapods-core (= 1.14.3) 29 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 30 | cocoapods-downloader (>= 2.1, < 3.0) 31 | cocoapods-plugins (>= 1.0.0, < 2.0) 32 | cocoapods-search (>= 1.0.0, < 2.0) 33 | cocoapods-trunk (>= 1.6.0, < 2.0) 34 | cocoapods-try (>= 1.1.0, < 2.0) 35 | colored2 (~> 3.1) 36 | escape (~> 0.0.4) 37 | fourflusher (>= 2.3.0, < 3.0) 38 | gh_inspector (~> 1.0) 39 | molinillo (~> 0.8.0) 40 | nap (~> 1.0) 41 | ruby-macho (>= 2.3.0, < 3.0) 42 | xcodeproj (>= 1.23.0, < 2.0) 43 | cocoapods-core (1.14.3) 44 | activesupport (>= 5.0, < 8) 45 | addressable (~> 2.8) 46 | algoliasearch (~> 1.0) 47 | concurrent-ruby (~> 1.1) 48 | fuzzy_match (~> 2.0.4) 49 | nap (~> 1.0) 50 | netrc (~> 0.11) 51 | public_suffix (~> 4.0) 52 | typhoeus (~> 1.0) 53 | cocoapods-deintegrate (1.0.5) 54 | cocoapods-disable-podfile-validations (0.2.0) 55 | cocoapods-downloader (2.1) 56 | cocoapods-generate (2.2.4) 57 | cocoapods-disable-podfile-validations (>= 0.1.1, < 0.3.0) 58 | cocoapods-plugins (1.0.0) 59 | nap 60 | cocoapods-search (1.0.1) 61 | cocoapods-trunk (1.6.0) 62 | nap (>= 0.8, < 2.0) 63 | netrc (~> 0.11) 64 | cocoapods-try (1.2.0) 65 | colored2 (3.1.2) 66 | concurrent-ruby (1.2.2) 67 | connection_pool (2.4.1) 68 | drb (2.2.0) 69 | ruby2_keywords 70 | escape (0.0.4) 71 | ethon (0.16.0) 72 | ffi (>= 1.15.0) 73 | ffi (1.16.3) 74 | fourflusher (2.3.1) 75 | fuzzy_match (2.0.4) 76 | gh_inspector (1.1.3) 77 | httpclient (2.8.3) 78 | i18n (1.14.1) 79 | concurrent-ruby (~> 1.0) 80 | json (2.6.3) 81 | minitest (5.20.0) 82 | molinillo (0.8.0) 83 | mutex_m (0.2.0) 84 | nanaimo (0.3.0) 85 | nap (1.1.0) 86 | netrc (0.11.0) 87 | public_suffix (4.0.7) 88 | rexml (3.2.6) 89 | ruby-macho (2.5.1) 90 | ruby2_keywords (0.0.5) 91 | typhoeus (1.4.1) 92 | ethon (>= 0.9.0) 93 | tzinfo (2.0.6) 94 | concurrent-ruby (~> 1.0) 95 | xcodeproj (1.23.0) 96 | CFPropertyList (>= 2.3.3, < 4.0) 97 | atomos (~> 0.1.3) 98 | claide (>= 1.0.2, < 2.0) 99 | colored2 (~> 3.1) 100 | nanaimo (~> 0.3.0) 101 | rexml (~> 3.2.4) 102 | 103 | PLATFORMS 104 | ruby 105 | 106 | DEPENDENCIES 107 | cocoapods (~> 1.14) 108 | cocoapods-generate (~> 2.2) 109 | 110 | BUNDLED WITH 111 | 2.4.8 112 | -------------------------------------------------------------------------------- /ios/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # Unless otherwise noted, rules are disabled because they reduce the readability of the code 2 | # See https://realm.github.io/SwiftLint/rule-directory.html for more info on each rule 3 | 4 | analyzer_rules: 5 | - explicit_self 6 | - unused_declaration 7 | - unused_import 8 | 9 | # Rule identifiers to exclude from running 10 | disabled_rules: 11 | - comment_spacing 12 | - force_cast 13 | - function_body_length 14 | 15 | # All opt-in rules are listed, with the ones we do not want commented out so we can know which rules we don't want and which rules are new when upgrading Swiftlint 16 | opt_in_rules: 17 | - anyobject_protocol 18 | - array_init 19 | - attributes 20 | #- closure_body_length 21 | - closure_end_indentation 22 | - closure_spacing 23 | - collection_alignment 24 | - conditional_returns_on_newline 25 | - contains_over_filter_count 26 | - contains_over_filter_is_empty 27 | - contains_over_first_not_nil 28 | - contains_over_range_nil_comparison 29 | - convenience_type 30 | - discouraged_object_literal 31 | - discouraged_optional_boolean 32 | - discouraged_optional_collection 33 | - empty_collection_literal 34 | - empty_count 35 | - empty_string 36 | - empty_xctest_method 37 | - enum_case_associated_values_count 38 | - expiring_todo 39 | #- explicit_acl 40 | #- explicit_enum_raw_value 41 | - explicit_init 42 | #- explicit_top_level_acl 43 | #- explicit_type_interface 44 | - extension_access_modifier 45 | - fallthrough 46 | - fatal_error_message 47 | - file_header 48 | #- file_name 49 | - file_name_no_space 50 | #- file_types_order 51 | - first_where 52 | - flatmap_over_map_reduce 53 | #- force_unwrapping 54 | - function_default_parameter_at_end 55 | - identical_operands 56 | - implicit_return 57 | #- implicitly_unwrapped_optional 58 | #- indentation_width 59 | - joined_default_parameter 60 | - last_where 61 | - legacy_multiple 62 | - legacy_random 63 | - let_var_whitespace 64 | - literal_expression_end_indentation 65 | #- lower_acl_than_parent 66 | #- missing_docs 67 | - modifier_order 68 | #- multiline_arguments 69 | #- multiline_arguments_brackets 70 | #- multiline_function_chains 71 | - multiline_literal_brackets 72 | - multiline_parameters 73 | - multiline_parameters_brackets 74 | - nimble_operator 75 | - no_extension_access_modifier 76 | #- no_grouping_extension 77 | - nslocalizedstring_key 78 | - nslocalizedstring_require_bundle 79 | - number_separator 80 | - object_literal 81 | - operator_usage_whitespace 82 | - optional_enum_case_matching 83 | - overridden_super_call 84 | - override_in_extension 85 | - pattern_matching_keywords 86 | - prefer_self_type_over_type_of_self 87 | - prefer_zero_over_explicit_init 88 | - prefixed_toplevel_constant 89 | - private_action 90 | - private_outlet 91 | - prohibited_interface_builder 92 | - prohibited_super_call 93 | - quick_discouraged_call 94 | - quick_discouraged_focused_test 95 | - quick_discouraged_pending_test 96 | - raw_value_for_camel_cased_codable_enum 97 | - reduce_into 98 | - redundant_nil_coalescing 99 | - redundant_type_annotation 100 | #- required_deinit 101 | #- required_enum_case 102 | - single_test_class 103 | - sorted_first_last 104 | - sorted_imports 105 | - static_operator 106 | - strict_fileprivate 107 | - strong_iboutlet 108 | #- switch_case_on_newline 109 | - toggle_bool 110 | - trailing_closure 111 | #- type_contents_order 112 | - unavailable_function 113 | - unneeded_parentheses_in_closure_argument 114 | - unowned_variable_capture 115 | - untyped_error_in_catch 116 | #- vertical_parameter_alignment_on_call 117 | #- vertical_whitespace_between_cases 118 | #- vertical_whitespace_closing_braces 119 | #- vertical_whitespace_opening_braces 120 | - xct_specific_matcher 121 | - yoda_condition 122 | 123 | # Paths to exclude 124 | excluded: 125 | - Pods 126 | - Data 127 | - R.generated.swift 128 | - .bundle 129 | # Add your own exclusion here 130 | 131 | conditional_returns_on_newline: 132 | if_only: true 133 | 134 | # Use the same deployment target as the project 135 | deployment_target: 136 | iOS_deployment_target: 9.0 137 | tvOS_deployment_target: 9.1 138 | watchOS_deployment_target: 2.0 139 | 140 | file_length: 141 | ignore_comment_only_lines: true 142 | 143 | line_length: 240 144 | 145 | trailing_whitespace: 146 | ignores_empty_lines: true 147 | ignores_comments: true 148 | 149 | unused_declaration: 150 | include_public_and_open: true 151 | 152 | file_header: 153 | forbidden_string: // 154 | 155 | identifier_name: # exclude length validation 156 | min_length: 1 157 | max_length: 999 158 | 159 | cyclomatic_complexity: 160 | ignores_case_statements: true 161 | 162 | discouraged_object_literal: 163 | color_literal: false 164 | 165 | attributes: 166 | always_on_line_above: [ "@objc" ] 167 | -------------------------------------------------------------------------------- /boilerplate-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Configuration 5 | # ----------------------------------------------------------------------------- 6 | 7 | pascalCaseRegex="^[A-Z][a-z]+([A-Z][a-z]+)*$" 8 | packageNameRegex="^([a-z]+\.){2}[a-z]+$" 9 | 10 | # The identifiers above will be replaced in the content of the files found below 11 | content=$(find . -type f \( \ 12 | -name "*.xml" -or \ 13 | -name "*.properties" -or \ 14 | -name "*.kt" -or \ 15 | -name "*.swift" -or \ 16 | -name "*.gradle.kts" -or \ 17 | -name "*.podspec" -or \ 18 | -name "Podfile" -or \ 19 | -name "*.plist" -or \ 20 | -name "*.pbxproj" \ 21 | \) \ 22 | -and ! -path "./build/*" \ 23 | -and ! -path "./boilerplate-setup.sh" \ 24 | -and ! -path "./.idea/*" \ 25 | -and ! -path "./.git/*" \ 26 | -and ! -path "./.gradle.kts/*" \ 27 | ) 28 | 29 | # ----------------------------------------------------------------------------- 30 | # Validation 31 | # ----------------------------------------------------------------------------- 32 | 33 | isPascalCase() { 34 | if [[ "$1" =~ $pascalCaseRegex ]]; then 35 | argumentIsPascalCase=true 36 | else 37 | echo 'You must specify a name in PascalCase.' 38 | fi 39 | } 40 | 41 | isPackageNameValid() { 42 | if [[ $1 =~ $packageNameRegex ]]; then 43 | argumentIsPascalCase=true 44 | else 45 | echo 'You must specify a valid package name format (ex: com.example.project)' 46 | fi 47 | } 48 | 49 | # ----------------------------------------------------------------------------- 50 | # Helper functions 51 | # ----------------------------------------------------------------------------- 52 | 53 | header() { 54 | echo "\033[0;33m▶ $1\033[0m" 55 | } 56 | 57 | success() { 58 | echo "\033[0;32m▶ $1\033[0m" 59 | } 60 | 61 | run() { 62 | echo ${@} 63 | eval "${@}" 64 | } 65 | 66 | # ----------------------------------------------------------------------------- 67 | # Execution 68 | # ----------------------------------------------------------------------------- 69 | 70 | read -p $'\nWhat is your project name? (ex: MyProject)\n' PROJECT_NAME 71 | 72 | argumentIsPascalCase=false 73 | while [ ${argumentIsPascalCase} == "false" ] 74 | do 75 | read -p $'\nWhat is the package name for this project? (ex: com.my.project)\n' USER_INPUT 76 | isPackageNameValid "$USER_INPUT" 77 | done 78 | PACKAGE_NAME=${USER_INPUT} 79 | 80 | argumentIsPascalCase=false 81 | while [ ${argumentIsPascalCase} == "false" ] 82 | do 83 | read -p $'\nWhat is the name of the shared code\'s iOS framework? (ex: MyProject)\n' USER_INPUT 84 | isPascalCase "$USER_INPUT" 85 | done 86 | FRAMEWORK_NAME=${USER_INPUT} 87 | 88 | header "Updating project name all files..." 89 | projectName="kmp-boilerplate" 90 | run "/usr/bin/sed -i .bak 's/$projectName/$PROJECT_NAME/g' settings.gradle.kts" 91 | run "/usr/bin/sed -i .bak 's/$projectName/$PROJECT_NAME/g' shared/build.gradle.kts" 92 | run "/usr/bin/sed -i .bak 's/$projectName/$PROJECT_NAME/g' ios/iosApp/Info.plist" 93 | run "/usr/bin/sed -i .bak 's/$projectName/$PROJECT_NAME/g' BOILERPLATE_README.md" 94 | success "Done!\n" 95 | 96 | header "Updating iOS Framework name..." 97 | frameworkName="Shared" 98 | for file in $content; do 99 | if [[ $file != *.kt ]]; then 100 | run "/usr/bin/sed -i .bak s/$frameworkName/$FRAMEWORK_NAME/g $file" 101 | fi 102 | done 103 | mv shared/"${frameworkName}".podspec shared/"${FRAMEWORK_NAME}".podspec 104 | success "Done!\n" 105 | 106 | header "Updating project.pbxproj product bundle identifier..." 107 | bundleIdentifier="com.mirego.kmp.boilerplate" 108 | run "/usr/bin/sed -i .bak s/$bundleIdentifier/$PACKAGE_NAME/g ios/iosApp.xcodeproj/project.pbxproj" 109 | success "Done!\n" 110 | 111 | header "Replacing package name in all files..." 112 | packageName="com.mirego.kmp.boilerplate" 113 | for file in $content; do 114 | run "/usr/bin/sed -i .bak s/$packageName/$PACKAGE_NAME/g $file" 115 | done 116 | success "Done!\n" 117 | 118 | header "Renaming folders..." 119 | packageNameAsPath=${PACKAGE_NAME//\./\/} 120 | for folder in $(find . -path '*/com/mirego/kmp/boilerplate*' -type d -and ! -path '*/generated/*' -and ! -path '*/build/*' -and ! -path '*/ios/*'); do 121 | newFolder="${folder//com\/mirego\/kmp\/boilerplate/$packageNameAsPath}" 122 | mkdir -p "$newFolder" 123 | cp -av "$folder/." "$newFolder/" 124 | rm -rf "$folder" 125 | done 126 | find "." -empty -type d -delete 127 | success "Done!\n" 128 | 129 | header "Cleaning bak files..." 130 | for file in $(find . -type f -name '*.bak'); do 131 | run rm "$file" 132 | done 133 | success "Done!\n" 134 | 135 | header "Replacing BOILERPLATE_README.md with project README.md..." 136 | run rm -fr README.md && mv BOILERPLATE_README.md README.md 137 | success "Done!\n" 138 | 139 | header "Removing boilerplate license → https://choosealicense.com" 140 | run rm -fr LICENSE.md 141 | success "Done!\n" 142 | 143 | header "Changing the Dependency-Check report format to HTML" 144 | run sed -i '' 's/SARIF/HTML/' build.gradle.kts 145 | success "Done!\n" 146 | 147 | header "Removing boilerplate setup script..." 148 | run rm -fr boilerplate-setup.sh 149 | success "Done!\n" 150 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |


This repository is the stable base upon which we build our Kotlin Multiplatform projects at Mirego.
We want to share it with the world so you can build awesome multiplatform applications too.

4 | 5 | 6 | 7 |
8 | 9 | ## Introduction 10 | 11 | To learn more about _why_ we created and maintain this boilerplate project, read 12 | our [blog post](https://shift.mirego.com/en/boilerplate-projects). 13 | 14 | ## Content 15 | 16 | This boilerplate comes with batteries included, you’ll find: 17 | 18 | - A brand new [Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html) project using 19 | [Kotlin 1.9.21](https://github.com/JetBrains/kotlin/releases/tag/v1.9.21) and 20 | the [Cocoapods Plugin](https://kotlinlang.org/docs/native-cocoapods.html) 21 | - An Android app using [Jetpack Compose](https://developer.android.com/jetpack/compose) 22 | - An iOS app using [SwiftUI](https://developer.apple.com/xcode/swiftui) 23 | - Asynchronous & multithreading support 24 | with [Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) 25 | - Model serialization support 26 | with [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 27 | - Opinionated Kotlin linting with [Ktlint](https://github.com/pinterest/ktlint) 28 | - Opinionated Swift linting with [SwiftLint](https://github.com/realm/SwiftLint) 29 | - A [CI workflow](.github/workflows/ci.yaml) 30 | using [GitHub actions](https://docs.github.com/en/actions) 31 | - Translations with [Trikot.KWord](https://github.com/mirego/trikot/tree/master/trikot-kword) 32 | and [Accent](https://www.accent.reviews) (using a scheduled GitHub 33 | Actions [workflow](./.github/workflows/accent.yaml)) 34 | - A clean and useful `README.md` template (in [english](./BOILERPLATE_README.md)) 35 | 36 | ## Usage 37 | 38 | There are 2 ways to use this boilerplate: 39 | 40 | 1. From the [`main` branch](https://github.com/mirego/kmp-boilerplate/tree/main) — This branch is 41 | the "lean" version, it does not contain any strongly opinionated 42 | libraries or tools. It is a good starting point if you want to build your own boilerplate. 43 | 2. From the [`main-full` branch](https://github.com/mirego/kmp-boilerplate/tree/main-full) — This 44 | branch contains all the opinionated libraries and tools 45 | described in the [Preferred libraries](#preferred-libraries) section. It is a good starting 46 | point if you want to quickly start building your app the _Mirego way_. 47 | 48 | ### With GitHub template 49 | 50 | 1. Pick your preferred branch (`main` or `main-full`) 51 | 2. Click on the [**Use this template**](https://github.com/mirego/kmp-boilerplate/generate) 52 | button to create a new repository 53 | 3. Clone your newly created project (`git clone https://github.com/you/repo.git`) 54 | 4. Run the boilerplate setup script (`./boilerplate-setup.sh`) 55 | 5. Commit the changes (`git commit -a -m "Rename kmp-boilerplate parts"`) 56 | 57 | ### Without GitHub template 58 | 59 | 1. Clone this project (`git clone https://github.com/mirego/kmp-boilerplate.git`) 60 | 2. Pick your preferred branch (`git checkout main` or `git checkout main-full`) 61 | 3. Delete the internal Git directory (`rm -rf .git`) 62 | 4. Run the boilerplate setup script (`./boilerplate-setup.sh`) 63 | 5. Create a new Git repository (`git init`) 64 | 6. Create the initial Git commit (`git commit -a -m "Initial commit using kmp-boilerplate"`) 65 | 66 | ### Building the project 67 | 68 | * Run `asdf install` to install the dependencies described in `.tool-versions` on your system 69 | * Make sure you have [Bundler](https://rubygems.org/gems/bundler) installed (`gem install bundler`) 70 | 71 | #### Android 72 | 73 | 1. Install shared code specific gems at the root of the project (`bundle install`) 74 | 2. Open the root folder using [Android Studio](https://developer.android.com/studio) 75 | 3. Sync Gradle dependencies 76 | 4. Build and run the app on your device or simulator 🚀 77 | 78 | #### iOS 79 | 80 | 1. Install iOS specific gems in the `/ios` folder (`cd ios && bundle install`) 81 | 2. Open the [workspace](./ios/iosApp.xcworkspace) using [Xcode](https://developer.apple.com/xcode/) 82 | 3. Specify your Development Team under the `Signing and Capabilities` tab of the `iosApp` target 83 | 4. Build and run the app on your device or simulator 🚀 84 | 85 | ## Preferred libraries 86 | 87 | Some frequently used libraries aren’t included by default in this boilerplate since all projects 88 | have their own needs and requirements. Here’s a list of our preferred libraries to help you get 89 | started: 90 | 91 | | Category | Libraries | 92 | |--------------------------|-------------------------------------------------------------------------------------------------------------------------| 93 | | HTTP networking | [`ktor`](https://ktor.io/) | 94 | | GraphQL client | [`apollo-kotlin`](https://www.apollographql.com/docs/kotlin/) | 95 | | Persisted preferences | [`multiplatform-setttings`](https://github.com/russhwolf/multiplatform-settings) | 96 | | I/O & File system | [`okio`](https://square.github.io/okio/multiplatform/) | 97 | | Mocking (Unit tests) | [`mockk`](https://mockk.io/) | 98 | | Declarative UI framework | [`trikot-viewmodels-declarative-flow`](https://github.com/mirego/trikot/tree/master/trikot-viewmodels-declarative-flow) | 99 | | Date & time | [`kotlinx-datetime`](https://github.com/Kotlin/kotlinx-datetime) | 100 | 101 | ## OWASP Dependency-Check 102 | 103 | [OWASP Dependency-Check](http://jeremylong.github.io/DependencyCheck/index.html) is installed as a 104 | Gradle plugin to scan your project to identify the use of known vulnerable components. It mainly 105 | checks for vulnerabilities in Gradle dependencies, but 106 | if [bundle-audit](https://github.com/rubysec/bundler-audit) is present on the system, it will also 107 | scan the Ruby Gems dependencies. It also has the capability to scan for Cocoapods/Swift Package 108 | Manager dependencies if executed on a macOS system. 109 | 110 | ## License 111 | 112 | Kmp Boilerplate is © 2023 [Mirego](https://www.mirego.com) and may be freely distributed under 113 | the [New BSD license](http://opensource.org/licenses/BSD-3-Clause). See 114 | the [`LICENSE.md`](https://github.com/mirego/kmp-boilerplate/blob/master/LICENSE.md) file. 115 | 116 | ## About Mirego 117 | 118 | [Mirego](https://www.mirego.com) is a team of passionate people who believe that work is a place 119 | where you can innovate and have fun. We’re a team of [talented people](https://life.mirego.com) who 120 | imagine and build beautiful Web and mobile applications. We come together to share ideas 121 | and [change the world](http://www.mirego.org). 122 | 123 | We also [love open-source software](https://open.mirego.com) and we try to give back to the 124 | community as much as we can. 125 | -------------------------------------------------------------------------------- /ios/iosApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 11 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 12 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; 13 | 7555FF83242A565900829871 /* GreetingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* GreetingView.swift */; }; 14 | 9B8ACFDB4E332DFCA8B97CBB /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E4E1328B104D05A50A097EE /* Pods_iosApp.framework */; }; 15 | BC5700EB2B1A94D200525C22 /* PreviewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5700EA2B1A94D200525C22 /* PreviewContext.swift */; }; 16 | BC83B466276E4F080053E064 /* FlowUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC83B465276E4F080053E064 /* FlowUtils.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 21 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 22 | 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; 23 | 308A8A1989CCC0B3DD133EE0 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; 24 | 4E4E1328B104D05A50A097EE /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 7555FF82242A565900829871 /* GreetingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GreetingView.swift; sourceTree = ""; }; 27 | 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | BC5700EA2B1A94D200525C22 /* PreviewContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewContext.swift; sourceTree = ""; }; 29 | BC83B465276E4F080053E064 /* FlowUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowUtils.swift; sourceTree = ""; }; 30 | E232C917135C2C1E3BC8748A /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | FA7C6C218E43EC797272100C /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | 9B8ACFDB4E332DFCA8B97CBB /* Pods_iosApp.framework in Frameworks */, 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | 058557D7273AAEEB004C7B11 /* Preview Content */ = { 46 | isa = PBXGroup; 47 | children = ( 48 | 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, 49 | BC5700EA2B1A94D200525C22 /* PreviewContext.swift */, 50 | ); 51 | path = "Preview Content"; 52 | sourceTree = ""; 53 | }; 54 | 7555FF72242A565900829871 = { 55 | isa = PBXGroup; 56 | children = ( 57 | 7555FF7D242A565900829871 /* iosApp */, 58 | 7555FF7C242A565900829871 /* Products */, 59 | D27C9C3C3252A21FE12B97B5 /* Pods */, 60 | C8C629BFDC2144230B71E3BC /* Frameworks */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | 7555FF7C242A565900829871 /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 7555FF7B242A565900829871 /* iosApp.app */, 68 | ); 69 | name = Products; 70 | sourceTree = ""; 71 | }; 72 | 7555FF7D242A565900829871 /* iosApp */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 7555FF8C242A565B00829871 /* Info.plist */, 76 | 058557BA273AAA24004C7B11 /* Assets.xcassets */, 77 | 2152FB032600AC8F00CF470E /* iOSApp.swift */, 78 | 7555FF82242A565900829871 /* GreetingView.swift */, 79 | BC83B464276E4EF80053E064 /* Utils */, 80 | 058557D7273AAEEB004C7B11 /* Preview Content */, 81 | ); 82 | path = iosApp; 83 | sourceTree = ""; 84 | }; 85 | BC83B464276E4EF80053E064 /* Utils */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | BC83B465276E4F080053E064 /* FlowUtils.swift */, 89 | ); 90 | path = Utils; 91 | sourceTree = ""; 92 | }; 93 | C8C629BFDC2144230B71E3BC /* Frameworks */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 4E4E1328B104D05A50A097EE /* Pods_iosApp.framework */, 97 | ); 98 | name = Frameworks; 99 | sourceTree = ""; 100 | }; 101 | D27C9C3C3252A21FE12B97B5 /* Pods */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 308A8A1989CCC0B3DD133EE0 /* Pods-iosApp.debug.xcconfig */, 105 | E232C917135C2C1E3BC8748A /* Pods-iosApp.release.xcconfig */, 106 | ); 107 | path = Pods; 108 | sourceTree = ""; 109 | }; 110 | /* End PBXGroup section */ 111 | 112 | /* Begin PBXNativeTarget section */ 113 | 7555FF7A242A565900829871 /* iosApp */ = { 114 | isa = PBXNativeTarget; 115 | buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; 116 | buildPhases = ( 117 | 033D393BC425899E720B28CF /* [CP] Check Pods Manifest.lock */, 118 | 7555FF77242A565900829871 /* Sources */, 119 | 7555FF79242A565900829871 /* Resources */, 120 | FA7C6C218E43EC797272100C /* Frameworks */, 121 | AA1E76016DFABDB8F08174B0 /* [CP] Embed Pods Frameworks */, 122 | BCE1AE6C2922BB2E00EBCEC6 /* Run SwiftLint */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = iosApp; 129 | productName = iosApp; 130 | productReference = 7555FF7B242A565900829871 /* iosApp.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | /* End PBXNativeTarget section */ 134 | 135 | /* Begin PBXProject section */ 136 | 7555FF73242A565900829871 /* Project object */ = { 137 | isa = PBXProject; 138 | attributes = { 139 | LastSwiftUpdateCheck = 1130; 140 | LastUpgradeCheck = 1130; 141 | ORGANIZATIONNAME = com.mirego; 142 | TargetAttributes = { 143 | 7555FF7A242A565900829871 = { 144 | CreatedOnToolsVersion = 11.3.1; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; 149 | compatibilityVersion = "Xcode 9.3"; 150 | developmentRegion = en; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | en, 154 | Base, 155 | ); 156 | mainGroup = 7555FF72242A565900829871; 157 | productRefGroup = 7555FF7C242A565900829871 /* Products */; 158 | projectDirPath = ""; 159 | projectRoot = ""; 160 | targets = ( 161 | 7555FF7A242A565900829871 /* iosApp */, 162 | ); 163 | }; 164 | /* End PBXProject section */ 165 | 166 | /* Begin PBXResourcesBuildPhase section */ 167 | 7555FF79242A565900829871 /* Resources */ = { 168 | isa = PBXResourcesBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, 172 | 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | /* End PBXResourcesBuildPhase section */ 177 | 178 | /* Begin PBXShellScriptBuildPhase section */ 179 | 033D393BC425899E720B28CF /* [CP] Check Pods Manifest.lock */ = { 180 | isa = PBXShellScriptBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | ); 184 | inputFileListPaths = ( 185 | ); 186 | inputPaths = ( 187 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 188 | "${PODS_ROOT}/Manifest.lock", 189 | ); 190 | name = "[CP] Check Pods Manifest.lock"; 191 | outputFileListPaths = ( 192 | ); 193 | outputPaths = ( 194 | "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 199 | showEnvVarsInLog = 0; 200 | }; 201 | AA1E76016DFABDB8F08174B0 /* [CP] Embed Pods Frameworks */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputFileListPaths = ( 207 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", 208 | ); 209 | name = "[CP] Embed Pods Frameworks"; 210 | outputFileListPaths = ( 211 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | shellPath = /bin/sh; 215 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n"; 216 | showEnvVarsInLog = 0; 217 | }; 218 | BCE1AE6C2922BB2E00EBCEC6 /* Run SwiftLint */ = { 219 | isa = PBXShellScriptBuildPhase; 220 | alwaysOutOfDate = 1; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | ); 224 | inputFileListPaths = ( 225 | ); 226 | inputPaths = ( 227 | ); 228 | name = "Run SwiftLint"; 229 | outputFileListPaths = ( 230 | ); 231 | outputPaths = ( 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | shellPath = /bin/sh; 235 | shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; 236 | }; 237 | /* End PBXShellScriptBuildPhase section */ 238 | 239 | /* Begin PBXSourcesBuildPhase section */ 240 | 7555FF77242A565900829871 /* Sources */ = { 241 | isa = PBXSourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, 245 | 7555FF83242A565900829871 /* GreetingView.swift in Sources */, 246 | BC5700EB2B1A94D200525C22 /* PreviewContext.swift in Sources */, 247 | BC83B466276E4F080053E064 /* FlowUtils.swift in Sources */, 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | /* End PBXSourcesBuildPhase section */ 252 | 253 | /* Begin XCBuildConfiguration section */ 254 | 7555FFA3242A565B00829871 /* Debug */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_ENABLE_OBJC_WEAK = YES; 265 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 266 | CLANG_WARN_BOOL_CONVERSION = YES; 267 | CLANG_WARN_COMMA = YES; 268 | CLANG_WARN_CONSTANT_CONVERSION = YES; 269 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 271 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INFINITE_RECURSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 278 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 280 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 281 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 282 | CLANG_WARN_STRICT_PROTOTYPES = YES; 283 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 284 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 285 | CLANG_WARN_UNREACHABLE_CODE = YES; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | COPY_PHASE_STRIP = NO; 288 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | ENABLE_TESTABILITY = YES; 291 | GCC_C_LANGUAGE_STANDARD = gnu11; 292 | GCC_DYNAMIC_NO_PIC = NO; 293 | GCC_NO_COMMON_BLOCKS = YES; 294 | GCC_OPTIMIZATION_LEVEL = 0; 295 | GCC_PREPROCESSOR_DEFINITIONS = ( 296 | "DEBUG=1", 297 | "$(inherited)", 298 | ); 299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 301 | GCC_WARN_UNDECLARED_SELECTOR = YES; 302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 303 | GCC_WARN_UNUSED_FUNCTION = YES; 304 | GCC_WARN_UNUSED_VARIABLE = YES; 305 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 306 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 307 | MTL_FAST_MATH = YES; 308 | ONLY_ACTIVE_ARCH = YES; 309 | SDKROOT = iphoneos; 310 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 311 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 312 | }; 313 | name = Debug; 314 | }; 315 | 7555FFA4242A565B00829871 /* Release */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ALWAYS_SEARCH_USER_PATHS = NO; 319 | CLANG_ANALYZER_NONNULL = YES; 320 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 321 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 322 | CLANG_CXX_LIBRARY = "libc++"; 323 | CLANG_ENABLE_MODULES = YES; 324 | CLANG_ENABLE_OBJC_ARC = YES; 325 | CLANG_ENABLE_OBJC_WEAK = YES; 326 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 327 | CLANG_WARN_BOOL_CONVERSION = YES; 328 | CLANG_WARN_COMMA = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 332 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INFINITE_RECURSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 339 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 342 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 343 | CLANG_WARN_STRICT_PROTOTYPES = YES; 344 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 345 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | COPY_PHASE_STRIP = NO; 349 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 350 | ENABLE_NS_ASSERTIONS = NO; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | GCC_C_LANGUAGE_STANDARD = gnu11; 353 | GCC_NO_COMMON_BLOCKS = YES; 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 361 | MTL_ENABLE_DEBUG_INFO = NO; 362 | MTL_FAST_MATH = YES; 363 | SDKROOT = iphoneos; 364 | SWIFT_COMPILATION_MODE = wholemodule; 365 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 366 | VALIDATE_PRODUCT = YES; 367 | }; 368 | name = Release; 369 | }; 370 | 7555FFA6242A565B00829871 /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | baseConfigurationReference = 308A8A1989CCC0B3DD133EE0 /* Pods-iosApp.debug.xcconfig */; 373 | buildSettings = { 374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 375 | CODE_SIGN_STYLE = Automatic; 376 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; 377 | DEVELOPMENT_TEAM = 4Q596JWQC5; 378 | ENABLE_PREVIEWS = YES; 379 | INFOPLIST_FILE = iosApp/Info.plist; 380 | LD_RUNPATH_SEARCH_PATHS = ( 381 | "$(inherited)", 382 | "@executable_path/Frameworks", 383 | ); 384 | MARKETING_VERSION = 0.0.1; 385 | PRODUCT_BUNDLE_IDENTIFIER = com.mirego.kmp.boilerplate; 386 | PRODUCT_NAME = "$(TARGET_NAME)"; 387 | SWIFT_VERSION = 5.0; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | }; 390 | name = Debug; 391 | }; 392 | 7555FFA7242A565B00829871 /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | baseConfigurationReference = E232C917135C2C1E3BC8748A /* Pods-iosApp.release.xcconfig */; 395 | buildSettings = { 396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 397 | CODE_SIGN_STYLE = Automatic; 398 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; 399 | DEVELOPMENT_TEAM = 4Q596JWQC5; 400 | ENABLE_PREVIEWS = YES; 401 | INFOPLIST_FILE = iosApp/Info.plist; 402 | LD_RUNPATH_SEARCH_PATHS = ( 403 | "$(inherited)", 404 | "@executable_path/Frameworks", 405 | ); 406 | MARKETING_VERSION = 0.0.1; 407 | PRODUCT_BUNDLE_IDENTIFIER = com.mirego.kmp.boilerplate; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | SWIFT_VERSION = 5.0; 410 | TARGETED_DEVICE_FAMILY = "1,2"; 411 | }; 412 | name = Release; 413 | }; 414 | /* End XCBuildConfiguration section */ 415 | 416 | /* Begin XCConfigurationList section */ 417 | 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { 418 | isa = XCConfigurationList; 419 | buildConfigurations = ( 420 | 7555FFA3242A565B00829871 /* Debug */, 421 | 7555FFA4242A565B00829871 /* Release */, 422 | ); 423 | defaultConfigurationIsVisible = 0; 424 | defaultConfigurationName = Release; 425 | }; 426 | 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { 427 | isa = XCConfigurationList; 428 | buildConfigurations = ( 429 | 7555FFA6242A565B00829871 /* Debug */, 430 | 7555FFA7242A565B00829871 /* Release */, 431 | ); 432 | defaultConfigurationIsVisible = 0; 433 | defaultConfigurationName = Release; 434 | }; 435 | /* End XCConfigurationList section */ 436 | }; 437 | rootObject = 7555FF73242A565900829871 /* Project object */; 438 | } 439 | --------------------------------------------------------------------------------